From 4f5c6490bb1d898fe44c413811744493fcb70e50 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 3 Jun 2023 09:59:13 +0100 Subject: [PATCH 01/69] initial experiment --- .../scala/zio/dynamodb/DynamoDBQuery.scala | 15 +- .../zio/dynamodb/proofs/IsPrimaryKey.scala | 9 ++ ...StudentZioDynamoDbTypeSafeAPIExample.scala | 129 ++++++++++-------- 3 files changed, 93 insertions(+), 60 deletions(-) create mode 100644 dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index f47e7fa73..09fde0ca0 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -1,7 +1,7 @@ package zio.dynamodb import zio.dynamodb.DynamoDBError.ValueNotFound -import zio.dynamodb.proofs.{ CanFilter, CanWhere, CanWhereKey } +import zio.dynamodb.proofs.{ CanFilter, CanWhere, CanWhereKey, IsPrimaryKey } import zio.dynamodb.DynamoDBQuery.BatchGetItem.TableGet import zio.dynamodb.DynamoDBQuery.BatchWriteItem.{ Delete, Put } import zio.dynamodb.DynamoDBQuery.{ @@ -493,6 +493,19 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } + def get[A: Schema, B: IsPrimaryKey]( + tableName: String, + partitionKey: UpdateExpression.Action.SetAction[A, B], + projections: ProjectionExpression[_, _]* + ): DynamoDBQuery[A, Either[DynamoDBError, A]] = ??? + + def get[A: Schema, B: IsPrimaryKey, C: IsPrimaryKey]( + tableName: String, + partitionKey: UpdateExpression.Action.SetAction[A, B], + sortKey: UpdateExpression.Action.SetAction[A, C], + projections: ProjectionExpression[_, _]* + ): DynamoDBQuery[A, Either[DynamoDBError, A]] = ??? + private[dynamodb] def fromItem[A: Schema](item: Item): Either[DynamoDBError, A] = { val av = ToAttributeValue.attrMapToAttributeValue.toAttributeValue(item) av.decode(Schema[A]) diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala new file mode 100644 index 000000000..b630f9bc4 --- /dev/null +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -0,0 +1,9 @@ +package zio.dynamodb.proofs + +sealed trait IsPrimaryKey[A] + +object IsPrimaryKey { + implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} + implicit val stringIsPrimaryKey = new IsPrimaryKey[String] {} + // todo binary data +} diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index 20426445f..1ec49e74e 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -22,62 +22,73 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { val ceStudentWithElephant: ConditionExpression[Student with Elephant] = ce2 && elephantCe private val program = for { - _ <- createTable("student", KeySchema("email", "subject"), BillingMode.PayPerRequest)( - AttributeDefinition.attrDefnString("email"), - AttributeDefinition.attrDefnString("subject") - ).execute - _ <- batchWriteFromStream(ZStream(avi, adam)) { student => - put("student", student) - }.runDrain - _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) - .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) - .runDrain - _ <- scanAll[Student]("student") - .parallel(10) - .filter { - enrollmentDate === Some(enrolDate) && payment === Payment.PayPal && payment === altPayment && $( - "payment" - ) === "PayPal" - } - .execute - .map(_.runCollect) - _ <- queryAll[Student]("student") - .filter( - enrollmentDate === Some(enrolDate) && payment === Payment.PayPal //&& elephantCe - ) - .whereKey(email === "avi@gmail.com" && subject === "maths" /* && elephantCe */ ) - .execute - .map(_.runCollect) - _ <- put[Student]("student", avi) - .where( - enrollmentDate === Some( - enrolDate - ) && email === "avi@gmail.com" && payment === Payment.CreditCard && $( - "payment" - ) === "CreditCard" /* && elephantCe */ - ) - .execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - altPayment.set(Payment.PayPal) + addresses.prependList(List(Address("line0", "postcode0"))) + studentNumber - .add(1000) + groups.addSet(Set("group3")) // + elephantAction - }.execute + _ <- createTable("student", KeySchema("email", "subject"), BillingMode.PayPerRequest)( + AttributeDefinition.attrDefnString("email"), + AttributeDefinition.attrDefnString("subject") + ).execute + _ <- batchWriteFromStream(ZStream(avi, adam)) { student => + put("student", student) + }.runDrain + /* + - must be a scalar + - mandatory Partition Key and Optional sort key + - String, Number or Binary + - defined once, used many times + problem with `Student.email.set("x"), Student.subject.set("y")` is that we can not create a function for this to reuse + Student.email.partitionKey("x") && Student.subject.sortKey("y") + */ + found <- get("table", Student.email.set("x"), Student.subject.set("y")).execute + _ = println(s"XXXXXXXX found=$found") + + _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute + _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) + .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) + .runDrain + _ <- scanAll[Student]("student") + .parallel(10) + .filter { + enrollmentDate === Some(enrolDate) && payment === Payment.PayPal && payment === altPayment && $( + "payment" + ) === "PayPal" + } + .execute + .map(_.runCollect) + _ <- queryAll[Student]("student") + .filter( + enrollmentDate === Some(enrolDate) && payment === Payment.PayPal //&& elephantCe + ) + .whereKey(email === "avi@gmail.com" && subject === "maths" /* && elephantCe */ ) + .execute + .map(_.runCollect) + _ <- put[Student]("student", avi) + .where( + enrollmentDate === Some( + enrolDate + ) && email === "avi@gmail.com" && payment === Payment.CreditCard && $( + "payment" + ) === "CreditCard" /* && elephantCe */ + ) + .execute + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + altPayment.set(Payment.PayPal) + addresses.prependList(List(Address("line0", "postcode0"))) + studentNumber + .add(1000) + groups.addSet(Set("group3")) // + elephantAction + }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - altPayment.set(Payment.PayPal) + addresses.appendList(List(Address("line3", "postcode3"))) + groups - .deleteFromSet(Set("group1")) - }.execute + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + altPayment.set(Payment.PayPal) + addresses.appendList(List(Address("line3", "postcode3"))) + groups + .deleteFromSet(Set("group1")) + }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - enrollmentDate.setIfNotExists(Some(enrolDate2)) + payment.set(altPayment) + address - .set( - Some(Address("line1", "postcode1")) - ) // + elephantAction - }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - addresses.remove(1) - }.execute - _ <- + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + enrollmentDate.setIfNotExists(Some(enrolDate2)) + payment.set(altPayment) + address + .set( + Some(Address("line1", "postcode1")) + ) // + elephantAction + }.execute + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + addresses.remove(1) + }.execute + _ <- delete("student", primaryKey("adam@gmail.com", "english")) .where( (enrollmentDate === Some(enrolDate) && payment <> Payment.PayPal && studentNumber @@ -86,11 +97,11 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { ) && collegeName.size > 1 && groups.size > 1 /* && zio.dynamodb.examples.Elephant.email === "elephant@gmail.com" */ ) ) .execute - _ <- scanAll[Student]("student") - .filter[Student](payment.in(Payment.PayPal) && payment.inSet(Set(Payment.PayPal))) - .execute - .tap(_.tap(student => Console.printLine(s"scanAll - student=$student")).runDrain) - _ <- deleteTable("student").execute + _ <- scanAll[Student]("student") + .filter[Student](payment.in(Payment.PayPal) && payment.inSet(Set(Payment.PayPal))) + .execute + .tap(_.tap(student => Console.printLine(s"scanAll - student=$student")).runDrain) + _ <- deleteTable("student").execute } yield () override def run = program.provide(layer).exitCode From d091bf3b73ea00bc54b6c90308beb7bd8689a12e Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 11 Jun 2023 08:08:44 +0100 Subject: [PATCH 02/69] explore richer PK domain --- dynamodb/src/main/scala/zio/Foo.scala | 85 +++++ .../dynamodb/KeyConditionExpression2.scala | 313 ++++++++++++++++++ .../zio/dynamodb/ProjectionExpression.scala | 12 + .../zio/dynamodb/proofs/IsPrimaryKey.scala | 5 +- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 110 +++--- 5 files changed, 470 insertions(+), 55 deletions(-) create mode 100644 dynamodb/src/main/scala/zio/Foo.scala create mode 100644 dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala new file mode 100644 index 000000000..d9b140d3a --- /dev/null +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -0,0 +1,85 @@ +package zio.dynamodb + +import zio.schema.Schema +import zio.schema.DeriveSchema +//import zio.dynamodb.PrimaryKey + +/** + * This API should only exists in type safe land ie only access is via a PE method (TODO confirm we can do this restriction) + * Looks like we can + * TODO: check how AWS uses filter vs where + * TODO: propogate From/To type parameters + */ +object Foo { + sealed trait PartitionKeyEprn { self => + def &&(other: SortKeyEprn): PartitionKeyEprn = PartitionKeyEprn.And(self, other) + def &&(other: ComplexSortKeyEprn): KeyConditionExpression = PartitionKeyEprn.ComplexAnd(self, other) + } + sealed trait KeyConditionExpression // extends PartitionKeyEprn + + object PartitionKeyEprn { + final case class PartitionKey(keyName: String) { + def ===(value: String): PartitionKeyEprn = Equals(this, value) + } + + final case class And(pk: PartitionKeyEprn, sk: SortKeyEprn) extends PartitionKeyEprn + final case class ComplexAnd(pk: PartitionKeyEprn, sk: ComplexSortKeyEprn) extends KeyConditionExpression + + final case class Equals(pk: PartitionKey, value: String) extends PartitionKeyEprn + } + + sealed trait SortKeyEprn + sealed trait ComplexSortKeyEprn + object SortKeyExprn { + final case class SortKey(keyName: String) { + def ===(value: String): SortKeyEprn = Equals(this, value) + def >(value: String): ComplexSortKeyEprn = GreaterThan(this, value) + // ... and so on for all the other operators + } + final case class Equals(sortKey: SortKey, value: String) extends SortKeyEprn + final case class GreaterThan(sortKey: SortKey, value: String) extends ComplexSortKeyEprn + } + +} + +object FooExample extends App { + import Foo._ + import Foo.PartitionKeyEprn._ + import Foo.SortKeyExprn._ + +// def get(k: PartitionKeyEprn) = { +// val pk = k match { +// case PartitionKeyEprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) +// case PartitionKeyEprn.And(pk, sk) => sk match { +// case SortKeyExprn.Equals(sk, value) => PrimaryKey(pk.keyName -> value) +// } +// } +// } + + def where(k: PartitionKeyEprn) = + k match { + case PartitionKeyEprn.Equals(pk, value) => println(s"pk=$pk, value=$value") + case PartitionKeyEprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") + } + def where(k: KeyConditionExpression) = + k match { + case PartitionKeyEprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") + } + + val x: PartitionKeyEprn = PartitionKey("email") === "x" + val y: PartitionKeyEprn = PartitionKey("email") === "x" && SortKey("subject") === "y" + val z: KeyConditionExpression = PartitionKey("email") === "x" && SortKey("subject") > "y" + + final case class Student(email: String, subject: String, age: Int) + object Student { + implicit val schema: Schema.CaseClass3[String, String, Int, Student] = DeriveSchema.gen[Student] + val (email, subject, age) = ProjectionExpression.accessors[Student] + } + + val pk: PartitionKeyEprn = Student.email.primaryKey === "x" + val pkAndSk: PartitionKeyEprn = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed + val pkAndSkExtended: KeyConditionExpression = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + println(pkAndSk) + println(pkAndSkExtended) +} diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala new file mode 100644 index 000000000..0bca9c84d --- /dev/null +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala @@ -0,0 +1,313 @@ +package zio.dynamodb + +import zio.dynamodb.PartitionKeyExpression2.PartitionKey +import zio.dynamodb.SortKeyExpression2.SortKey + +/* +KeyCondition expression is a restricted version of ConditionExpression where by +- partition exprn is required and can only use "=" equals comparison +- optionally AND can be used to add a sort key expression + +eg partitionKeyName = :partitionkeyval AND sortKeyName = :sortkeyval +comparisons operators are the same as for Condition + + */ + +sealed trait KeyConditionExpression2 extends Renderable { self => + def render: AliasMapRender[String] = + self match { + case KeyConditionExpression2.And(left, right) => + left.render + .zipWith( + right.render + ) { case (l, r) => s"$l AND $r" } // TODO: Avi - use alias map if DDB requires it + case expression: PartitionKeyExpression2 => expression.render + } + +} + +object KeyConditionExpression2 { + private[dynamodb] final case class And(left: PartitionKeyExpression2, right: SortKeyExpression2) + extends KeyConditionExpression2 + def partitionKey(key: String): PartitionKey = PartitionKey(key) + + /** + * Create a KeyConditionExpression from a ConditionExpression + * Must be in the form of ` && ` where format of `` is: + * {{{ === }}} + * and the format of `` is: + * {{{ }}} where op can be one of `===`, `>`, `>=`, `<`, `<=`, `between`, `beginsWith` + * + * Example using type API: + * {{{ + * val (email, subject, enrollmentDate, payment) = ProjectionExpression.accessors[Student] + * // ... + * val keyConditionExprn = filterKey(email === "avi@gmail.com" && subject === "maths") + * }}} + */ + private[dynamodb] def fromConditionExpressionUnsafe(c: ConditionExpression[_]): KeyConditionExpression = + KeyConditionExpression(c).getOrElse( + throw new IllegalStateException(s"Error: invalid key condition expression $c") + ) + + // // TODO: Avi - revists + // private[dynamodb] def apply(c: ConditionExpression[_]): Either[String, KeyConditionExpression2] = + // c match { + // case ConditionExpression.Equals( + // ProjectionExpressionOperand(MapElement(Root, partitionKey)), + // ConditionExpression.Operand.ValueOperand(av) + // ) => + // Right(PartitionKeyExpression2.Equals(PartitionKey(partitionKey), av)) + // case ConditionExpression.And( + // ConditionExpression.Equals( + // ProjectionExpressionOperand(MapElement(Root, partitionKey)), + // ConditionExpression.Operand.ValueOperand(avL) + // ), + // rhs + // ) => + // rhs match { + // case ConditionExpression.Equals( + // ProjectionExpressionOperand(MapElement(Root, sortKey)), + // ConditionExpression.Operand.ValueOperand(avR) + // ) => + // Right( + // PartitionKeyExpression2 + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SimSortKeyExpression2.Equals(SortKey(sortKey), avR)) + // ) + // case ConditionExpression.NotEqual( + // ProjectionExpressionOperand(MapElement(Root, sortKey)), + // ConditionExpression.Operand.ValueOperand(avR) + // ) => + // Right( + // PartitionKeyExpression + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SortKeyExpression.NotEqual(SortKey(sortKey), avR)) + // ) + // case ConditionExpression.GreaterThan( + // ProjectionExpressionOperand(MapElement(Root, sortKey)), + // ConditionExpression.Operand.ValueOperand(avR) + // ) => + // Right( + // PartitionKeyExpression + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SortKeyExpression.GreaterThan(SortKey(sortKey), avR)) + // ) + // case ConditionExpression.LessThan( + // ProjectionExpressionOperand(MapElement(Root, sortKey)), + // ConditionExpression.Operand.ValueOperand(avR) + // ) => + // Right( + // PartitionKeyExpression + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SortKeyExpression.LessThan(SortKey(sortKey), avR)) + // ) + // case ConditionExpression.GreaterThanOrEqual( + // ProjectionExpressionOperand(MapElement(Root, sortKey)), + // ConditionExpression.Operand.ValueOperand(avR) + // ) => + // Right( + // PartitionKeyExpression + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SortKeyExpression.GreaterThanOrEqual(SortKey(sortKey), avR)) + // ) + // case ConditionExpression.LessThanOrEqual( + // ProjectionExpressionOperand(MapElement(Root, sortKey)), + // ConditionExpression.Operand.ValueOperand(avR) + // ) => + // Right( + // PartitionKeyExpression + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SortKeyExpression.LessThanOrEqual(SortKey(sortKey), avR)) + // ) + // case ConditionExpression.Between( + // ProjectionExpressionOperand(MapElement(Root, sortKey)), + // avMin, + // avMax + // ) => + // Right( + // PartitionKeyExpression + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SortKeyExpression.Between(SortKey(sortKey), avMin, avMax)) + // ) + // case ConditionExpression.BeginsWith( + // MapElement(Root, sortKey), + // av + // ) => + // Right( + // PartitionKeyExpression + // .Equals(PartitionKey(partitionKey), avL) + // .&&(SortKeyExpression.BeginsWith(SortKey(sortKey), av)) + // ) + // case c => Left(s"condition '$c' is not a valid sort condition expression") + // } + + // case c => Left(s"condition $c is not a valid key condition expression") + // } + +} + +sealed trait PartitionKeyExpression2 extends KeyConditionExpression2 { self => + import KeyConditionExpression2.And + + def &&(that: SortKeyExpression2): KeyConditionExpression2 = And(self, that) + + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExpression2.Equals(left, right) => + AliasMapRender.getOrInsert(right).map(v => s"${left.keyName} = $v") + } +} +object PartitionKeyExpression2 { + final case class PartitionKey(keyName: String) { self => + def ===[A](that: A)(implicit t: ToAttributeValue[A]): PartitionKeyExpression2 = + Equals(self, t.toAttributeValue(that)) + } + final case class Equals(left: PartitionKey, right: AttributeValue) extends PartitionKeyExpression2 +} + +sealed trait SimpleSortKeyExpression2 { self => + def render: AliasMapRender[String] = + self match { + case SimpleSortKeyExpression2.Equals(left, right) => + AliasMapRender + .getOrInsert(right) + .map { v => + s"${left.keyName} = $v" + } + case SortKeyExpression2.LessThan(left, right) => + AliasMapRender + .getOrInsert(right) + .map { v => + s"${left.keyName} < $v" + } + case SortKeyExpression2.NotEqual(left, right) => + AliasMapRender + .getOrInsert(right) + .map { v => + s"${left.keyName} <> $v" + } + case SortKeyExpression2.GreaterThan(left, right) => + AliasMapRender + .getOrInsert(right) + .map { v => + s"${left.keyName} > $v" + } + case SortKeyExpression2.LessThanOrEqual(left, right) => + AliasMapRender + .getOrInsert(right) + .map { v => + s"${left.keyName} <= $v" + } + case SortKeyExpression2.GreaterThanOrEqual(left, right) => + AliasMapRender + .getOrInsert(right) + .map { v => + s"${left.keyName} >= $v" + } + case SortKeyExpression2.Between(left, min, max) => + AliasMapRender + .getOrInsert(min) + .flatMap(min => + AliasMapRender.getOrInsert(max).map { max => + s"${left.keyName} BETWEEN $min AND $max" + } + ) + case SortKeyExpression2.BeginsWith(left, value) => + AliasMapRender + .getOrInsert(value) + .map { v => + s"begins_with(${left.keyName}, $v)" + } + } +} +object SimpleSortKeyExpression2 { + private[dynamodb] final case class Equals(left: SortKey, right: AttributeValue) extends SimpleSortKeyExpression2 +} + +sealed trait SortKeyExpression2 extends SimpleSortKeyExpression2 { self => + // def render: AliasMapRender[String] = + // self match { + // case SimpleSortKeyExpression2.Equals(left, right) => + // AliasMapRender + // .getOrInsert(right) + // .map { v => + // s"${left.keyName} = $v" + // } + // case SortKeyExpression2.LessThan(left, right) => + // AliasMapRender + // .getOrInsert(right) + // .map { v => + // s"${left.keyName} < $v" + // } + // case SortKeyExpression2.NotEqual(left, right) => + // AliasMapRender + // .getOrInsert(right) + // .map { v => + // s"${left.keyName} <> $v" + // } + // case SortKeyExpression2.GreaterThan(left, right) => + // AliasMapRender + // .getOrInsert(right) + // .map { v => + // s"${left.keyName} > $v" + // } + // case SortKeyExpression2.LessThanOrEqual(left, right) => + // AliasMapRender + // .getOrInsert(right) + // .map { v => + // s"${left.keyName} <= $v" + // } + // case SortKeyExpression2.GreaterThanOrEqual(left, right) => + // AliasMapRender + // .getOrInsert(right) + // .map { v => + // s"${left.keyName} >= $v" + // } + // case SortKeyExpression2.Between(left, min, max) => + // AliasMapRender + // .getOrInsert(min) + // .flatMap(min => + // AliasMapRender.getOrInsert(max).map { max => + // s"${left.keyName} BETWEEN $min AND $max" + // } + // ) + // case SortKeyExpression2.BeginsWith(left, value) => + // AliasMapRender + // .getOrInsert(value) + // .map { v => + // s"begins_with(${left.keyName}, $v)" + // } + // } + +} + +object SortKeyExpression2 { + + final case class SortKey(keyName: String) { self => + def ===[A](that: A)(implicit t: ToAttributeValue[A]): SimpleSortKeyExpression2 = + SimpleSortKeyExpression2.Equals(self, t.toAttributeValue(that)) + def <>[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = NotEqual(self, t.toAttributeValue(that)) + def <[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = LessThan(self, t.toAttributeValue(that)) + def <=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = + LessThanOrEqual(self, t.toAttributeValue(that)) + def >[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = + GreaterThanOrEqual(self, t.toAttributeValue(that)) + def >=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = + GreaterThanOrEqual(self, t.toAttributeValue(that)) + def between[A](min: A, max: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = + Between(self, t.toAttributeValue(min), t.toAttributeValue(max)) + def beginsWith[A](value: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = + BeginsWith(self, t.toAttributeValue(value)) + } + +// private[dynamodb] final case class Equals(left: SortKey, right: AttributeValue) extends SimpleSortKeyExpression2 + private[dynamodb] final case class NotEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression2 + private[dynamodb] final case class LessThan(left: SortKey, right: AttributeValue) extends SortKeyExpression2 + private[dynamodb] final case class GreaterThan(left: SortKey, right: AttributeValue) extends SortKeyExpression2 + private[dynamodb] final case class LessThanOrEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression2 + private[dynamodb] final case class GreaterThanOrEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression2 + private[dynamodb] final case class Between(left: SortKey, min: AttributeValue, max: AttributeValue) + extends SortKeyExpression2 + private[dynamodb] final case class BeginsWith(left: SortKey, value: AttributeValue) extends SortKeyExpression2 +} diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 7e70ac122..9e3d31845 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -133,6 +133,18 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { + import zio.dynamodb.Foo + def primaryKey: Foo.PartitionKeyEprn.PartitionKey = + self match { + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyEprn.PartitionKey(key) + case _ => throw new IllegalArgumentException("Not a partition key") + } + def sortKey: Foo.SortKeyExprn.SortKey = + self match { + case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey(key) + case _ => throw new IllegalArgumentException("Not a partition key") + } + def set(a: To): UpdateExpression.Action.SetAction[From, To] = UpdateExpression.Action.SetAction( self, diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index b630f9bc4..4389248b0 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -3,7 +3,10 @@ package zio.dynamodb.proofs sealed trait IsPrimaryKey[A] object IsPrimaryKey { - implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} + implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} + + // HOWEVER - String type in the DB is overloaded in Scala land eg Date in Scala is String in DB + // so do we also allow Scala types that are strings in the DB? implicit val stringIsPrimaryKey = new IsPrimaryKey[String] {} // todo binary data } diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index 1ec49e74e..bbe4b7030 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -35,60 +35,62 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { - String, Number or Binary - defined once, used many times problem with `Student.email.set("x"), Student.subject.set("y")` is that we can not create a function for this to reuse - Student.email.partitionKey("x") && Student.subject.sortKey("y") + Student.email.partitionKey === "x" // qualifies as a both a PK and a WhereKeyExpression + Student.email.partitionKey === "x" && Student.subject.sortKey === "y" // qualifies as a both a PK and a WhereKeyExpression + Student.email.partitionKey === "x" && Student.subject.sortKey > 1 // qualifies as only a WhereKeyExpression */ found <- get("table", Student.email.set("x"), Student.subject.set("y")).execute _ = println(s"XXXXXXXX found=$found") - - _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) - .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) - .runDrain - _ <- scanAll[Student]("student") - .parallel(10) - .filter { - enrollmentDate === Some(enrolDate) && payment === Payment.PayPal && payment === altPayment && $( - "payment" - ) === "PayPal" - } - .execute - .map(_.runCollect) - _ <- queryAll[Student]("student") - .filter( - enrollmentDate === Some(enrolDate) && payment === Payment.PayPal //&& elephantCe - ) - .whereKey(email === "avi@gmail.com" && subject === "maths" /* && elephantCe */ ) - .execute - .map(_.runCollect) - _ <- put[Student]("student", avi) - .where( - enrollmentDate === Some( - enrolDate - ) && email === "avi@gmail.com" && payment === Payment.CreditCard && $( - "payment" - ) === "CreditCard" /* && elephantCe */ - ) - .execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - altPayment.set(Payment.PayPal) + addresses.prependList(List(Address("line0", "postcode0"))) + studentNumber - .add(1000) + groups.addSet(Set("group3")) // + elephantAction - }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - altPayment.set(Payment.PayPal) + addresses.appendList(List(Address("line3", "postcode3"))) + groups - .deleteFromSet(Set("group1")) - }.execute + _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute + _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) + .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) + .runDrain + _ <- scanAll[Student]("student") + .parallel(10) + .filter { + enrollmentDate === Some(enrolDate) && payment === Payment.PayPal && payment === altPayment && $( + "payment" + ) === "PayPal" + } + .execute + .map(_.runCollect) + _ <- queryAll[Student]("student") + .filter( + enrollmentDate === Some(enrolDate) && payment === Payment.PayPal //&& elephantCe + ) + .whereKey(email === "avi@gmail.com" && subject === "maths" /* && elephantCe */ ) + .execute + .map(_.runCollect) + _ <- put[Student]("student", avi) + .where( + enrollmentDate === Some( + enrolDate + ) && email === "avi@gmail.com" && payment === Payment.CreditCard && $( + "payment" + ) === "CreditCard" /* && elephantCe */ + ) + .execute + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + altPayment.set(Payment.PayPal) + addresses.prependList(List(Address("line0", "postcode0"))) + studentNumber + .add(1000) + groups.addSet(Set("group3")) // + elephantAction + }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - enrollmentDate.setIfNotExists(Some(enrolDate2)) + payment.set(altPayment) + address - .set( - Some(Address("line1", "postcode1")) - ) // + elephantAction - }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { - addresses.remove(1) - }.execute - _ <- + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + altPayment.set(Payment.PayPal) + addresses.appendList(List(Address("line3", "postcode3"))) + groups + .deleteFromSet(Set("group1")) + }.execute + + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + enrollmentDate.setIfNotExists(Some(enrolDate2)) + payment.set(altPayment) + address + .set( + Some(Address("line1", "postcode1")) + ) // + elephantAction + }.execute + _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + addresses.remove(1) + }.execute + _ <- delete("student", primaryKey("adam@gmail.com", "english")) .where( (enrollmentDate === Some(enrolDate) && payment <> Payment.PayPal && studentNumber @@ -97,11 +99,11 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { ) && collegeName.size > 1 && groups.size > 1 /* && zio.dynamodb.examples.Elephant.email === "elephant@gmail.com" */ ) ) .execute - _ <- scanAll[Student]("student") - .filter[Student](payment.in(Payment.PayPal) && payment.inSet(Set(Payment.PayPal))) - .execute - .tap(_.tap(student => Console.printLine(s"scanAll - student=$student")).runDrain) - _ <- deleteTable("student").execute + _ <- scanAll[Student]("student") + .filter[Student](payment.in(Payment.PayPal) && payment.inSet(Set(Payment.PayPal))) + .execute + .tap(_.tap(student => Console.printLine(s"scanAll - student=$student")).runDrain) + _ <- deleteTable("student").execute } yield () override def run = program.provide(layer).exitCode From 4cbc4125705b1482b4cbb87234cb3b5cfc73d3f8 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 11 Jun 2023 08:28:02 +0100 Subject: [PATCH 03/69] explore richer PK domain --- dynamodb/src/main/scala/zio/Foo.scala | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index d9b140d3a..1669a2325 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -2,7 +2,7 @@ package zio.dynamodb import zio.schema.Schema import zio.schema.DeriveSchema -//import zio.dynamodb.PrimaryKey +import zio.dynamodb.PrimaryKey /** * This API should only exists in type safe land ie only access is via a PE method (TODO confirm we can do this restriction) @@ -47,14 +47,21 @@ object FooExample extends App { import Foo.PartitionKeyEprn._ import Foo.SortKeyExprn._ -// def get(k: PartitionKeyEprn) = { -// val pk = k match { -// case PartitionKeyEprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) -// case PartitionKeyEprn.And(pk, sk) => sk match { -// case SortKeyExprn.Equals(sk, value) => PrimaryKey(pk.keyName -> value) -// } -// } -// } + // DynamoDbQuery's still use PrimaryKey + // typesafe API constructors only expose PartitionKeyEprn + def asPk(k: PartitionKeyEprn): PrimaryKey = { + val pk = k match { + case PartitionKeyEprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) + case PartitionKeyEprn.And(pk, sk) => + (pk, sk) match { + case (PartitionKeyEprn.Equals(pk, value), SortKeyExprn.Equals(sk, value2)) => + PrimaryKey(pk.keyName -> value, sk.keyName -> value2) + case _ => + throw new IllegalArgumentException("This should not happed?????") + } + } + pk + } def where(k: PartitionKeyEprn) = k match { @@ -80,6 +87,6 @@ object FooExample extends App { val pkAndSk: PartitionKeyEprn = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed val pkAndSkExtended: KeyConditionExpression = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - println(pkAndSk) + println(asPk(pkAndSk)) println(pkAndSkExtended) } From f15f5b3bc4a429088cca6c01c96a7742080f99ae Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 11 Jun 2023 09:19:42 +0100 Subject: [PATCH 04/69] explore richer PK domain --- dynamodb/src/main/scala/zio/Foo.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 1669a2325..af8fdf3a4 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -3,6 +3,7 @@ package zio.dynamodb import zio.schema.Schema import zio.schema.DeriveSchema import zio.dynamodb.PrimaryKey +import zio.dynamodb.proofs.IsPrimaryKey /** * This API should only exists in type safe land ie only access is via a PE method (TODO confirm we can do this restriction) @@ -19,13 +20,16 @@ object Foo { object PartitionKeyEprn { final case class PartitionKey(keyName: String) { - def ===(value: String): PartitionKeyEprn = Equals(this, value) + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyEprn = { + val _ = ev + Equals(this, to.toAttributeValue(value)) + } } final case class And(pk: PartitionKeyEprn, sk: SortKeyEprn) extends PartitionKeyEprn final case class ComplexAnd(pk: PartitionKeyEprn, sk: ComplexSortKeyEprn) extends KeyConditionExpression - final case class Equals(pk: PartitionKey, value: String) extends PartitionKeyEprn + final case class Equals(pk: PartitionKey, value: AttributeValue) extends PartitionKeyEprn } sealed trait SortKeyEprn @@ -73,7 +77,7 @@ object FooExample extends App { case PartitionKeyEprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") } - val x: PartitionKeyEprn = PartitionKey("email") === "x" + val x: PartitionKeyEprn = PartitionKey("email") === 1 val y: PartitionKeyEprn = PartitionKey("email") === "x" && SortKey("subject") === "y" val z: KeyConditionExpression = PartitionKey("email") === "x" && SortKey("subject") > "y" From d32047cfa83e60118b42d413d7d1450cacffd742 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 11 Jun 2023 09:29:36 +0100 Subject: [PATCH 05/69] explore richer PK domain --- dynamodb/src/main/scala/zio/Foo.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index af8fdf3a4..f7a7f2646 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -36,12 +36,18 @@ object Foo { sealed trait ComplexSortKeyEprn object SortKeyExprn { final case class SortKey(keyName: String) { - def ===(value: String): SortKeyEprn = Equals(this, value) - def >(value: String): ComplexSortKeyEprn = GreaterThan(this, value) + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn = { + val _ = ev + Equals(this, to.toAttributeValue(value)) + } + def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ComplexSortKeyEprn = { + val _ = ev + GreaterThan(this, to.toAttributeValue(value)) + } // ... and so on for all the other operators } - final case class Equals(sortKey: SortKey, value: String) extends SortKeyEprn - final case class GreaterThan(sortKey: SortKey, value: String) extends ComplexSortKeyEprn + final case class Equals(sortKey: SortKey, value: AttributeValue) extends SortKeyEprn + final case class GreaterThan(sortKey: SortKey, value: AttributeValue) extends ComplexSortKeyEprn } } From cd75e91d32b700950b05c9aef8c938dc844551fe Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 11 Jun 2023 12:28:32 +0100 Subject: [PATCH 06/69] explore richer PK domain --- dynamodb/src/main/scala/zio/Foo.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index f7a7f2646..0a020f0d8 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -8,8 +8,8 @@ import zio.dynamodb.proofs.IsPrimaryKey /** * This API should only exists in type safe land ie only access is via a PE method (TODO confirm we can do this restriction) * Looks like we can - * TODO: check how AWS uses filter vs where - * TODO: propogate From/To type parameters + * TODO: + * copy render methods from KeyConditionExpression */ object Foo { sealed trait PartitionKeyEprn { self => @@ -73,12 +73,12 @@ object FooExample extends App { pk } - def where(k: PartitionKeyEprn) = + def whereKey(k: PartitionKeyEprn) = k match { case PartitionKeyEprn.Equals(pk, value) => println(s"pk=$pk, value=$value") case PartitionKeyEprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") } - def where(k: KeyConditionExpression) = + def whereKey(k: KeyConditionExpression) = k match { case PartitionKeyEprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") } From 0b62ba12c4b99a8f17d622655576996a482a97cc Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 24 Jun 2023 09:21:21 +0100 Subject: [PATCH 07/69] initial render experiment --- dynamodb/src/main/scala/zio/Foo.scala | 111 +++++++++++++----- .../zio/dynamodb/ProjectionExpression.scala | 10 +- 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 0a020f0d8..419440be9 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -4,88 +4,127 @@ import zio.schema.Schema import zio.schema.DeriveSchema import zio.dynamodb.PrimaryKey import zio.dynamodb.proofs.IsPrimaryKey +import zio.dynamodb.Foo.PartitionKeyExprn.ComplexAnd /** * This API should only exists in type safe land ie only access is via a PE method (TODO confirm we can do this restriction) * Looks like we can * TODO: - * copy render methods from KeyConditionExpression + * rename PartitionKeyEprn to PrimaryKeyExprn + * copy render methods from + * make PartitionKeyEprn/KeyConditionExpression sum types so that they can be stored in the same type on the query class + * DONE */ object Foo { - sealed trait PartitionKeyEprn { self => - def &&(other: SortKeyEprn): PartitionKeyEprn = PartitionKeyEprn.And(self, other) - def &&(other: ComplexSortKeyEprn): KeyConditionExpression = PartitionKeyEprn.ComplexAnd(self, other) + sealed trait KeyConditionExpression extends Renderable { self => + def render: AliasMapRender[String] = + self match { + case and @ Foo.PartitionKeyExprn.And(_, _) => and.render + case and @ ComplexAnd(_, _) => and.render + case equals @ Foo.PartitionKeyExprn.Equals(_, _) => equals.render + } + } + object KeyConditionExpression { + } + // models primary key expressions + // email.primaryKey === "x" + // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + sealed trait PartitionKeyExprn extends KeyConditionExpression { self => + def &&(other: SortKeyEprn): PartitionKeyExprn = PartitionKeyExprn.And(self, other) + def &&(other: ExtendedSortKeyEprn): ExtendedPartitionKeyExprn = PartitionKeyExprn.ComplexAnd(self, other) + + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExprn.And(pk, sk) => + for { + pkStr <- pk.render + skStr <- sk.render + } yield s"$pkStr AND $skStr" + case PartitionKeyExprn.Equals(pk, value) => + for { + pkStr <- AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } yield s"$pkStr = $value" + } } - sealed trait KeyConditionExpression // extends PartitionKeyEprn - object PartitionKeyEprn { + // models "extended" primary key expressions + // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + sealed trait ExtendedPartitionKeyExprn extends KeyConditionExpression { self => + override def render: AliasMapRender[String] = ??? + } + + // no overlap between PartitionKeyExprn and ExtendedPartitionKeyExprn + + object PartitionKeyExprn { final case class PartitionKey(keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyEprn = { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExprn = { val _ = ev Equals(this, to.toAttributeValue(value)) } } - final case class And(pk: PartitionKeyEprn, sk: SortKeyEprn) extends PartitionKeyEprn - final case class ComplexAnd(pk: PartitionKeyEprn, sk: ComplexSortKeyEprn) extends KeyConditionExpression + final case class And(pk: PartitionKeyExprn, sk: SortKeyEprn) extends PartitionKeyExprn + final case class ComplexAnd(pk: PartitionKeyExprn, sk: ExtendedSortKeyEprn) extends ExtendedPartitionKeyExprn - final case class Equals(pk: PartitionKey, value: AttributeValue) extends PartitionKeyEprn + final case class Equals(pk: PartitionKey, value: AttributeValue) extends PartitionKeyExprn } - sealed trait SortKeyEprn - sealed trait ComplexSortKeyEprn - object SortKeyExprn { + sealed trait SortKeyEprn { + def render: AliasMapRender[String] = ??? + } + sealed trait ExtendedSortKeyEprn + object SortKeyExprn { final case class SortKey(keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn = { val _ = ev Equals(this, to.toAttributeValue(value)) } - def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ComplexSortKeyEprn = { + def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyEprn = { val _ = ev GreaterThan(this, to.toAttributeValue(value)) } // ... and so on for all the other operators } final case class Equals(sortKey: SortKey, value: AttributeValue) extends SortKeyEprn - final case class GreaterThan(sortKey: SortKey, value: AttributeValue) extends ComplexSortKeyEprn + final case class GreaterThan(sortKey: SortKey, value: AttributeValue) extends ExtendedSortKeyEprn } } object FooExample extends App { import Foo._ - import Foo.PartitionKeyEprn._ + import Foo.PartitionKeyExprn._ import Foo.SortKeyExprn._ // DynamoDbQuery's still use PrimaryKey // typesafe API constructors only expose PartitionKeyEprn - def asPk(k: PartitionKeyEprn): PrimaryKey = { + def asPk(k: PartitionKeyExprn): PrimaryKey = { val pk = k match { - case PartitionKeyEprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) - case PartitionKeyEprn.And(pk, sk) => + case PartitionKeyExprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) + case PartitionKeyExprn.And(pk, sk) => (pk, sk) match { - case (PartitionKeyEprn.Equals(pk, value), SortKeyExprn.Equals(sk, value2)) => + case (PartitionKeyExprn.Equals(pk, value), SortKeyExprn.Equals(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) - case _ => + case _ => throw new IllegalArgumentException("This should not happed?????") } } pk } - def whereKey(k: PartitionKeyEprn) = + def whereKey(k: PartitionKeyExprn) = k match { - case PartitionKeyEprn.Equals(pk, value) => println(s"pk=$pk, value=$value") - case PartitionKeyEprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") + case PartitionKeyExprn.Equals(pk, value) => println(s"pk=$pk, value=$value") + case PartitionKeyExprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") } - def whereKey(k: KeyConditionExpression) = + def whereKey(k: ExtendedPartitionKeyExprn) = k match { - case PartitionKeyEprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") + case PartitionKeyExprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") } - val x: PartitionKeyEprn = PartitionKey("email") === 1 - val y: PartitionKeyEprn = PartitionKey("email") === "x" && SortKey("subject") === "y" - val z: KeyConditionExpression = PartitionKey("email") === "x" && SortKey("subject") > "y" + val x: KeyConditionExpression = PartitionKey("email") === 1 + val y: PartitionKeyExprn = PartitionKey("email") === "x" && SortKey("subject") === "y" + val z: ExtendedPartitionKeyExprn = PartitionKey("email") === "x" && SortKey("subject") > "y" final case class Student(email: String, subject: String, age: Int) object Student { @@ -93,10 +132,18 @@ object FooExample extends App { val (email, subject, age) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyEprn = Student.email.primaryKey === "x" - val pkAndSk: PartitionKeyEprn = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pk: PartitionKeyExprn = Student.email.primaryKey === "x" + val pkAndSk: PartitionKeyExprn = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended: KeyConditionExpression = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + val pkAndSkExtended: ExtendedPartitionKeyExprn = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + // GetItem Query will have two overridden versions + // 1) takes AttrMap/PriamaryKey - for users of low level API + // 2) takes PartitionKeyExprn - internally this can be converted to AttrMap/PriamaryKey + + // whereKey function (for Query) can have 2 overridden versions + // 1) takes PartitionKeyExprn + // 2) takes ExtendedPartitionKeyExprn + // 3) down side is that users of low level API will have to construct case class instances manually - but they have to do that now anyway println(asPk(pkAndSk)) println(pkAndSkExtended) } diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 9e3d31845..3aab1c240 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -134,15 +134,15 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { import zio.dynamodb.Foo - def primaryKey: Foo.PartitionKeyEprn.PartitionKey = + def primaryKey: Foo.PartitionKeyExprn.PartitionKey = self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyEprn.PartitionKey(key) - case _ => throw new IllegalArgumentException("Not a partition key") + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey(key) + case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: Foo.SortKeyExprn.SortKey = + def sortKey: Foo.SortKeyExprn.SortKey = self match { case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey(key) - case _ => throw new IllegalArgumentException("Not a partition key") + case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } def set(a: To): UpdateExpression.Action.SetAction[From, To] = From bb351d8dfbff99d395012993ac6f5cb02aafba7a Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 1 Jul 2023 07:25:42 +0100 Subject: [PATCH 08/69] added FROM type param --- dynamodb/src/main/scala/zio/Foo.scala | 125 ++++++++++-------- .../zio/dynamodb/ProjectionExpression.scala | 8 +- 2 files changed, 75 insertions(+), 58 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 419440be9..fa3ee12a6 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -4,7 +4,6 @@ import zio.schema.Schema import zio.schema.DeriveSchema import zio.dynamodb.PrimaryKey import zio.dynamodb.proofs.IsPrimaryKey -import zio.dynamodb.Foo.PartitionKeyExprn.ComplexAnd /** * This API should only exists in type safe land ie only access is via a PE method (TODO confirm we can do this restriction) @@ -16,89 +15,87 @@ import zio.dynamodb.Foo.PartitionKeyExprn.ComplexAnd * DONE */ object Foo { - sealed trait KeyConditionExpression extends Renderable { self => - def render: AliasMapRender[String] = - self match { - case and @ Foo.PartitionKeyExprn.And(_, _) => and.render - case and @ ComplexAnd(_, _) => and.render - case equals @ Foo.PartitionKeyExprn.Equals(_, _) => equals.render - } - } - object KeyConditionExpression { + sealed trait KeyConditionExpression[From] extends Renderable { self => + def render: AliasMapRender[String] = ??? // do render in concrete classes } + object KeyConditionExpression {} // models primary key expressions // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - sealed trait PartitionKeyExprn extends KeyConditionExpression { self => - def &&(other: SortKeyEprn): PartitionKeyExprn = PartitionKeyExprn.And(self, other) - def &&(other: ExtendedSortKeyEprn): ExtendedPartitionKeyExprn = PartitionKeyExprn.ComplexAnd(self, other) - - override def render: AliasMapRender[String] = - self match { - case PartitionKeyExprn.And(pk, sk) => - for { - pkStr <- pk.render - skStr <- sk.render - } yield s"$pkStr AND $skStr" - case PartitionKeyExprn.Equals(pk, value) => - for { - pkStr <- AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } yield s"$pkStr = $value" - } + sealed trait PartitionKeyExprn[From] extends KeyConditionExpression[From] { self => + def &&(other: SortKeyEprn[From]): PartitionKeyExprn[From] = PartitionKeyExprn.And[From](self, other) + def &&(other: ExtendedSortKeyEprn[From]): ExtendedPartitionKeyExprn[From] = + PartitionKeyExprn.ComplexAnd[From](self, other) + + // def render2: AliasMapRender[String] = + // self match { + // case PartitionKeyExprn.And(pk, sk) => + // for { + // pkStr <- pk.render + // skStr <- sk.render + // } yield s"$pkStr AND $skStr" + // case PartitionKeyExprn.Equals(pk, value) => + // for { + // pkStr <- AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + // } yield s"$pkStr = $value" + // } } // models "extended" primary key expressions // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedPartitionKeyExprn extends KeyConditionExpression { self => - override def render: AliasMapRender[String] = ??? + sealed trait ExtendedPartitionKeyExprn[From] extends KeyConditionExpression[From] { self => +// override def render: AliasMapRender[String] = ??? } // no overlap between PartitionKeyExprn and ExtendedPartitionKeyExprn object PartitionKeyExprn { - final case class PartitionKey(keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExprn = { + final case class PartitionKey[From](keyName: String) { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExprn[From] = { val _ = ev Equals(this, to.toAttributeValue(value)) } } - final case class And(pk: PartitionKeyExprn, sk: SortKeyEprn) extends PartitionKeyExprn - final case class ComplexAnd(pk: PartitionKeyExprn, sk: ExtendedSortKeyEprn) extends ExtendedPartitionKeyExprn + final case class And[From](pk: PartitionKeyExprn[From], sk: SortKeyEprn[From]) extends PartitionKeyExprn[From] { + self => + // do render in concrete classes - final case class Equals(pk: PartitionKey, value: AttributeValue) extends PartitionKeyExprn + } + final case class ComplexAnd[From](pk: PartitionKeyExprn[From], sk: ExtendedSortKeyEprn[From]) + extends ExtendedPartitionKeyExprn[From] + + final case class Equals[From](pk: PartitionKey[From], value: AttributeValue) extends PartitionKeyExprn[From] } - sealed trait SortKeyEprn { - def render: AliasMapRender[String] = ??? + sealed trait SortKeyEprn[From] { +// def render: AliasMapRender[String] = ??? } - sealed trait ExtendedSortKeyEprn - object SortKeyExprn { - final case class SortKey(keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn = { + sealed trait ExtendedSortKeyEprn[From] + object SortKeyExprn { + final case class SortKey[From](keyName: String) { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { val _ = ev - Equals(this, to.toAttributeValue(value)) + Equals[From](this, to.toAttributeValue(value)) } - def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyEprn = { + def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyEprn[From] = { val _ = ev GreaterThan(this, to.toAttributeValue(value)) } // ... and so on for all the other operators } - final case class Equals(sortKey: SortKey, value: AttributeValue) extends SortKeyEprn - final case class GreaterThan(sortKey: SortKey, value: AttributeValue) extends ExtendedSortKeyEprn + final case class Equals[From](sortKey: SortKey[From], value: AttributeValue) extends SortKeyEprn[From] + final case class GreaterThan[From](sortKey: SortKey[From], value: AttributeValue) extends ExtendedSortKeyEprn[From] } } object FooExample extends App { import Foo._ - import Foo.PartitionKeyExprn._ - import Foo.SortKeyExprn._ // DynamoDbQuery's still use PrimaryKey // typesafe API constructors only expose PartitionKeyEprn - def asPk(k: PartitionKeyExprn): PrimaryKey = { + def asPk[From](k: PartitionKeyExprn[From]): PrimaryKey = { val pk = k match { case PartitionKeyExprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) case PartitionKeyExprn.And(pk, sk) => @@ -112,19 +109,32 @@ object FooExample extends App { pk } - def whereKey(k: PartitionKeyExprn) = + def whereKey[From](k: PartitionKeyExprn[From]) = k match { case PartitionKeyExprn.Equals(pk, value) => println(s"pk=$pk, value=$value") case PartitionKeyExprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") } - def whereKey(k: ExtendedPartitionKeyExprn) = + def whereKey[From](k: ExtendedPartitionKeyExprn[From]) = k match { case PartitionKeyExprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") } - val x: KeyConditionExpression = PartitionKey("email") === 1 - val y: PartitionKeyExprn = PartitionKey("email") === "x" && SortKey("subject") === "y" - val z: ExtendedPartitionKeyExprn = PartitionKey("email") === "x" && SortKey("subject") > "y" + // in low level - non type safe land + // TODO: Avi - fix "Nothing <: From, but trait SortKeyEprn is invariant in type From" + // otherwise users of the low level API will have to type all eprns with Nothing for From + import Foo.PartitionKeyExprn._ + import Foo.SortKeyExprn._ + val x: PartitionKeyExprn[Nothing] = PartitionKey("email") === 1 + /* + "type mismatch;\n found : zio.dynamodb.Foo.SortKeyEprn[Nothing]\n required: zio.dynamodb.Foo.SortKeyEprn[From]\ + nNote: Nothing <: From, but trait SortKeyEprn is invariant in type From.\nYou may wish to define From as +From instead. (SLS 4.5)", + */ + val yyyy: PartitionKeyExprn[Nothing] = PartitionKey("email") === "x" + val xxxx: SortKeyEprn[Nothing] = SortKey("subject") === "y" + val sdfsdf: PartitionKeyExprn[Nothing] = yyyy && xxxx + val y = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" + val z: ExtendedPartitionKeyExprn[Nothing] = + PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" final case class Student(email: String, subject: String, age: Int) object Student { @@ -132,10 +142,14 @@ object FooExample extends App { val (email, subject, age) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyExprn = Student.email.primaryKey === "x" - val pkAndSk: PartitionKeyExprn = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pk: PartitionKeyExprn[Student] = Student.email.primaryKey === "x" + val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" + val sk2: ExtendedSortKeyEprn[Student] = Student.subject.sortKey > "y" + val pkAndSk: PartitionKeyExprn[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended: ExtendedPartitionKeyExprn = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + val pkAndSkExtended: ExtendedPartitionKeyExprn[Student] = + Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + // GetItem Query will have two overridden versions // 1) takes AttrMap/PriamaryKey - for users of low level API // 2) takes PartitionKeyExprn - internally this can be converted to AttrMap/PriamaryKey @@ -146,4 +160,7 @@ object FooExample extends App { // 3) down side is that users of low level API will have to construct case class instances manually - but they have to do that now anyway println(asPk(pkAndSk)) println(pkAndSkExtended) + + // Render requirements + pkAndSk.render.map(s => println(s"rendered: $s")) } diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 3aab1c240..6a06b244c 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -134,14 +134,14 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { import zio.dynamodb.Foo - def primaryKey: Foo.PartitionKeyExprn.PartitionKey = + def primaryKey: Foo.PartitionKeyExprn.PartitionKey[From] = // need implicit evidence that type can be primary key IsPrimaryKey[To] self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey(key) + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: Foo.SortKeyExprn.SortKey = + def sortKey: Foo.SortKeyExprn.SortKey[From] = self match { - case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey(key) + case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } From 7e4b4781ae84a96de5d7cf14c9e0207e0d52624b Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 2 Jul 2023 07:17:07 +0100 Subject: [PATCH 09/69] fixed KeyConditionExpression heirarchy --- dynamodb/src/main/scala/zio/Foo.scala | 169 ++++++++++++++++---------- 1 file changed, 107 insertions(+), 62 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index fa3ee12a6..5c5467e8f 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -16,36 +16,35 @@ import zio.dynamodb.proofs.IsPrimaryKey */ object Foo { sealed trait KeyConditionExpression[From] extends Renderable { self => - def render: AliasMapRender[String] = ??? // do render in concrete classes + def render: AliasMapRender[String] // do render in concrete classes } object KeyConditionExpression {} // models primary key expressions // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" sealed trait PartitionKeyExprn[From] extends KeyConditionExpression[From] { self => - def &&(other: SortKeyEprn[From]): PartitionKeyExprn[From] = PartitionKeyExprn.And[From](self, other) - def &&(other: ExtendedSortKeyEprn[From]): ExtendedPartitionKeyExprn[From] = + def &&(other: SortKeyEprn[From]): CompositePrimaryKeyExprn[From] = PartitionKeyExprn.And[From](self, other) + def &&(other: ExtendedSortKeyEprn[From]): ExtendedCompositePrimaryKeyExprn[From] = PartitionKeyExprn.ComplexAnd[From](self, other) - // def render2: AliasMapRender[String] = - // self match { - // case PartitionKeyExprn.And(pk, sk) => - // for { - // pkStr <- pk.render - // skStr <- sk.render - // } yield s"$pkStr AND $skStr" - // case PartitionKeyExprn.Equals(pk, value) => - // for { - // pkStr <- AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - // } yield s"$pkStr = $value" - // } + def render2: AliasMapRender[String] = + self match { + case PartitionKeyExprn.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } + + def render: AliasMapRender[String] = + self match { + case PartitionKeyExprn.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } } + sealed trait CompositePrimaryKeyExprn[From] extends KeyConditionExpression[From] { self => } + // models "extended" primary key expressions // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedPartitionKeyExprn[From] extends KeyConditionExpression[From] { self => -// override def render: AliasMapRender[String] = ??? - } + sealed trait ExtendedCompositePrimaryKeyExprn[From] extends KeyConditionExpression[From] { self => } // no overlap between PartitionKeyExprn and ExtendedPartitionKeyExprn @@ -57,22 +56,68 @@ object Foo { } } - final case class And[From](pk: PartitionKeyExprn[From], sk: SortKeyEprn[From]) extends PartitionKeyExprn[From] { + final case class And[From](pk: PartitionKeyExprn[From], sk: SortKeyEprn[From]) + extends CompositePrimaryKeyExprn[From] { self => + + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExprn.And(pk, sk) => + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" + } + // do render in concrete classes } final case class ComplexAnd[From](pk: PartitionKeyExprn[From], sk: ExtendedSortKeyEprn[From]) - extends ExtendedPartitionKeyExprn[From] + extends ExtendedCompositePrimaryKeyExprn[From] { self => + + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExprn.ComplexAnd(pk, sk) => + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" + } + + } + + final case class Equals[From](pk: PartitionKey[From], value: AttributeValue) extends PartitionKeyExprn[From] { + self => - final case class Equals[From](pk: PartitionKey[From], value: AttributeValue) extends PartitionKeyExprn[From] + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExprn.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } + + } } - sealed trait SortKeyEprn[From] { -// def render: AliasMapRender[String] = ??? + sealed trait SortKeyEprn[From] { self => + def render2: AliasMapRender[String] = + self match { + case SortKeyExprn.Equals(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} = $v") + } + } + sealed trait ExtendedSortKeyEprn[From] { self => + def render2: AliasMapRender[String] = + self match { + case SortKeyExprn.GreaterThan(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} > $v") + } + } - sealed trait ExtendedSortKeyEprn[From] - object SortKeyExprn { + object SortKeyExprn { final case class SortKey[From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { val _ = ev @@ -82,7 +127,7 @@ object Foo { val _ = ev GreaterThan(this, to.toAttributeValue(value)) } - // ... and so on for all the other operators + // ... and so on for all the other extended operators } final case class Equals[From](sortKey: SortKey[From], value: AttributeValue) extends SortKeyEprn[From] final case class GreaterThan[From](sortKey: SortKey[From], value: AttributeValue) extends ExtendedSortKeyEprn[From] @@ -95,27 +140,26 @@ object FooExample extends App { // DynamoDbQuery's still use PrimaryKey // typesafe API constructors only expose PartitionKeyEprn - def asPk[From](k: PartitionKeyExprn[From]): PrimaryKey = { - val pk = k match { + def asPk[From](k: PartitionKeyExprn[From]): PrimaryKey = + k match { case PartitionKeyExprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) - case PartitionKeyExprn.And(pk, sk) => + } + def asPk[From](k: CompositePrimaryKeyExprn[From]): PrimaryKey = + k match { + case PartitionKeyExprn.And(pk, sk) => (pk, sk) match { case (PartitionKeyExprn.Equals(pk, value), SortKeyExprn.Equals(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) - case _ => - throw new IllegalArgumentException("This should not happed?????") } } - pk - } - def whereKey[From](k: PartitionKeyExprn[From]) = - k match { - case PartitionKeyExprn.Equals(pk, value) => println(s"pk=$pk, value=$value") - case PartitionKeyExprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") - } - def whereKey[From](k: ExtendedPartitionKeyExprn[From]) = + def whereKey[From](k: KeyConditionExpression[From]) = k match { + // PartitionKeyExprn + case PartitionKeyExprn.Equals(pk, value) => println(s"pk=$pk, value=$value") + // CompositePrimaryKeyExprn + case PartitionKeyExprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") + // ExtendedCompositePrimaryKeyExprn case PartitionKeyExprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") } @@ -124,17 +168,17 @@ object FooExample extends App { // otherwise users of the low level API will have to type all eprns with Nothing for From import Foo.PartitionKeyExprn._ import Foo.SortKeyExprn._ - val x: PartitionKeyExprn[Nothing] = PartitionKey("email") === 1 - /* - "type mismatch;\n found : zio.dynamodb.Foo.SortKeyEprn[Nothing]\n required: zio.dynamodb.Foo.SortKeyEprn[From]\ - nNote: Nothing <: From, but trait SortKeyEprn is invariant in type From.\nYou may wish to define From as +From instead. (SLS 4.5)", - */ - val yyyy: PartitionKeyExprn[Nothing] = PartitionKey("email") === "x" - val xxxx: SortKeyEprn[Nothing] = SortKey("subject") === "y" - val sdfsdf: PartitionKeyExprn[Nothing] = yyyy && xxxx - val y = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" - val z: ExtendedPartitionKeyExprn[Nothing] = + val x1: PartitionKeyExprn[Nothing] = PartitionKey("email") === "x" + val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" + val x3: CompositePrimaryKeyExprn[Nothing] = x1 && x2 + val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" + val x5: ExtendedCompositePrimaryKeyExprn[Nothing] = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" +// "type mismatch;\n found : zio.dynamodb.Foo.SortKeyEprn[Nothing]\n required: zio.dynamodb.Foo.SortKeyEprn[From]\ +// nNote: Nothing <: From, but trait SortKeyEprn is invariant in type From.\nYou may wish to define From as +From instead. (SLS 4.5)", +// val yy = PartitionKey("email") === "x" && SortKey("subject") === "y" +// import zio.dynamodb.ProjectionExpression.$ +// val x6 = $("foo.bar").primaryKey === "x" final case class Student(email: String, subject: String, age: Int) object Student { @@ -142,25 +186,26 @@ object FooExample extends App { val (email, subject, age) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyExprn[Student] = Student.email.primaryKey === "x" - val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" - val sk2: ExtendedSortKeyEprn[Student] = Student.subject.sortKey > "y" - val pkAndSk: PartitionKeyExprn[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pk: PartitionKeyExprn[Student] = Student.email.primaryKey === "x" + val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" + val sk2: ExtendedSortKeyEprn[Student] = Student.subject.sortKey > "y" + val pkAndSk: CompositePrimaryKeyExprn[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended: ExtendedPartitionKeyExprn[Student] = + val pkAndSkExtended: ExtendedCompositePrimaryKeyExprn[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - // GetItem Query will have two overridden versions + // GetItem Query will have three overridden versions // 1) takes AttrMap/PriamaryKey - for users of low level API // 2) takes PartitionKeyExprn - internally this can be converted to AttrMap/PriamaryKey + // 3) takes CompositePrimaryKeyExprn - internally this can b converted to AttrMap/PriamaryKey - // whereKey function (for Query) can have 2 overridden versions - // 1) takes PartitionKeyExprn - // 2) takes ExtendedPartitionKeyExprn - // 3) down side is that users of low level API will have to construct case class instances manually - but they have to do that now anyway - println(asPk(pkAndSk)) - println(pkAndSkExtended) + // whereKey function (for Query) + // 1) takes a KeyConditionExpression + // 2) down side is that users of low level API will have to construct case class instances manually - but they have to do that now anyway + // println(asPk(pkAndSk)) + // println(pkAndSkExtended) // Render requirements - pkAndSk.render.map(s => println(s"rendered: $s")) + val (aliasMap, s) = pkAndSkExtended.render.execute + println(s"aliasMap=$aliasMap, s=$s") } From 2a6fac8b064145e6c8544bde152eb08344504274 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 2 Jul 2023 07:19:37 +0100 Subject: [PATCH 10/69] doc --- dynamodb/src/main/scala/zio/Foo.scala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 5c5467e8f..324b5701c 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -6,13 +6,7 @@ import zio.dynamodb.PrimaryKey import zio.dynamodb.proofs.IsPrimaryKey /** - * This API should only exists in type safe land ie only access is via a PE method (TODO confirm we can do this restriction) - * Looks like we can - * TODO: - * rename PartitionKeyEprn to PrimaryKeyExprn - * copy render methods from - * make PartitionKeyEprn/KeyConditionExpression sum types so that they can be stored in the same type on the query class - * DONE + * Typesafe KeyConditionExpression/primary key experiment */ object Foo { sealed trait KeyConditionExpression[From] extends Renderable { self => From 56e4067a735ff14a83a77c5c3247df2891d89c55 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 2 Jul 2023 07:29:11 +0100 Subject: [PATCH 11/69] cleanup --- dynamodb/src/main/scala/zio/Foo.scala | 2 -- .../src/main/scala/zio/dynamodb/ProjectionExpression.scala | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 324b5701c..324052f8b 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -158,8 +158,6 @@ object FooExample extends App { } // in low level - non type safe land - // TODO: Avi - fix "Nothing <: From, but trait SortKeyEprn is invariant in type From" - // otherwise users of the low level API will have to type all eprns with Nothing for From import Foo.PartitionKeyExprn._ import Foo.SortKeyExprn._ val x1: PartitionKeyExprn[Nothing] = PartitionKey("email") === "x" diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 6a06b244c..169094878 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -134,7 +134,8 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { import zio.dynamodb.Foo - def primaryKey: Foo.PartitionKeyExprn.PartitionKey[From] = // need implicit evidence that type can be primary key IsPrimaryKey[To] + // TODO: need implicit evidence that type can be primary key IsPrimaryKey[To] + def primaryKey: Foo.PartitionKeyExprn.PartitionKey[From] = self match { case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen From 5266d500a55c06ac3097d1e93b3fb1a19111a04c Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 2 Jul 2023 08:12:39 +0100 Subject: [PATCH 12/69] extend to low level API --- dynamodb/src/main/scala/zio/Foo.scala | 5 +++-- .../zio/dynamodb/ProjectionExpression.scala | 21 ++++++++++++++++--- .../zio/dynamodb/proofs/IsPrimaryKey.scala | 5 ++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 324052f8b..00d678799 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -169,8 +169,9 @@ object FooExample extends App { // "type mismatch;\n found : zio.dynamodb.Foo.SortKeyEprn[Nothing]\n required: zio.dynamodb.Foo.SortKeyEprn[From]\ // nNote: Nothing <: From, but trait SortKeyEprn is invariant in type From.\nYou may wish to define From as +From instead. (SLS 4.5)", // val yy = PartitionKey("email") === "x" && SortKey("subject") === "y" -// import zio.dynamodb.ProjectionExpression.$ -// val x6 = $("foo.bar").primaryKey === "x" +import zio.dynamodb.ProjectionExpression.$ +val x6: CompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" +val x7: ExtendedCompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" final case class Student(email: String, subject: String, age: Int) object Student { diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 169094878..0ce332806 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -134,17 +134,21 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { import zio.dynamodb.Foo - // TODO: need implicit evidence that type can be primary key IsPrimaryKey[To] - def primaryKey: Foo.PartitionKeyExprn.PartitionKey[From] = + + def primaryKey(implicit ev: IsPrimaryKey[To]): Foo.PartitionKeyExprn.PartitionKey[From] = { + val _ = ev self match { case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: Foo.SortKeyExprn.SortKey[From] = + } + def sortKey(implicit ev: IsPrimaryKey[To]): Foo.SortKeyExprn.SortKey[From] = { + val _ = ev self match { case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } + } def set(a: To): UpdateExpression.Action.SetAction[From, To] = UpdateExpression.Action.SetAction( @@ -541,6 +545,17 @@ object ProjectionExpression extends ProjectionExpressionLowPriorityImplicits0 { implicit class ProjectionExpressionSyntax[From](self: ProjectionExpression[From, Unknown]) { + def primaryKey: Foo.PartitionKeyExprn.PartitionKey[From] = + self match { + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey[From](key) + case _ => throw new IllegalArgumentException("Not a partition key") // should not happen + } + def sortKey: Foo.SortKeyExprn.SortKey[From] = + self match { + case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey[From](key) + case _ => throw new IllegalArgumentException("Not a partition key") // should not happen + } + /** * Modify or Add an item Attribute */ diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index 4389248b0..b56768414 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -1,11 +1,14 @@ package zio.dynamodb.proofs +import scala.annotation.implicitNotFound + +@implicitNotFound("DynamoDB does not support primary key type ${A}") sealed trait IsPrimaryKey[A] object IsPrimaryKey { implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} - // HOWEVER - String type in the DB is overloaded in Scala land eg Date in Scala is String in DB + // TODO - String type in the DB is overloaded in Scala land eg Date in Scala is String in DB // so do we also allow Scala types that are strings in the DB? implicit val stringIsPrimaryKey = new IsPrimaryKey[String] {} // todo binary data From e43fb87d65a1d32e715316fe308408b394865cc0 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 8 Jul 2023 06:17:51 +0100 Subject: [PATCH 13/69] contravariance for From --- dynamodb/src/main/scala/zio/Foo.scala | 46 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 00d678799..b79b2d332 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -8,18 +8,22 @@ import zio.dynamodb.proofs.IsPrimaryKey /** * Typesafe KeyConditionExpression/primary key experiment */ +/* +TODO +- Expr - shortning of name everywhere +*/ object Foo { - sealed trait KeyConditionExpression[From] extends Renderable { self => + sealed trait KeyConditionExpression[-From] extends Renderable { self => def render: AliasMapRender[String] // do render in concrete classes } object KeyConditionExpression {} // models primary key expressions // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - sealed trait PartitionKeyExprn[From] extends KeyConditionExpression[From] { self => - def &&(other: SortKeyEprn[From]): CompositePrimaryKeyExprn[From] = PartitionKeyExprn.And[From](self, other) - def &&(other: ExtendedSortKeyEprn[From]): ExtendedCompositePrimaryKeyExprn[From] = - PartitionKeyExprn.ComplexAnd[From](self, other) + sealed trait PartitionKeyExprn[-From] extends KeyConditionExpression[From] { self => + def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExprn[From1] = PartitionKeyExprn.And[From1](self, other) + def &&[From1 <: From](other: ExtendedSortKeyEprn[From1]): ExtendedCompositePrimaryKeyExprn[From1] = + PartitionKeyExprn.ComplexAnd[From1](self, other) def render2: AliasMapRender[String] = self match { @@ -34,15 +38,23 @@ object Foo { } } - sealed trait CompositePrimaryKeyExprn[From] extends KeyConditionExpression[From] { self => } + // TODO: colapse single item sum types into a case class + sealed trait CompositePrimaryKeyExprn[-From] extends KeyConditionExpression[From] { self => } // models "extended" primary key expressions // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedCompositePrimaryKeyExprn[From] extends KeyConditionExpression[From] { self => } + + + sealed trait ExtendedCompositePrimaryKeyExprn[-From] extends KeyConditionExpression[From] { self => } + + object ExtendedCompositePrimaryKeyExprn { + // TODO move ComplexAnd here + } // no overlap between PartitionKeyExprn and ExtendedPartitionKeyExprn object PartitionKeyExprn { + // belongs to the package top level final case class PartitionKey[From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExprn[From] = { val _ = ev @@ -50,6 +62,7 @@ object Foo { } } + // TODO move to companion object final case class And[From](pk: PartitionKeyExprn[From], sk: SortKeyEprn[From]) extends CompositePrimaryKeyExprn[From] { self => @@ -66,7 +79,9 @@ object Foo { // do render in concrete classes } - final case class ComplexAnd[From](pk: PartitionKeyExprn[From], sk: ExtendedSortKeyEprn[From]) + + // TODO move to companion object + final case class ComplexAnd[-From](pk: PartitionKeyExprn[From], sk: ExtendedSortKeyEprn[From]) extends ExtendedCompositePrimaryKeyExprn[From] { self => override def render: AliasMapRender[String] = @@ -92,7 +107,7 @@ object Foo { } } - sealed trait SortKeyEprn[From] { self => + sealed trait SortKeyEprn[-From] { self => def render2: AliasMapRender[String] = self match { case SortKeyExprn.Equals(sk, value) => @@ -101,7 +116,7 @@ object Foo { .map(v => s"${sk.keyName} = $v") } } - sealed trait ExtendedSortKeyEprn[From] { self => + sealed trait ExtendedSortKeyEprn[-From] { self => def render2: AliasMapRender[String] = self match { case SortKeyExprn.GreaterThan(sk, value) => @@ -134,6 +149,8 @@ object FooExample extends App { // DynamoDbQuery's still use PrimaryKey // typesafe API constructors only expose PartitionKeyEprn + + // TODO: move to PartitionKeyExprn def asPk[From](k: PartitionKeyExprn[From]): PrimaryKey = k match { case PartitionKeyExprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) @@ -168,10 +185,11 @@ object FooExample extends App { PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" // "type mismatch;\n found : zio.dynamodb.Foo.SortKeyEprn[Nothing]\n required: zio.dynamodb.Foo.SortKeyEprn[From]\ // nNote: Nothing <: From, but trait SortKeyEprn is invariant in type From.\nYou may wish to define From as +From instead. (SLS 4.5)", -// val yy = PartitionKey("email") === "x" && SortKey("subject") === "y" -import zio.dynamodb.ProjectionExpression.$ -val x6: CompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" -val x7: ExtendedCompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" + val yy: CompositePrimaryKeyExprn[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" + + import zio.dynamodb.ProjectionExpression.$ + val x6: CompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" + val x7: ExtendedCompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" final case class Student(email: String, subject: String, age: Int) object Student { From 9980af242f86a9a55fd5d529e6971c89bf8bef96 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 8 Jul 2023 06:22:22 +0100 Subject: [PATCH 14/69] clean up --- dynamodb/src/main/scala/zio/Foo.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index b79b2d332..83d13b3c3 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -11,7 +11,7 @@ import zio.dynamodb.proofs.IsPrimaryKey /* TODO - Expr - shortning of name everywhere -*/ + */ object Foo { sealed trait KeyConditionExpression[-From] extends Renderable { self => def render: AliasMapRender[String] // do render in concrete classes @@ -21,7 +21,8 @@ object Foo { // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" sealed trait PartitionKeyExprn[-From] extends KeyConditionExpression[From] { self => - def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExprn[From1] = PartitionKeyExprn.And[From1](self, other) + def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExprn[From1] = + PartitionKeyExprn.And[From1](self, other) def &&[From1 <: From](other: ExtendedSortKeyEprn[From1]): ExtendedCompositePrimaryKeyExprn[From1] = PartitionKeyExprn.ComplexAnd[From1](self, other) @@ -44,7 +45,6 @@ object Foo { // models "extended" primary key expressions // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedCompositePrimaryKeyExprn[-From] extends KeyConditionExpression[From] { self => } object ExtendedCompositePrimaryKeyExprn { @@ -183,9 +183,10 @@ object FooExample extends App { val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" val x5: ExtendedCompositePrimaryKeyExprn[Nothing] = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" -// "type mismatch;\n found : zio.dynamodb.Foo.SortKeyEprn[Nothing]\n required: zio.dynamodb.Foo.SortKeyEprn[From]\ -// nNote: Nothing <: From, but trait SortKeyEprn is invariant in type From.\nYou may wish to define From as +From instead. (SLS 4.5)", - val yy: CompositePrimaryKeyExprn[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" + + val y0: PartitionKeyExprn[Any] = PartitionKey("email") === "x" + val y1: CompositePrimaryKeyExprn[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" + val y2: ExtendedCompositePrimaryKeyExprn[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" import zio.dynamodb.ProjectionExpression.$ val x6: CompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" From 9640c0327adcbc8142f0656a131f5ac873daad0b Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 8 Jul 2023 06:36:07 +0100 Subject: [PATCH 15/69] shorten names to Expr --- dynamodb/src/main/scala/zio/Foo.scala | 119 +++++++++++++------------- 1 file changed, 60 insertions(+), 59 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 83d13b3c3..3b4a4fb35 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -10,66 +10,67 @@ import zio.dynamodb.proofs.IsPrimaryKey */ /* TODO -- Expr - shortning of name everywhere +- make raw constrctors that contain Phamtom types private + - expose helper methods for them */ object Foo { - sealed trait KeyConditionExpression[-From] extends Renderable { self => + sealed trait KeyConditionExpr[-From] extends Renderable { self => def render: AliasMapRender[String] // do render in concrete classes } - object KeyConditionExpression {} + object KeyConditionExpr {} // models primary key expressions // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - sealed trait PartitionKeyExprn[-From] extends KeyConditionExpression[From] { self => - def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExprn[From1] = - PartitionKeyExprn.And[From1](self, other) - def &&[From1 <: From](other: ExtendedSortKeyEprn[From1]): ExtendedCompositePrimaryKeyExprn[From1] = - PartitionKeyExprn.ComplexAnd[From1](self, other) + sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => + def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExpr[From1] = + PartitionKeyExpr.And[From1](self, other) + def &&[From1 <: From](other: ExtendedSortKeyEprn[From1]): ExtendedCompositePrimaryKeyExpr[From1] = + PartitionKeyExpr.ComplexAnd[From1](self, other) def render2: AliasMapRender[String] = self match { - case PartitionKeyExprn.Equals(pk, value) => + case PartitionKeyExpr.Equals(pk, value) => AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } def render: AliasMapRender[String] = self match { - case PartitionKeyExprn.Equals(pk, value) => + case PartitionKeyExpr.Equals(pk, value) => AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } } // TODO: colapse single item sum types into a case class - sealed trait CompositePrimaryKeyExprn[-From] extends KeyConditionExpression[From] { self => } + sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } // models "extended" primary key expressions // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedCompositePrimaryKeyExprn[-From] extends KeyConditionExpression[From] { self => } + sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } - object ExtendedCompositePrimaryKeyExprn { + object ExtendedCompositePrimaryKeyExpr { // TODO move ComplexAnd here } - // no overlap between PartitionKeyExprn and ExtendedPartitionKeyExprn + // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr - object PartitionKeyExprn { + object PartitionKeyExpr { // belongs to the package top level final case class PartitionKey[From](keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExprn[From] = { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { val _ = ev Equals(this, to.toAttributeValue(value)) } } // TODO move to companion object - final case class And[From](pk: PartitionKeyExprn[From], sk: SortKeyEprn[From]) - extends CompositePrimaryKeyExprn[From] { + final case class And[From](pk: PartitionKeyExpr[From], sk: SortKeyEprn[From]) + extends CompositePrimaryKeyExpr[From] { self => override def render: AliasMapRender[String] = self match { - case PartitionKeyExprn.And(pk, sk) => + case PartitionKeyExpr.And(pk, sk) => for { pkStr <- pk.render2 skStr <- sk.render2 @@ -81,12 +82,12 @@ object Foo { } // TODO move to companion object - final case class ComplexAnd[-From](pk: PartitionKeyExprn[From], sk: ExtendedSortKeyEprn[From]) - extends ExtendedCompositePrimaryKeyExprn[From] { self => + final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyEprn[From]) + extends ExtendedCompositePrimaryKeyExpr[From] { self => override def render: AliasMapRender[String] = self match { - case PartitionKeyExprn.ComplexAnd(pk, sk) => + case PartitionKeyExpr.ComplexAnd(pk, sk) => for { pkStr <- pk.render2 skStr <- sk.render2 @@ -95,12 +96,12 @@ object Foo { } - final case class Equals[From](pk: PartitionKey[From], value: AttributeValue) extends PartitionKeyExprn[From] { + final case class Equals[From](pk: PartitionKey[From], value: AttributeValue) extends PartitionKeyExpr[From] { self => override def render: AliasMapRender[String] = self match { - case PartitionKeyExprn.Equals(pk, value) => + case PartitionKeyExpr.Equals(pk, value) => AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } @@ -110,7 +111,7 @@ object Foo { sealed trait SortKeyEprn[-From] { self => def render2: AliasMapRender[String] = self match { - case SortKeyExprn.Equals(sk, value) => + case SortKeyExpr.Equals(sk, value) => AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} = $v") @@ -119,14 +120,14 @@ object Foo { sealed trait ExtendedSortKeyEprn[-From] { self => def render2: AliasMapRender[String] = self match { - case SortKeyExprn.GreaterThan(sk, value) => + case SortKeyExpr.GreaterThan(sk, value) => AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} > $v") } } - object SortKeyExprn { + object SortKeyExpr { final case class SortKey[From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { val _ = ev @@ -150,47 +151,47 @@ object FooExample extends App { // DynamoDbQuery's still use PrimaryKey // typesafe API constructors only expose PartitionKeyEprn - // TODO: move to PartitionKeyExprn - def asPk[From](k: PartitionKeyExprn[From]): PrimaryKey = + // TODO: move to PartitionKeyExpr + def asPk[From](k: PartitionKeyExpr[From]): PrimaryKey = k match { - case PartitionKeyExprn.Equals(pk, value) => PrimaryKey(pk.keyName -> value) + case PartitionKeyExpr.Equals(pk, value) => PrimaryKey(pk.keyName -> value) } - def asPk[From](k: CompositePrimaryKeyExprn[From]): PrimaryKey = + def asPk[From](k: CompositePrimaryKeyExpr[From]): PrimaryKey = k match { - case PartitionKeyExprn.And(pk, sk) => + case PartitionKeyExpr.And(pk, sk) => (pk, sk) match { - case (PartitionKeyExprn.Equals(pk, value), SortKeyExprn.Equals(sk, value2)) => + case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) } } - def whereKey[From](k: KeyConditionExpression[From]) = + def whereKey[From](k: KeyConditionExpr[From]) = k match { - // PartitionKeyExprn - case PartitionKeyExprn.Equals(pk, value) => println(s"pk=$pk, value=$value") - // CompositePrimaryKeyExprn - case PartitionKeyExprn.And(pk, sk) => println(s"pk=$pk, sk=$sk") - // ExtendedCompositePrimaryKeyExprn - case PartitionKeyExprn.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") + // PartitionKeyExpr + case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") + // CompositePrimaryKeyExpr + case PartitionKeyExpr.And(pk, sk) => println(s"pk=$pk, sk=$sk") + // ExtendedCompositePrimaryKeyExpr + case PartitionKeyExpr.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") } // in low level - non type safe land - import Foo.PartitionKeyExprn._ - import Foo.SortKeyExprn._ - val x1: PartitionKeyExprn[Nothing] = PartitionKey("email") === "x" - val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" - val x3: CompositePrimaryKeyExprn[Nothing] = x1 && x2 - val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" - val x5: ExtendedCompositePrimaryKeyExprn[Nothing] = + import Foo.PartitionKeyExpr._ + import Foo.SortKeyExpr._ + val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" + val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" + val x3: CompositePrimaryKeyExpr[Nothing] = x1 && x2 + val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" + val x5: ExtendedCompositePrimaryKeyExpr[Nothing] = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" - val y0: PartitionKeyExprn[Any] = PartitionKey("email") === "x" - val y1: CompositePrimaryKeyExprn[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" - val y2: ExtendedCompositePrimaryKeyExprn[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" + val y0: PartitionKeyExpr[Any] = PartitionKey("email") === "x" + val y1: CompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" + val y2: ExtendedCompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" import zio.dynamodb.ProjectionExpression.$ - val x6: CompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" - val x7: ExtendedCompositePrimaryKeyExprn[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" + val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" + val x7: ExtendedCompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" final case class Student(email: String, subject: String, age: Int) object Student { @@ -198,18 +199,18 @@ object FooExample extends App { val (email, subject, age) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyExprn[Student] = Student.email.primaryKey === "x" - val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" - val sk2: ExtendedSortKeyEprn[Student] = Student.subject.sortKey > "y" - val pkAndSk: CompositePrimaryKeyExprn[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" + val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" + val sk2: ExtendedSortKeyEprn[Student] = Student.subject.sortKey > "y" + val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended: ExtendedCompositePrimaryKeyExprn[Student] = + val pkAndSkExtended: ExtendedCompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" // GetItem Query will have three overridden versions // 1) takes AttrMap/PriamaryKey - for users of low level API - // 2) takes PartitionKeyExprn - internally this can be converted to AttrMap/PriamaryKey - // 3) takes CompositePrimaryKeyExprn - internally this can b converted to AttrMap/PriamaryKey + // 2) takes PartitionKeyExpr - internally this can be converted to AttrMap/PriamaryKey + // 3) takes CompositePrimaryKeyExpr - internally this can b converted to AttrMap/PriamaryKey // whereKey function (for Query) // 1) takes a KeyConditionExpression From 9d9aa2bbc435db66a501b3e949971cfdb39ab85a Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 8 Jul 2023 07:17:38 +0100 Subject: [PATCH 16/69] fix PE refactor --- dynamodb/src/main/scala/zio/Foo.scala | 10 ++++++---- .../zio/dynamodb/ProjectionExpression.scala | 16 ++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 3b4a4fb35..af48237ac 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -10,12 +10,13 @@ import zio.dynamodb.proofs.IsPrimaryKey */ /* TODO +- consider collapsing 1 member sealed traits - make raw constrctors that contain Phamtom types private - expose helper methods for them */ object Foo { - sealed trait KeyConditionExpr[-From] extends Renderable { self => - def render: AliasMapRender[String] // do render in concrete classes + sealed trait KeyConditionExpr[-From] extends Renderable { self => + def render: AliasMapRender[String] } object KeyConditionExpr {} // models primary key expressions @@ -40,12 +41,13 @@ object Foo { } } - // TODO: colapse single item sum types into a case class + // sealed trait has only one member but it is usefull as a type alias to guide the user + // for instance And extends it - but having a type called And would make no sense sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } + // sealed trait has only one member but it is usefull as a type alias to guide the user // models "extended" primary key expressions // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } object ExtendedCompositePrimaryKeyExpr { diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 0ce332806..3f9af1cb2 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -135,17 +135,17 @@ trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowP implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { import zio.dynamodb.Foo - def primaryKey(implicit ev: IsPrimaryKey[To]): Foo.PartitionKeyExprn.PartitionKey[From] = { + def primaryKey(implicit ev: IsPrimaryKey[To]): Foo.PartitionKeyExpr.PartitionKey[From] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExpr.PartitionKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } - def sortKey(implicit ev: IsPrimaryKey[To]): Foo.SortKeyExprn.SortKey[From] = { + def sortKey(implicit ev: IsPrimaryKey[To]): Foo.SortKeyExpr.SortKey[From] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExpr.SortKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } @@ -545,14 +545,14 @@ object ProjectionExpression extends ProjectionExpressionLowPriorityImplicits0 { implicit class ProjectionExpressionSyntax[From](self: ProjectionExpression[From, Unknown]) { - def primaryKey: Foo.PartitionKeyExprn.PartitionKey[From] = + def primaryKey: Foo.PartitionKeyExpr.PartitionKey[From] = self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExprn.PartitionKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExpr.PartitionKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: Foo.SortKeyExprn.SortKey[From] = + def sortKey: Foo.SortKeyExpr.SortKey[From] = self match { - case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExprn.SortKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExpr.SortKey[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } From d8e529237832589d9e64143ba43e14c38e8c0401 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 8 Jul 2023 07:39:00 +0100 Subject: [PATCH 17/69] doc comments --- dynamodb/src/main/scala/zio/Foo.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index af48237ac..b84c2b91c 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -110,6 +110,7 @@ object Foo { } } + // single member sealed trait - but useful as a type alias sealed trait SortKeyEprn[-From] { self => def render2: AliasMapRender[String] = self match { @@ -119,6 +120,7 @@ object Foo { .map(v => s"${sk.keyName} = $v") } } + // single member sealed trait - but useful as a type alias sealed trait ExtendedSortKeyEprn[-From] { self => def render2: AliasMapRender[String] = self match { From 83b3bdccd23c47378268447920a12aecabf8829f Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 8 Jul 2023 07:42:40 +0100 Subject: [PATCH 18/69] remove failed experiment --- .../dynamodb/KeyConditionExpression2.scala | 313 ------------------ 1 file changed, 313 deletions(-) delete mode 100644 dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala deleted file mode 100644 index 0bca9c84d..000000000 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression2.scala +++ /dev/null @@ -1,313 +0,0 @@ -package zio.dynamodb - -import zio.dynamodb.PartitionKeyExpression2.PartitionKey -import zio.dynamodb.SortKeyExpression2.SortKey - -/* -KeyCondition expression is a restricted version of ConditionExpression where by -- partition exprn is required and can only use "=" equals comparison -- optionally AND can be used to add a sort key expression - -eg partitionKeyName = :partitionkeyval AND sortKeyName = :sortkeyval -comparisons operators are the same as for Condition - - */ - -sealed trait KeyConditionExpression2 extends Renderable { self => - def render: AliasMapRender[String] = - self match { - case KeyConditionExpression2.And(left, right) => - left.render - .zipWith( - right.render - ) { case (l, r) => s"$l AND $r" } // TODO: Avi - use alias map if DDB requires it - case expression: PartitionKeyExpression2 => expression.render - } - -} - -object KeyConditionExpression2 { - private[dynamodb] final case class And(left: PartitionKeyExpression2, right: SortKeyExpression2) - extends KeyConditionExpression2 - def partitionKey(key: String): PartitionKey = PartitionKey(key) - - /** - * Create a KeyConditionExpression from a ConditionExpression - * Must be in the form of ` && ` where format of `` is: - * {{{ === }}} - * and the format of `` is: - * {{{ }}} where op can be one of `===`, `>`, `>=`, `<`, `<=`, `between`, `beginsWith` - * - * Example using type API: - * {{{ - * val (email, subject, enrollmentDate, payment) = ProjectionExpression.accessors[Student] - * // ... - * val keyConditionExprn = filterKey(email === "avi@gmail.com" && subject === "maths") - * }}} - */ - private[dynamodb] def fromConditionExpressionUnsafe(c: ConditionExpression[_]): KeyConditionExpression = - KeyConditionExpression(c).getOrElse( - throw new IllegalStateException(s"Error: invalid key condition expression $c") - ) - - // // TODO: Avi - revists - // private[dynamodb] def apply(c: ConditionExpression[_]): Either[String, KeyConditionExpression2] = - // c match { - // case ConditionExpression.Equals( - // ProjectionExpressionOperand(MapElement(Root, partitionKey)), - // ConditionExpression.Operand.ValueOperand(av) - // ) => - // Right(PartitionKeyExpression2.Equals(PartitionKey(partitionKey), av)) - // case ConditionExpression.And( - // ConditionExpression.Equals( - // ProjectionExpressionOperand(MapElement(Root, partitionKey)), - // ConditionExpression.Operand.ValueOperand(avL) - // ), - // rhs - // ) => - // rhs match { - // case ConditionExpression.Equals( - // ProjectionExpressionOperand(MapElement(Root, sortKey)), - // ConditionExpression.Operand.ValueOperand(avR) - // ) => - // Right( - // PartitionKeyExpression2 - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SimSortKeyExpression2.Equals(SortKey(sortKey), avR)) - // ) - // case ConditionExpression.NotEqual( - // ProjectionExpressionOperand(MapElement(Root, sortKey)), - // ConditionExpression.Operand.ValueOperand(avR) - // ) => - // Right( - // PartitionKeyExpression - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SortKeyExpression.NotEqual(SortKey(sortKey), avR)) - // ) - // case ConditionExpression.GreaterThan( - // ProjectionExpressionOperand(MapElement(Root, sortKey)), - // ConditionExpression.Operand.ValueOperand(avR) - // ) => - // Right( - // PartitionKeyExpression - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SortKeyExpression.GreaterThan(SortKey(sortKey), avR)) - // ) - // case ConditionExpression.LessThan( - // ProjectionExpressionOperand(MapElement(Root, sortKey)), - // ConditionExpression.Operand.ValueOperand(avR) - // ) => - // Right( - // PartitionKeyExpression - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SortKeyExpression.LessThan(SortKey(sortKey), avR)) - // ) - // case ConditionExpression.GreaterThanOrEqual( - // ProjectionExpressionOperand(MapElement(Root, sortKey)), - // ConditionExpression.Operand.ValueOperand(avR) - // ) => - // Right( - // PartitionKeyExpression - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SortKeyExpression.GreaterThanOrEqual(SortKey(sortKey), avR)) - // ) - // case ConditionExpression.LessThanOrEqual( - // ProjectionExpressionOperand(MapElement(Root, sortKey)), - // ConditionExpression.Operand.ValueOperand(avR) - // ) => - // Right( - // PartitionKeyExpression - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SortKeyExpression.LessThanOrEqual(SortKey(sortKey), avR)) - // ) - // case ConditionExpression.Between( - // ProjectionExpressionOperand(MapElement(Root, sortKey)), - // avMin, - // avMax - // ) => - // Right( - // PartitionKeyExpression - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SortKeyExpression.Between(SortKey(sortKey), avMin, avMax)) - // ) - // case ConditionExpression.BeginsWith( - // MapElement(Root, sortKey), - // av - // ) => - // Right( - // PartitionKeyExpression - // .Equals(PartitionKey(partitionKey), avL) - // .&&(SortKeyExpression.BeginsWith(SortKey(sortKey), av)) - // ) - // case c => Left(s"condition '$c' is not a valid sort condition expression") - // } - - // case c => Left(s"condition $c is not a valid key condition expression") - // } - -} - -sealed trait PartitionKeyExpression2 extends KeyConditionExpression2 { self => - import KeyConditionExpression2.And - - def &&(that: SortKeyExpression2): KeyConditionExpression2 = And(self, that) - - override def render: AliasMapRender[String] = - self match { - case PartitionKeyExpression2.Equals(left, right) => - AliasMapRender.getOrInsert(right).map(v => s"${left.keyName} = $v") - } -} -object PartitionKeyExpression2 { - final case class PartitionKey(keyName: String) { self => - def ===[A](that: A)(implicit t: ToAttributeValue[A]): PartitionKeyExpression2 = - Equals(self, t.toAttributeValue(that)) - } - final case class Equals(left: PartitionKey, right: AttributeValue) extends PartitionKeyExpression2 -} - -sealed trait SimpleSortKeyExpression2 { self => - def render: AliasMapRender[String] = - self match { - case SimpleSortKeyExpression2.Equals(left, right) => - AliasMapRender - .getOrInsert(right) - .map { v => - s"${left.keyName} = $v" - } - case SortKeyExpression2.LessThan(left, right) => - AliasMapRender - .getOrInsert(right) - .map { v => - s"${left.keyName} < $v" - } - case SortKeyExpression2.NotEqual(left, right) => - AliasMapRender - .getOrInsert(right) - .map { v => - s"${left.keyName} <> $v" - } - case SortKeyExpression2.GreaterThan(left, right) => - AliasMapRender - .getOrInsert(right) - .map { v => - s"${left.keyName} > $v" - } - case SortKeyExpression2.LessThanOrEqual(left, right) => - AliasMapRender - .getOrInsert(right) - .map { v => - s"${left.keyName} <= $v" - } - case SortKeyExpression2.GreaterThanOrEqual(left, right) => - AliasMapRender - .getOrInsert(right) - .map { v => - s"${left.keyName} >= $v" - } - case SortKeyExpression2.Between(left, min, max) => - AliasMapRender - .getOrInsert(min) - .flatMap(min => - AliasMapRender.getOrInsert(max).map { max => - s"${left.keyName} BETWEEN $min AND $max" - } - ) - case SortKeyExpression2.BeginsWith(left, value) => - AliasMapRender - .getOrInsert(value) - .map { v => - s"begins_with(${left.keyName}, $v)" - } - } -} -object SimpleSortKeyExpression2 { - private[dynamodb] final case class Equals(left: SortKey, right: AttributeValue) extends SimpleSortKeyExpression2 -} - -sealed trait SortKeyExpression2 extends SimpleSortKeyExpression2 { self => - // def render: AliasMapRender[String] = - // self match { - // case SimpleSortKeyExpression2.Equals(left, right) => - // AliasMapRender - // .getOrInsert(right) - // .map { v => - // s"${left.keyName} = $v" - // } - // case SortKeyExpression2.LessThan(left, right) => - // AliasMapRender - // .getOrInsert(right) - // .map { v => - // s"${left.keyName} < $v" - // } - // case SortKeyExpression2.NotEqual(left, right) => - // AliasMapRender - // .getOrInsert(right) - // .map { v => - // s"${left.keyName} <> $v" - // } - // case SortKeyExpression2.GreaterThan(left, right) => - // AliasMapRender - // .getOrInsert(right) - // .map { v => - // s"${left.keyName} > $v" - // } - // case SortKeyExpression2.LessThanOrEqual(left, right) => - // AliasMapRender - // .getOrInsert(right) - // .map { v => - // s"${left.keyName} <= $v" - // } - // case SortKeyExpression2.GreaterThanOrEqual(left, right) => - // AliasMapRender - // .getOrInsert(right) - // .map { v => - // s"${left.keyName} >= $v" - // } - // case SortKeyExpression2.Between(left, min, max) => - // AliasMapRender - // .getOrInsert(min) - // .flatMap(min => - // AliasMapRender.getOrInsert(max).map { max => - // s"${left.keyName} BETWEEN $min AND $max" - // } - // ) - // case SortKeyExpression2.BeginsWith(left, value) => - // AliasMapRender - // .getOrInsert(value) - // .map { v => - // s"begins_with(${left.keyName}, $v)" - // } - // } - -} - -object SortKeyExpression2 { - - final case class SortKey(keyName: String) { self => - def ===[A](that: A)(implicit t: ToAttributeValue[A]): SimpleSortKeyExpression2 = - SimpleSortKeyExpression2.Equals(self, t.toAttributeValue(that)) - def <>[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = NotEqual(self, t.toAttributeValue(that)) - def <[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = LessThan(self, t.toAttributeValue(that)) - def <=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = - LessThanOrEqual(self, t.toAttributeValue(that)) - def >[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = - GreaterThanOrEqual(self, t.toAttributeValue(that)) - def >=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = - GreaterThanOrEqual(self, t.toAttributeValue(that)) - def between[A](min: A, max: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = - Between(self, t.toAttributeValue(min), t.toAttributeValue(max)) - def beginsWith[A](value: A)(implicit t: ToAttributeValue[A]): SortKeyExpression2 = - BeginsWith(self, t.toAttributeValue(value)) - } - -// private[dynamodb] final case class Equals(left: SortKey, right: AttributeValue) extends SimpleSortKeyExpression2 - private[dynamodb] final case class NotEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression2 - private[dynamodb] final case class LessThan(left: SortKey, right: AttributeValue) extends SortKeyExpression2 - private[dynamodb] final case class GreaterThan(left: SortKey, right: AttributeValue) extends SortKeyExpression2 - private[dynamodb] final case class LessThanOrEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression2 - private[dynamodb] final case class GreaterThanOrEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression2 - private[dynamodb] final case class Between(left: SortKey, min: AttributeValue, max: AttributeValue) - extends SortKeyExpression2 - private[dynamodb] final case class BeginsWith(left: SortKey, value: AttributeValue) extends SortKeyExpression2 -} From b8854e80408a5dad09fcb634e49ac6ebb2d58e5a Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 9 Jul 2023 07:28:31 +0100 Subject: [PATCH 19/69] extracted companion objects --- dynamodb/src/main/scala/zio/Foo.scala | 140 ++++++++++-------- .../zio/dynamodb/ProjectionExpression.scala | 16 +- 2 files changed, 89 insertions(+), 67 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index b84c2b91c..5eae252ad 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -15,18 +15,39 @@ TODO - expose helper methods for them */ object Foo { + + // belongs to the package top level + final case class PartitionKey2[From](keyName: String) { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { + val _ = ev + PartitionKeyExpr.Equals(this, to.toAttributeValue(value)) + } + } + + final case class SortKey2[From](keyName: String) { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { + val _ = ev + SortKeyExpr.Equals[From](this, to.toAttributeValue(value)) + } + def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + val _ = ev + ExtendedSortKeyExpr.GreaterThan(this, to.toAttributeValue(value)) + } + // ... and so on for all the other extended operators + } + sealed trait KeyConditionExpr[-From] extends Renderable { self => def render: AliasMapRender[String] } - object KeyConditionExpr {} + // models primary key expressions // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExpr[From1] = - PartitionKeyExpr.And[From1](self, other) - def &&[From1 <: From](other: ExtendedSortKeyEprn[From1]): ExtendedCompositePrimaryKeyExpr[From1] = - PartitionKeyExpr.ComplexAnd[From1](self, other) + CompositePrimaryKeyExpr.And[From1](self, other) + def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = + ExtendedCompositePrimaryKeyExpr.ComplexAnd[From1](self, other) def render2: AliasMapRender[String] = self match { @@ -34,6 +55,11 @@ object Foo { AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } + def asAttrMap: AttrMap = + self match { + case PartitionKeyExpr.Equals(pk, value) => AttrMap(pk.keyName -> value) + } + def render: AliasMapRender[String] = self match { case PartitionKeyExpr.Equals(pk, value) => @@ -43,28 +69,19 @@ object Foo { // sealed trait has only one member but it is usefull as a type alias to guide the user // for instance And extends it - but having a type called And would make no sense - sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } - - // sealed trait has only one member but it is usefull as a type alias to guide the user - // models "extended" primary key expressions - // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } + sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => + def asAttrVal: PrimaryKey = + self match { + case CompositePrimaryKeyExpr.And(pk, sk) => + (pk, sk) match { + case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => + PrimaryKey(pk.keyName -> value, sk.keyName -> value2) + } + } - object ExtendedCompositePrimaryKeyExpr { - // TODO move ComplexAnd here } - // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr - - object PartitionKeyExpr { - // belongs to the package top level - final case class PartitionKey[From](keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { - val _ = ev - Equals(this, to.toAttributeValue(value)) - } - } - + object CompositePrimaryKeyExpr { // TODO move to companion object final case class And[From](pk: PartitionKeyExpr[From], sk: SortKeyEprn[From]) extends CompositePrimaryKeyExpr[From] { @@ -72,7 +89,7 @@ object Foo { override def render: AliasMapRender[String] = self match { - case PartitionKeyExpr.And(pk, sk) => + case CompositePrimaryKeyExpr.And(pk, sk) => for { pkStr <- pk.render2 skStr <- sk.render2 @@ -83,13 +100,22 @@ object Foo { } + } + + // sealed trait has only one member but it is usefull as a type alias to guide the user + // models "extended" primary key expressions + // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } + + object ExtendedCompositePrimaryKeyExpr { + // TODO move ComplexAnd here // TODO move to companion object - final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyEprn[From]) + final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) extends ExtendedCompositePrimaryKeyExpr[From] { self => override def render: AliasMapRender[String] = self match { - case PartitionKeyExpr.ComplexAnd(pk, sk) => + case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => for { pkStr <- pk.render2 skStr <- sk.render2 @@ -98,7 +124,13 @@ object Foo { } - final case class Equals[From](pk: PartitionKey[From], value: AttributeValue) extends PartitionKeyExpr[From] { + } + + // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr + + object PartitionKeyExpr { + + final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { self => override def render: AliasMapRender[String] = @@ -121,30 +153,22 @@ object Foo { } } // single member sealed trait - but useful as a type alias - sealed trait ExtendedSortKeyEprn[-From] { self => + sealed trait ExtendedSortKeyExpr[-From] { self => def render2: AliasMapRender[String] = self match { - case SortKeyExpr.GreaterThan(sk, value) => + case ExtendedSortKeyExpr.GreaterThan(sk, value) => AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} > $v") } } + object ExtendedSortKeyExpr { + final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] + } + object SortKeyExpr { - final case class SortKey[From](keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { - val _ = ev - Equals[From](this, to.toAttributeValue(value)) - } - def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyEprn[From] = { - val _ = ev - GreaterThan(this, to.toAttributeValue(value)) - } - // ... and so on for all the other extended operators - } - final case class Equals[From](sortKey: SortKey[From], value: AttributeValue) extends SortKeyEprn[From] - final case class GreaterThan[From](sortKey: SortKey[From], value: AttributeValue) extends ExtendedSortKeyEprn[From] + final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyEprn[From] } } @@ -162,7 +186,7 @@ object FooExample extends App { } def asPk[From](k: CompositePrimaryKeyExpr[From]): PrimaryKey = k match { - case PartitionKeyExpr.And(pk, sk) => + case CompositePrimaryKeyExpr.And(pk, sk) => (pk, sk) match { case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) @@ -172,26 +196,24 @@ object FooExample extends App { def whereKey[From](k: KeyConditionExpr[From]) = k match { // PartitionKeyExpr - case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") + case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") // CompositePrimaryKeyExpr - case PartitionKeyExpr.And(pk, sk) => println(s"pk=$pk, sk=$sk") + case CompositePrimaryKeyExpr.And(pk, sk) => println(s"pk=$pk, sk=$sk") // ExtendedCompositePrimaryKeyExpr - case PartitionKeyExpr.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") + case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") } // in low level - non type safe land - import Foo.PartitionKeyExpr._ - import Foo.SortKeyExpr._ - val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" - val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" - val x3: CompositePrimaryKeyExpr[Nothing] = x1 && x2 - val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" - val x5: ExtendedCompositePrimaryKeyExpr[Nothing] = - PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" - - val y0: PartitionKeyExpr[Any] = PartitionKey("email") === "x" - val y1: CompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" - val y2: ExtendedCompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" + // val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" + // val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" + // val x3: CompositePrimaryKeyExpr[Nothing] = x1 && x2 + // val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" + // val x5: ExtendedCompositePrimaryKeyExpr[Nothing] = + // PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" + + // val y0: PartitionKeyExpr[Any] = PartitionKey("email") === "x" + // val y1: CompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" + // val y2: ExtendedCompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" import zio.dynamodb.ProjectionExpression.$ val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" @@ -205,7 +227,7 @@ object FooExample extends App { val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" - val sk2: ExtendedSortKeyEprn[Student] = Student.subject.sortKey > "y" + val sk2: ExtendedSortKeyExpr[Student] = Student.subject.sortKey > "y" val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed val pkAndSkExtended: ExtendedCompositePrimaryKeyExpr[Student] = diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 3f9af1cb2..e7b714628 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -135,17 +135,17 @@ trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowP implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { import zio.dynamodb.Foo - def primaryKey(implicit ev: IsPrimaryKey[To]): Foo.PartitionKeyExpr.PartitionKey[From] = { + def primaryKey(implicit ev: IsPrimaryKey[To]): Foo.PartitionKey2[From] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExpr.PartitionKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } - def sortKey(implicit ev: IsPrimaryKey[To]): Foo.SortKeyExpr.SortKey[From] = { + def sortKey(implicit ev: IsPrimaryKey[To]): Foo.SortKey2[From] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExpr.SortKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.SortKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } @@ -545,14 +545,14 @@ object ProjectionExpression extends ProjectionExpressionLowPriorityImplicits0 { implicit class ProjectionExpressionSyntax[From](self: ProjectionExpression[From, Unknown]) { - def primaryKey: Foo.PartitionKeyExpr.PartitionKey[From] = + def primaryKey: Foo.PartitionKey2[From] = self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKeyExpr.PartitionKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.PartitionKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: Foo.SortKeyExpr.SortKey[From] = + def sortKey: Foo.SortKey2[From] = self match { - case ProjectionExpression.MapElement(_, key) => Foo.SortKeyExpr.SortKey[From](key) + case ProjectionExpression.MapElement(_, key) => Foo.SortKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } From b93fd5d2924a39a00a2bb65c607d8aba4870ec9d Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 9 Jul 2023 07:41:52 +0100 Subject: [PATCH 20/69] plan export of files --- dynamodb/src/main/scala/zio/Foo.scala | 50 +++++++++++++++++---------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala index 5eae252ad..cf5f39ccf 100644 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ b/dynamodb/src/main/scala/zio/Foo.scala @@ -13,6 +13,19 @@ TODO - consider collapsing 1 member sealed traits - make raw constrctors that contain Phamtom types private - expose helper methods for them + +Files +- KeyConditionExpr.scala +- PartitionKey2.scala +- SortKey2.scala +- KeyConditionExpr.scala +- PartitionKeyExpr.scala +- CompositePrimaryKeyExpr.scala +- ExtendedCompositePrimaryKeyExpr.scala +- SortKeyExpr.scala +- ExtendedSortKeyExpr.scala + + */ object Foo { @@ -66,6 +79,19 @@ object Foo { AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } } + object PartitionKeyExpr { + + final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { + self => + + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExpr.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } + + } + } // sealed trait has only one member but it is usefull as a type alias to guide the user // for instance And extends it - but having a type called And would make no sense @@ -128,22 +154,8 @@ object Foo { // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr - object PartitionKeyExpr { - - final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { - self => - - override def render: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } - - } - } - // single member sealed trait - but useful as a type alias - sealed trait SortKeyEprn[-From] { self => + sealed trait SortKeyEprn[-From] { self => def render2: AliasMapRender[String] = self match { case SortKeyExpr.Equals(sk, value) => @@ -152,6 +164,10 @@ object Foo { .map(v => s"${sk.keyName} = $v") } } + object SortKeyExpr { + final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyEprn[From] + } + // single member sealed trait - but useful as a type alias sealed trait ExtendedSortKeyExpr[-From] { self => def render2: AliasMapRender[String] = @@ -167,10 +183,6 @@ object Foo { final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] } - object SortKeyExpr { - final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyEprn[From] - } - } object FooExample extends App { From caed257dfa5a36720fd0138a4e8fddbc624313c9 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 9 Jul 2023 08:09:57 +0100 Subject: [PATCH 21/69] export classes from Foo object --- dynamodb/src/main/scala/zio/Foo.scala | 262 ------------------ .../zio/dynamodb/ProjectionExpression.scala | 17 +- .../scala/zio/dynamodb/keyConditionExpr.scala | 180 ++++++++++++ .../dynamodb/KeyConditionExprExample.scala | 79 ++++++ 4 files changed, 267 insertions(+), 271 deletions(-) delete mode 100644 dynamodb/src/main/scala/zio/Foo.scala create mode 100644 dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala create mode 100644 dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala diff --git a/dynamodb/src/main/scala/zio/Foo.scala b/dynamodb/src/main/scala/zio/Foo.scala deleted file mode 100644 index cf5f39ccf..000000000 --- a/dynamodb/src/main/scala/zio/Foo.scala +++ /dev/null @@ -1,262 +0,0 @@ -package zio.dynamodb - -import zio.schema.Schema -import zio.schema.DeriveSchema -import zio.dynamodb.PrimaryKey -import zio.dynamodb.proofs.IsPrimaryKey - -/** - * Typesafe KeyConditionExpression/primary key experiment - */ -/* -TODO -- consider collapsing 1 member sealed traits -- make raw constrctors that contain Phamtom types private - - expose helper methods for them - -Files -- KeyConditionExpr.scala -- PartitionKey2.scala -- SortKey2.scala -- KeyConditionExpr.scala -- PartitionKeyExpr.scala -- CompositePrimaryKeyExpr.scala -- ExtendedCompositePrimaryKeyExpr.scala -- SortKeyExpr.scala -- ExtendedSortKeyExpr.scala - - - */ -object Foo { - - // belongs to the package top level - final case class PartitionKey2[From](keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { - val _ = ev - PartitionKeyExpr.Equals(this, to.toAttributeValue(value)) - } - } - - final case class SortKey2[From](keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { - val _ = ev - SortKeyExpr.Equals[From](this, to.toAttributeValue(value)) - } - def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - val _ = ev - ExtendedSortKeyExpr.GreaterThan(this, to.toAttributeValue(value)) - } - // ... and so on for all the other extended operators - } - - sealed trait KeyConditionExpr[-From] extends Renderable { self => - def render: AliasMapRender[String] - } - - // models primary key expressions - // email.primaryKey === "x" - // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => - def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExpr[From1] = - CompositePrimaryKeyExpr.And[From1](self, other) - def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = - ExtendedCompositePrimaryKeyExpr.ComplexAnd[From1](self, other) - - def render2: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } - - def asAttrMap: AttrMap = - self match { - case PartitionKeyExpr.Equals(pk, value) => AttrMap(pk.keyName -> value) - } - - def render: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } - } - object PartitionKeyExpr { - - final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { - self => - - override def render: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } - - } - } - - // sealed trait has only one member but it is usefull as a type alias to guide the user - // for instance And extends it - but having a type called And would make no sense - sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => - def asAttrVal: PrimaryKey = - self match { - case CompositePrimaryKeyExpr.And(pk, sk) => - (pk, sk) match { - case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => - PrimaryKey(pk.keyName -> value, sk.keyName -> value2) - } - } - - } - - object CompositePrimaryKeyExpr { - // TODO move to companion object - final case class And[From](pk: PartitionKeyExpr[From], sk: SortKeyEprn[From]) - extends CompositePrimaryKeyExpr[From] { - self => - - override def render: AliasMapRender[String] = - self match { - case CompositePrimaryKeyExpr.And(pk, sk) => - for { - pkStr <- pk.render2 - skStr <- sk.render2 - } yield s"$pkStr AND $skStr" - } - - // do render in concrete classes - - } - - } - - // sealed trait has only one member but it is usefull as a type alias to guide the user - // models "extended" primary key expressions - // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } - - object ExtendedCompositePrimaryKeyExpr { - // TODO move ComplexAnd here - // TODO move to companion object - final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) - extends ExtendedCompositePrimaryKeyExpr[From] { self => - - override def render: AliasMapRender[String] = - self match { - case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => - for { - pkStr <- pk.render2 - skStr <- sk.render2 - } yield s"$pkStr AND $skStr" - } - - } - - } - - // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr - - // single member sealed trait - but useful as a type alias - sealed trait SortKeyEprn[-From] { self => - def render2: AliasMapRender[String] = - self match { - case SortKeyExpr.Equals(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} = $v") - } - } - object SortKeyExpr { - final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyEprn[From] - } - - // single member sealed trait - but useful as a type alias - sealed trait ExtendedSortKeyExpr[-From] { self => - def render2: AliasMapRender[String] = - self match { - case ExtendedSortKeyExpr.GreaterThan(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} > $v") - } - - } - object ExtendedSortKeyExpr { - final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] - } - -} - -object FooExample extends App { - import Foo._ - - // DynamoDbQuery's still use PrimaryKey - // typesafe API constructors only expose PartitionKeyEprn - - // TODO: move to PartitionKeyExpr - def asPk[From](k: PartitionKeyExpr[From]): PrimaryKey = - k match { - case PartitionKeyExpr.Equals(pk, value) => PrimaryKey(pk.keyName -> value) - } - def asPk[From](k: CompositePrimaryKeyExpr[From]): PrimaryKey = - k match { - case CompositePrimaryKeyExpr.And(pk, sk) => - (pk, sk) match { - case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => - PrimaryKey(pk.keyName -> value, sk.keyName -> value2) - } - } - - def whereKey[From](k: KeyConditionExpr[From]) = - k match { - // PartitionKeyExpr - case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") - // CompositePrimaryKeyExpr - case CompositePrimaryKeyExpr.And(pk, sk) => println(s"pk=$pk, sk=$sk") - // ExtendedCompositePrimaryKeyExpr - case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") - } - - // in low level - non type safe land - // val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" - // val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" - // val x3: CompositePrimaryKeyExpr[Nothing] = x1 && x2 - // val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" - // val x5: ExtendedCompositePrimaryKeyExpr[Nothing] = - // PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" - - // val y0: PartitionKeyExpr[Any] = PartitionKey("email") === "x" - // val y1: CompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" - // val y2: ExtendedCompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" - - import zio.dynamodb.ProjectionExpression.$ - val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" - val x7: ExtendedCompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" - - final case class Student(email: String, subject: String, age: Int) - object Student { - implicit val schema: Schema.CaseClass3[String, String, Int, Student] = DeriveSchema.gen[Student] - val (email, subject, age) = ProjectionExpression.accessors[Student] - } - - val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" - val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" - val sk2: ExtendedSortKeyExpr[Student] = Student.subject.sortKey > "y" - val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended: ExtendedCompositePrimaryKeyExpr[Student] = - Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - - // GetItem Query will have three overridden versions - // 1) takes AttrMap/PriamaryKey - for users of low level API - // 2) takes PartitionKeyExpr - internally this can be converted to AttrMap/PriamaryKey - // 3) takes CompositePrimaryKeyExpr - internally this can b converted to AttrMap/PriamaryKey - - // whereKey function (for Query) - // 1) takes a KeyConditionExpression - // 2) down side is that users of low level API will have to construct case class instances manually - but they have to do that now anyway - // println(asPk(pkAndSk)) - // println(pkAndSkExtended) - - // Render requirements - val (aliasMap, s) = pkAndSkExtended.render.execute - println(s"aliasMap=$aliasMap, s=$s") -} diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index e7b714628..279fd5d8c 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -133,19 +133,18 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { - import zio.dynamodb.Foo - def primaryKey(implicit ev: IsPrimaryKey[To]): Foo.PartitionKey2[From] = { + def primaryKey(implicit ev: IsPrimaryKey[To]): PartitionKey2[From] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKey2[From](key) + case ProjectionExpression.MapElement(_, key) => PartitionKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } - def sortKey(implicit ev: IsPrimaryKey[To]): Foo.SortKey2[From] = { + def sortKey(implicit ev: IsPrimaryKey[To]): SortKey2[From] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => Foo.SortKey2[From](key) + case ProjectionExpression.MapElement(_, key) => SortKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } @@ -545,14 +544,14 @@ object ProjectionExpression extends ProjectionExpressionLowPriorityImplicits0 { implicit class ProjectionExpressionSyntax[From](self: ProjectionExpression[From, Unknown]) { - def primaryKey: Foo.PartitionKey2[From] = + def primaryKey: PartitionKey2[From] = self match { - case ProjectionExpression.MapElement(_, key) => Foo.PartitionKey2[From](key) + case ProjectionExpression.MapElement(_, key) => PartitionKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: Foo.SortKey2[From] = + def sortKey: SortKey2[From] = self match { - case ProjectionExpression.MapElement(_, key) => Foo.SortKey2[From](key) + case ProjectionExpression.MapElement(_, key) => SortKey2[From](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala new file mode 100644 index 000000000..21c762448 --- /dev/null +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -0,0 +1,180 @@ +package zio.dynamodb + +import zio.dynamodb.PrimaryKey +import zio.dynamodb.proofs.IsPrimaryKey + +/** + * Typesafe KeyConditionExpression/primary key experiment + */ +/* +TODO +- consider collapsing 1 member sealed traits +- make raw constrctors that contain Phamtom types private + - expose helper methods for them + +Files +- KeyConditionExpr.scala +- PartitionKey2.scala +- SortKey2.scala +- KeyConditionExpr.scala +- PartitionKeyExpr.scala +- CompositePrimaryKeyExpr.scala +- ExtendedCompositePrimaryKeyExpr.scala +- SortKeyExpr.scala +- ExtendedSortKeyExpr.scala + + + */ + +// belongs to the package top level +final case class PartitionKey2[From](keyName: String) { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { + val _ = ev + PartitionKeyExpr.Equals(this, to.toAttributeValue(value)) + } +} + +final case class SortKey2[From](keyName: String) { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { + val _ = ev + SortKeyExpr.Equals[From](this, to.toAttributeValue(value)) + } + def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + val _ = ev + ExtendedSortKeyExpr.GreaterThan(this, to.toAttributeValue(value)) + } + // ... and so on for all the other extended operators +} + +sealed trait KeyConditionExpr[-From] extends Renderable { self => + def render: AliasMapRender[String] +} + +// models primary key expressions +// email.primaryKey === "x" +// Student.email.primaryKey === "x" && Student.subject.sortKey === "y" +sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => + def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExpr[From1] = + CompositePrimaryKeyExpr.And[From1](self, other) + def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = + ExtendedCompositePrimaryKeyExpr.ComplexAnd[From1](self, other) + + def render2: AliasMapRender[String] = + self match { + case PartitionKeyExpr.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } + + def asAttrMap: AttrMap = + self match { + case PartitionKeyExpr.Equals(pk, value) => AttrMap(pk.keyName -> value) + } + + def render: AliasMapRender[String] = + self match { + case PartitionKeyExpr.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } +} +object PartitionKeyExpr { + + final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { + self => + + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExpr.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } + + } +} + +// sealed trait has only one member but it is usefull as a type alias to guide the user +// for instance And extends it - but having a type called And would make no sense +sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => + def asAttrVal: PrimaryKey = + self match { + case CompositePrimaryKeyExpr.And(pk, sk) => + (pk, sk) match { + case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => + PrimaryKey(pk.keyName -> value, sk.keyName -> value2) + } + } + +} + +object CompositePrimaryKeyExpr { + // TODO move to companion object + final case class And[From](pk: PartitionKeyExpr[From], sk: SortKeyEprn[From]) extends CompositePrimaryKeyExpr[From] { + self => + + override def render: AliasMapRender[String] = + self match { + case CompositePrimaryKeyExpr.And(pk, sk) => + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" + } + + // do render in concrete classes + + } + +} + +// sealed trait has only one member but it is usefull as a type alias to guide the user +// models "extended" primary key expressions +// Student.email.primaryKey === "x" && Student.subject.sortKey > "y" +sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } + +object ExtendedCompositePrimaryKeyExpr { + // TODO move ComplexAnd here + // TODO move to companion object + final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) + extends ExtendedCompositePrimaryKeyExpr[From] { self => + + override def render: AliasMapRender[String] = + self match { + case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" + } + + } + +} + +// no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr + +// single member sealed trait - but useful as a type alias +sealed trait SortKeyEprn[-From] { self => + def render2: AliasMapRender[String] = + self match { + case SortKeyExpr.Equals(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} = $v") + } +} +object SortKeyExpr { + final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyEprn[From] +} + +// single member sealed trait - but useful as a type alias +sealed trait ExtendedSortKeyExpr[-From] { self => + def render2: AliasMapRender[String] = + self match { + case ExtendedSortKeyExpr.GreaterThan(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} > $v") + } + +} +object ExtendedSortKeyExpr { + final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] +} diff --git a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala b/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala new file mode 100644 index 000000000..5c0602352 --- /dev/null +++ b/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala @@ -0,0 +1,79 @@ +package zio.dynamodb + +import zio.schema.Schema +import zio.schema.DeriveSchema + +object KeyConditionExprExample extends App { + + // DynamoDbQuery's still use PrimaryKey + // typesafe API constructors only expose PartitionKeyEprn + + // TODO: move to PartitionKeyExpr + def asPk[From](k: PartitionKeyExpr[From]): PrimaryKey = + k match { + case PartitionKeyExpr.Equals(pk, value) => PrimaryKey(pk.keyName -> value) + } + def asPk[From](k: CompositePrimaryKeyExpr[From]): PrimaryKey = + k match { + case CompositePrimaryKeyExpr.And(pk, sk) => + (pk, sk) match { + case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => + PrimaryKey(pk.keyName -> value, sk.keyName -> value2) + } + } + + def whereKey[From](k: KeyConditionExpr[From]) = + k match { + // PartitionKeyExpr + case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") + // CompositePrimaryKeyExpr + case CompositePrimaryKeyExpr.And(pk, sk) => println(s"pk=$pk, sk=$sk") + // ExtendedCompositePrimaryKeyExpr + case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") + } + + // in low level - non type safe land + // val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" + // val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" + // val x3: CompositePrimaryKeyExpr[Nothing] = x1 && x2 + // val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" + // val x5: ExtendedCompositePrimaryKeyExpr[Nothing] = + // PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" + + // val y0: PartitionKeyExpr[Any] = PartitionKey("email") === "x" + // val y1: CompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" + // val y2: ExtendedCompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" + + import zio.dynamodb.ProjectionExpression.$ + val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" + val x7: ExtendedCompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" + + final case class Student(email: String, subject: String, age: Int) + object Student { + implicit val schema: Schema.CaseClass3[String, String, Int, Student] = DeriveSchema.gen[Student] + val (email, subject, age) = ProjectionExpression.accessors[Student] + } + + val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" + val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" + val sk2: ExtendedSortKeyExpr[Student] = Student.subject.sortKey > "y" + val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed + val pkAndSkExtended: ExtendedCompositePrimaryKeyExpr[Student] = + Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + + // GetItem Query will have three overridden versions + // 1) takes AttrMap/PriamaryKey - for users of low level API + // 2) takes PartitionKeyExpr - internally this can be converted to AttrMap/PriamaryKey + // 3) takes CompositePrimaryKeyExpr - internally this can b converted to AttrMap/PriamaryKey + + // whereKey function (for Query) + // 1) takes a KeyConditionExpression + // 2) down side is that users of low level API will have to construct case class instances manually - but they have to do that now anyway + // println(asPk(pkAndSk)) + // println(pkAndSkExtended) + + // Render requirements + val (aliasMap, s) = pkAndSkExtended.render.execute + println(s"aliasMap=$aliasMap, s=$s") +} From fea04137bf270899b6ff7dded64f73477ac1f3f6 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 9 Jul 2023 08:28:14 +0100 Subject: [PATCH 22/69] KeyConditionExpr companion object --- .../scala/zio/dynamodb/keyConditionExpr.scala | 197 +++++++++--------- .../dynamodb/KeyConditionExprExample.scala | 16 +- 2 files changed, 118 insertions(+), 95 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 21c762448..c845b2797 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -26,6 +26,8 @@ Files */ +import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr + // belongs to the package top level final case class PartitionKey2[From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { @@ -34,8 +36,11 @@ final case class PartitionKey2[From](keyName: String) { } } +import zio.dynamodb.KeyConditionExpr.SortKeyExpr +import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr + final case class SortKey2[From](keyName: String) { - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyEprn[From] = { + def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyExpr[From] = { val _ = ev SortKeyExpr.Equals[From](this, to.toAttributeValue(value)) } @@ -50,131 +55,135 @@ sealed trait KeyConditionExpr[-From] extends Renderable { self => def render: AliasMapRender[String] } +object KeyConditionExpr { // models primary key expressions // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" -sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => - def &&[From1 <: From](other: SortKeyEprn[From1]): CompositePrimaryKeyExpr[From1] = - CompositePrimaryKeyExpr.And[From1](self, other) - def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = - ExtendedCompositePrimaryKeyExpr.ComplexAnd[From1](self, other) - - def render2: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } + sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => + def &&[From1 <: From](other: SortKeyExpr[From1]): CompositePrimaryKeyExpr[From1] = + CompositePrimaryKeyExpr.And[From1](self, other) + def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = + ExtendedCompositePrimaryKeyExpr.ComplexAnd[From1](self, other) - def asAttrMap: AttrMap = - self match { - case PartitionKeyExpr.Equals(pk, value) => AttrMap(pk.keyName -> value) - } - - def render: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } -} -object PartitionKeyExpr { + def render2: AliasMapRender[String] = + self match { + case PartitionKeyExpr.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } - final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { - self => + def asAttrMap: AttrMap = + self match { + case PartitionKeyExpr.Equals(pk, value) => AttrMap(pk.keyName -> value) + } - override def render: AliasMapRender[String] = + def render: AliasMapRender[String] = self match { case PartitionKeyExpr.Equals(pk, value) => AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } - } -} + object PartitionKeyExpr { -// sealed trait has only one member but it is usefull as a type alias to guide the user -// for instance And extends it - but having a type called And would make no sense -sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => - def asAttrVal: PrimaryKey = - self match { - case CompositePrimaryKeyExpr.And(pk, sk) => - (pk, sk) match { - case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => - PrimaryKey(pk.keyName -> value, sk.keyName -> value2) - } - } + final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { + self => -} + override def render: AliasMapRender[String] = + self match { + case PartitionKeyExpr.Equals(pk, value) => + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + } -object CompositePrimaryKeyExpr { - // TODO move to companion object - final case class And[From](pk: PartitionKeyExpr[From], sk: SortKeyEprn[From]) extends CompositePrimaryKeyExpr[From] { - self => + } + } - override def render: AliasMapRender[String] = +// sealed trait has only one member but it is usefull as a type alias to guide the user +// for instance And extends it - but having a type called And would make no sense + sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => + def asAttrVal: PrimaryKey = self match { case CompositePrimaryKeyExpr.And(pk, sk) => - for { - pkStr <- pk.render2 - skStr <- sk.render2 - } yield s"$pkStr AND $skStr" + (pk, sk) match { + case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => + PrimaryKey(pk.keyName -> value, sk.keyName -> value2) + } } - // do render in concrete classes - } -} + object CompositePrimaryKeyExpr { + // TODO move to companion object + final case class And[From](pk: PartitionKeyExpr[From], sk: SortKeyExpr[From]) + extends CompositePrimaryKeyExpr[From] { + self => + + override def render: AliasMapRender[String] = + self match { + case CompositePrimaryKeyExpr.And(pk, sk) => + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" + } + + // do render in concrete classes + + } + + } // sealed trait has only one member but it is usefull as a type alias to guide the user // models "extended" primary key expressions // Student.email.primaryKey === "x" && Student.subject.sortKey > "y" -sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } - -object ExtendedCompositePrimaryKeyExpr { - // TODO move ComplexAnd here - // TODO move to companion object - final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) - extends ExtendedCompositePrimaryKeyExpr[From] { self => + sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } + + object ExtendedCompositePrimaryKeyExpr { + // TODO move ComplexAnd here + // TODO move to companion object + final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) + extends ExtendedCompositePrimaryKeyExpr[From] { self => + + override def render: AliasMapRender[String] = + self match { + case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" + } - override def render: AliasMapRender[String] = - self match { - case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => - for { - pkStr <- pk.render2 - skStr <- sk.render2 - } yield s"$pkStr AND $skStr" - } + } } -} - // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr // single member sealed trait - but useful as a type alias -sealed trait SortKeyEprn[-From] { self => - def render2: AliasMapRender[String] = - self match { - case SortKeyExpr.Equals(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} = $v") - } -} -object SortKeyExpr { - final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyEprn[From] -} + sealed trait SortKeyExpr[-From] { self => + def render2: AliasMapRender[String] = + self match { + case SortKeyExpr.Equals(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} = $v") + } + } + object SortKeyExpr { + final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyExpr[From] + } -// single member sealed trait - but useful as a type alias -sealed trait ExtendedSortKeyExpr[-From] { self => - def render2: AliasMapRender[String] = - self match { - case ExtendedSortKeyExpr.GreaterThan(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} > $v") - } + // single member sealed trait - but useful as a type alias + sealed trait ExtendedSortKeyExpr[-From] { self => + def render2: AliasMapRender[String] = + self match { + case ExtendedSortKeyExpr.GreaterThan(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} > $v") + } + + } + object ExtendedSortKeyExpr { + final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] + } -} -object ExtendedSortKeyExpr { - final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] } diff --git a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala b/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala index 5c0602352..82050b6ee 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala @@ -5,6 +5,9 @@ import zio.schema.DeriveSchema object KeyConditionExprExample extends App { + import zio.dynamodb.KeyConditionExpr._ + import zio.dynamodb.KeyConditionExpr.SortKeyExpr + // DynamoDbQuery's still use PrimaryKey // typesafe API constructors only expose PartitionKeyEprn @@ -55,7 +58,7 @@ object KeyConditionExprExample extends App { } val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" - val sk1: SortKeyEprn[Student] = Student.subject.sortKey === "y" + val sk1: SortKeyExpr[Student] = Student.subject.sortKey === "y" val sk2: ExtendedSortKeyExpr[Student] = Student.subject.sortKey > "y" val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed @@ -77,3 +80,14 @@ object KeyConditionExprExample extends App { val (aliasMap, s) = pkAndSkExtended.render.execute println(s"aliasMap=$aliasMap, s=$s") } + +object Foo { + final case class Student(email: String, subject: String, age: Int) + object Student { + implicit val schema: Schema.CaseClass3[String, String, Int, Student] = DeriveSchema.gen[Student] + val (email, subject, age) = ProjectionExpression.accessors[Student] + } + + val pkAndSk: KeyConditionExpr.CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + +} From 307b65565191d2ea4c814065bb1bd9d844ef6d32 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 9 Jul 2023 09:45:10 +0100 Subject: [PATCH 23/69] Merged single member sum types --- .../scala/zio/dynamodb/keyConditionExpr.scala | 108 ++++++------------ .../dynamodb/KeyConditionExprExample.scala | 20 ++-- 2 files changed, 49 insertions(+), 79 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index c845b2797..5508841ce 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -22,8 +22,6 @@ Files - ExtendedCompositePrimaryKeyExpr.scala - SortKeyExpr.scala - ExtendedSortKeyExpr.scala - - */ import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr @@ -42,7 +40,7 @@ import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr final case class SortKey2[From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyExpr[From] = { val _ = ev - SortKeyExpr.Equals[From](this, to.toAttributeValue(value)) + SortKeyExpr[From](this, to.toAttributeValue(value)) } def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { val _ = ev @@ -56,14 +54,14 @@ sealed trait KeyConditionExpr[-From] extends Renderable { self => } object KeyConditionExpr { -// models primary key expressions -// email.primaryKey === "x" -// Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + // models primary key expressions + // email.primaryKey === "x" + // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => def &&[From1 <: From](other: SortKeyExpr[From1]): CompositePrimaryKeyExpr[From1] = - CompositePrimaryKeyExpr.And[From1](self, other) + CompositePrimaryKeyExpr[From1](self, other) def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = - ExtendedCompositePrimaryKeyExpr.ComplexAnd[From1](self, other) + ExtendedCompositePrimaryKeyExpr[From1](self, other) def render2: AliasMapRender[String] = self match { @@ -96,82 +94,47 @@ object KeyConditionExpr { } } -// sealed trait has only one member but it is usefull as a type alias to guide the user -// for instance And extends it - but having a type called And would make no sense - sealed trait CompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => + final case class SortKeyExpr[From](sortKey: SortKey2[From], value: AttributeValue) { self => + def render2: AliasMapRender[String] = + AliasMapRender + .getOrInsert(value) + .map(v => s"${sortKey.keyName} = $v") + } + + final case class CompositePrimaryKeyExpr[From](pk: PartitionKeyExpr[From], sk: SortKeyExpr[From]) + extends KeyConditionExpr[From] { + self => + def asAttrVal: PrimaryKey = - self match { - case CompositePrimaryKeyExpr.And(pk, sk) => + self match { // TODO: delete match + case CompositePrimaryKeyExpr(pk, sk) => (pk, sk) match { - case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => + case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) } } - } - - object CompositePrimaryKeyExpr { - // TODO move to companion object - final case class And[From](pk: PartitionKeyExpr[From], sk: SortKeyExpr[From]) - extends CompositePrimaryKeyExpr[From] { - self => - - override def render: AliasMapRender[String] = - self match { - case CompositePrimaryKeyExpr.And(pk, sk) => - for { - pkStr <- pk.render2 - skStr <- sk.render2 - } yield s"$pkStr AND $skStr" - } - - // do render in concrete classes - - } - - } - -// sealed trait has only one member but it is usefull as a type alias to guide the user -// models "extended" primary key expressions -// Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - sealed trait ExtendedCompositePrimaryKeyExpr[-From] extends KeyConditionExpr[From] { self => } - - object ExtendedCompositePrimaryKeyExpr { - // TODO move ComplexAnd here - // TODO move to companion object - final case class ComplexAnd[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) - extends ExtendedCompositePrimaryKeyExpr[From] { self => - - override def render: AliasMapRender[String] = - self match { - case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => - for { - pkStr <- pk.render2 - skStr <- sk.render2 - } yield s"$pkStr AND $skStr" - } - - } + override def render: AliasMapRender[String] = + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" } + final case class ExtendedCompositePrimaryKeyExpr[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) + extends KeyConditionExpr[From] { + self => -// no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr + def render: AliasMapRender[String] = + for { + pkStr <- pk.render2 + skStr <- sk.render2 + } yield s"$pkStr AND $skStr" -// single member sealed trait - but useful as a type alias - sealed trait SortKeyExpr[-From] { self => - def render2: AliasMapRender[String] = - self match { - case SortKeyExpr.Equals(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} = $v") - } - } - object SortKeyExpr { - final case class Equals[From](sortKey: SortKey2[From], value: AttributeValue) extends SortKeyExpr[From] } + // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr - // single member sealed trait - but useful as a type alias + // single member sealed trait ATM but will have more members sealed trait ExtendedSortKeyExpr[-From] { self => def render2: AliasMapRender[String] = self match { @@ -184,6 +147,7 @@ object KeyConditionExpr { } object ExtendedSortKeyExpr { final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] + // TODO add other extended operators } } diff --git a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala b/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala index 82050b6ee..774c13ffe 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala @@ -18,9 +18,9 @@ object KeyConditionExprExample extends App { } def asPk[From](k: CompositePrimaryKeyExpr[From]): PrimaryKey = k match { - case CompositePrimaryKeyExpr.And(pk, sk) => + case CompositePrimaryKeyExpr(pk, sk) => (pk, sk) match { - case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr.Equals(sk, value2)) => + case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) } } @@ -28,11 +28,11 @@ object KeyConditionExprExample extends App { def whereKey[From](k: KeyConditionExpr[From]) = k match { // PartitionKeyExpr - case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") + case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") // CompositePrimaryKeyExpr - case CompositePrimaryKeyExpr.And(pk, sk) => println(s"pk=$pk, sk=$sk") + case CompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") // ExtendedCompositePrimaryKeyExpr - case ExtendedCompositePrimaryKeyExpr.ComplexAnd(pk, sk) => println(s"pk=$pk, sk=$sk") + case ExtendedCompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") } // in low level - non type safe land @@ -51,8 +51,13 @@ object KeyConditionExprExample extends App { val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" val x7: ExtendedCompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" + final case class Elephant(email: String, subject: String, age: Int) + object Elephant { + implicit val schema: Schema.CaseClass3[String, String, Int, Elephant] = DeriveSchema.gen[Elephant] + val (email, subject, age) = ProjectionExpression.accessors[Elephant] + } final case class Student(email: String, subject: String, age: Int) - object Student { + object Student { implicit val schema: Schema.CaseClass3[String, String, Int, Student] = DeriveSchema.gen[Student] val (email, subject, age) = ProjectionExpression.accessors[Student] } @@ -88,6 +93,7 @@ object Foo { val (email, subject, age) = ProjectionExpression.accessors[Student] } - val pkAndSk: KeyConditionExpr.CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pkAndSk: KeyConditionExpr.CompositePrimaryKeyExpr[Student] = + Student.email.primaryKey === "x" && Student.subject.sortKey === "y" } From a6c6b0014a8a4090e46c1cfd0b0257d3494f0017 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Mon, 10 Jul 2023 08:12:09 +0100 Subject: [PATCH 24/69] make concrete classes package private --- .../scala/zio/dynamodb/keyConditionExpr.scala | 23 +++++++++++-------- .../examples}/KeyConditionExprExample.scala | 6 ++++- 2 files changed, 18 insertions(+), 11 deletions(-) rename {dynamodb/src/test/scala/zio/dynamodb => examples/src/main/scala/zio/dynamodb/examples}/KeyConditionExprExample.scala (97%) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 5508841ce..89fdd7238 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -27,7 +27,7 @@ Files import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr // belongs to the package top level -final case class PartitionKey2[From](keyName: String) { +private[dynamodb] final case class PartitionKey2[From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { val _ = ev PartitionKeyExpr.Equals(this, to.toAttributeValue(value)) @@ -37,7 +37,7 @@ final case class PartitionKey2[From](keyName: String) { import zio.dynamodb.KeyConditionExpr.SortKeyExpr import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr -final case class SortKey2[From](keyName: String) { +private[dynamodb] final case class SortKey2[From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyExpr[From] = { val _ = ev SortKeyExpr[From](this, to.toAttributeValue(value)) @@ -82,7 +82,8 @@ object KeyConditionExpr { } object PartitionKeyExpr { - final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) extends PartitionKeyExpr[From] { + private[dynamodb] final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) + extends PartitionKeyExpr[From] { self => override def render: AliasMapRender[String] = @@ -94,18 +95,18 @@ object KeyConditionExpr { } } - final case class SortKeyExpr[From](sortKey: SortKey2[From], value: AttributeValue) { self => + private[dynamodb] final case class SortKeyExpr[From](sortKey: SortKey2[From], value: AttributeValue) { self => def render2: AliasMapRender[String] = AliasMapRender .getOrInsert(value) .map(v => s"${sortKey.keyName} = $v") } - final case class CompositePrimaryKeyExpr[From](pk: PartitionKeyExpr[From], sk: SortKeyExpr[From]) + private[dynamodb] final case class CompositePrimaryKeyExpr[From](pk: PartitionKeyExpr[From], sk: SortKeyExpr[From]) extends KeyConditionExpr[From] { self => - def asAttrVal: PrimaryKey = + def asAttrVal: AttrMap = self match { // TODO: delete match case CompositePrimaryKeyExpr(pk, sk) => (pk, sk) match { @@ -121,8 +122,10 @@ object KeyConditionExpr { } yield s"$pkStr AND $skStr" } - final case class ExtendedCompositePrimaryKeyExpr[-From](pk: PartitionKeyExpr[From], sk: ExtendedSortKeyExpr[From]) - extends KeyConditionExpr[From] { + private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From]( + pk: PartitionKeyExpr[From], + sk: ExtendedSortKeyExpr[From] + ) extends KeyConditionExpr[From] { self => def render: AliasMapRender[String] = @@ -132,7 +135,6 @@ object KeyConditionExpr { } yield s"$pkStr AND $skStr" } - // no overlap between PartitionKeyExpr and ExtendedPartitionKeyExpr // single member sealed trait ATM but will have more members sealed trait ExtendedSortKeyExpr[-From] { self => @@ -146,7 +148,8 @@ object KeyConditionExpr { } object ExtendedSortKeyExpr { - final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] + private[dynamodb] final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) + extends ExtendedSortKeyExpr[From] // TODO add other extended operators } diff --git a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala similarity index 97% rename from dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala rename to examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index 774c13ffe..cc28b5673 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -1,4 +1,8 @@ -package zio.dynamodb +package zio.dynamodb.examples + +import zio.dynamodb.PrimaryKey +import zio.dynamodb.KeyConditionExpr +import zio.dynamodb.ProjectionExpression import zio.schema.Schema import zio.schema.DeriveSchema From 03a49c1bf4903ef7228886b67dc805e568aa3cc4 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Tue, 11 Jul 2023 07:16:05 +0100 Subject: [PATCH 25/69] collapsed PartitionKeyExpr trait --- .../scala/zio/dynamodb/keyConditionExpr.scala | 47 +++++-------------- .../examples/KeyConditionExprExample.scala | 6 +-- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 89fdd7238..f27c54d4b 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -27,10 +27,10 @@ Files import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr // belongs to the package top level -private[dynamodb] final case class PartitionKey2[From](keyName: String) { +private[dynamodb] final case class PartitionKey2[-From](keyName: String) { def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { val _ = ev - PartitionKeyExpr.Equals(this, to.toAttributeValue(value)) + PartitionKeyExpr(this, to.toAttributeValue(value)) } } @@ -57,42 +57,19 @@ object KeyConditionExpr { // models primary key expressions // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - sealed trait PartitionKeyExpr[-From] extends KeyConditionExpr[From] { self => + + private[dynamodb] final case class PartitionKeyExpr[-From](pk: PartitionKey2[From], value: AttributeValue) + extends KeyConditionExpr[From] { self => + def &&[From1 <: From](other: SortKeyExpr[From1]): CompositePrimaryKeyExpr[From1] = CompositePrimaryKeyExpr[From1](self, other) def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = ExtendedCompositePrimaryKeyExpr[From1](self, other) - def render2: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } - - def asAttrMap: AttrMap = - self match { - case PartitionKeyExpr.Equals(pk, value) => AttrMap(pk.keyName -> value) - } - - def render: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } - } - object PartitionKeyExpr { - - private[dynamodb] final case class Equals[From](pk: PartitionKey2[From], value: AttributeValue) - extends PartitionKeyExpr[From] { - self => + def asAttrMap: AttrMap = AttrMap(pk.keyName -> value) - override def render: AliasMapRender[String] = - self match { - case PartitionKeyExpr.Equals(pk, value) => - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") - } - - } + override def render: AliasMapRender[String] = + AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } private[dynamodb] final case class SortKeyExpr[From](sortKey: SortKey2[From], value: AttributeValue) { self => @@ -110,14 +87,14 @@ object KeyConditionExpr { self match { // TODO: delete match case CompositePrimaryKeyExpr(pk, sk) => (pk, sk) match { - case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr(sk, value2)) => + case (PartitionKeyExpr(pk, value), SortKeyExpr(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) } } override def render: AliasMapRender[String] = for { - pkStr <- pk.render2 + pkStr <- pk.render skStr <- sk.render2 } yield s"$pkStr AND $skStr" @@ -130,7 +107,7 @@ object KeyConditionExpr { def render: AliasMapRender[String] = for { - pkStr <- pk.render2 + pkStr <- pk.render skStr <- sk.render2 } yield s"$pkStr AND $skStr" diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index cc28b5673..73d68fe5e 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -18,13 +18,13 @@ object KeyConditionExprExample extends App { // TODO: move to PartitionKeyExpr def asPk[From](k: PartitionKeyExpr[From]): PrimaryKey = k match { - case PartitionKeyExpr.Equals(pk, value) => PrimaryKey(pk.keyName -> value) + case PartitionKeyExpr(pk, value) => PrimaryKey(pk.keyName -> value) } def asPk[From](k: CompositePrimaryKeyExpr[From]): PrimaryKey = k match { case CompositePrimaryKeyExpr(pk, sk) => (pk, sk) match { - case (PartitionKeyExpr.Equals(pk, value), SortKeyExpr(sk, value2)) => + case (PartitionKeyExpr(pk, value), SortKeyExpr(sk, value2)) => PrimaryKey(pk.keyName -> value, sk.keyName -> value2) } } @@ -32,7 +32,7 @@ object KeyConditionExprExample extends App { def whereKey[From](k: KeyConditionExpr[From]) = k match { // PartitionKeyExpr - case PartitionKeyExpr.Equals(pk, value) => println(s"pk=$pk, value=$value") + case PartitionKeyExpr(pk, value) => println(s"pk=$pk, value=$value") // CompositePrimaryKeyExpr case CompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") // ExtendedCompositePrimaryKeyExpr From 163102557d4972f8a5d9ff24841e9161b451c6cd Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Tue, 11 Jul 2023 07:44:55 +0100 Subject: [PATCH 26/69] collapse pattern match --- .../scala/zio/dynamodb/keyConditionExpr.scala | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index f27c54d4b..ae67f7b3a 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -13,10 +13,10 @@ TODO - expose helper methods for them Files -- KeyConditionExpr.scala - PartitionKey2.scala - SortKey2.scala - KeyConditionExpr.scala +- KeyConditionExpr.scala - PartitionKeyExpr.scala - CompositePrimaryKeyExpr.scala - ExtendedCompositePrimaryKeyExpr.scala @@ -83,14 +83,7 @@ object KeyConditionExpr { extends KeyConditionExpr[From] { self => - def asAttrVal: AttrMap = - self match { // TODO: delete match - case CompositePrimaryKeyExpr(pk, sk) => - (pk, sk) match { - case (PartitionKeyExpr(pk, value), SortKeyExpr(sk, value2)) => - PrimaryKey(pk.keyName -> value, sk.keyName -> value2) - } - } + def asAttrVal: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) override def render: AliasMapRender[String] = for { @@ -113,6 +106,21 @@ object KeyConditionExpr { } + /* + TODO: add all: + def <>[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = NotEqual(self, t.toAttributeValue(that)) + def <[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = LessThan(self, t.toAttributeValue(that)) + def <=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = + LessThanOrEqual(self, t.toAttributeValue(that)) + def >[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = + GreaterThanOrEqual(self, t.toAttributeValue(that)) + def >=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = + GreaterThanOrEqual(self, t.toAttributeValue(that)) + def between[A](min: A, max: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = + Between(self, t.toAttributeValue(min), t.toAttributeValue(max)) + def beginsWith[A](value: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = + BeginsWith(self, t.toAttributeValue(value)) + */ // single member sealed trait ATM but will have more members sealed trait ExtendedSortKeyExpr[-From] { self => def render2: AliasMapRender[String] = From f70d960657241bf4641d070a86b4e402e5bd2b08 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Tue, 11 Jul 2023 07:46:59 +0100 Subject: [PATCH 27/69] rename --- dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index ae67f7b3a..e1be0b48e 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -83,7 +83,7 @@ object KeyConditionExpr { extends KeyConditionExpr[From] { self => - def asAttrVal: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) + def asAttrMap: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) override def render: AliasMapRender[String] = for { From 78a7a6e986ac3f189714e340dcbd3c533a47daa8 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Wed, 12 Jul 2023 06:14:15 +0100 Subject: [PATCH 28/69] added less than --- .../main/scala/zio/dynamodb/keyConditionExpr.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index e1be0b48e..e652c8155 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -46,6 +46,10 @@ private[dynamodb] final case class SortKey2[From](keyName: String) { val _ = ev ExtendedSortKeyExpr.GreaterThan(this, to.toAttributeValue(value)) } + def <[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + val _ = ev + ExtendedSortKeyExpr.LessThan(this, to.toAttributeValue(value)) + } // ... and so on for all the other extended operators } @@ -121,7 +125,6 @@ object KeyConditionExpr { def beginsWith[A](value: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = BeginsWith(self, t.toAttributeValue(value)) */ - // single member sealed trait ATM but will have more members sealed trait ExtendedSortKeyExpr[-From] { self => def render2: AliasMapRender[String] = self match { @@ -129,12 +132,18 @@ object KeyConditionExpr { AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} > $v") + case ExtendedSortKeyExpr.LessThan(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} < $v") } } object ExtendedSortKeyExpr { private[dynamodb] final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] + private[dynamodb] final case class LessThan[From](sortKey: SortKey2[From], value: AttributeValue) + extends ExtendedSortKeyExpr[From] // TODO add other extended operators } From 1991c8366801ff5897cf68dda85d23a8a18eace3 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Wed, 12 Jul 2023 07:46:25 +0100 Subject: [PATCH 29/69] add remaining ops --- .../scala/zio/dynamodb/keyConditionExpr.scala | 107 +++++++++++------- .../ConditionExpressionExamples.scala | 1 + .../examples/KeyConditionExprExample.scala | 18 +-- 3 files changed, 76 insertions(+), 50 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index e652c8155..e59539948 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -4,52 +4,53 @@ import zio.dynamodb.PrimaryKey import zio.dynamodb.proofs.IsPrimaryKey /** - * Typesafe KeyConditionExpression/primary key experiment + * Typesafe KeyConditionExpr/primary key experiment + * + * TODO break out files + * - PartitionKey2.scala + * - SortKey2.scala + * - KeyConditionExpr.scala */ -/* -TODO -- consider collapsing 1 member sealed traits -- make raw constrctors that contain Phamtom types private - - expose helper methods for them - -Files -- PartitionKey2.scala -- SortKey2.scala -- KeyConditionExpr.scala -- KeyConditionExpr.scala -- PartitionKeyExpr.scala -- CompositePrimaryKeyExpr.scala -- ExtendedCompositePrimaryKeyExpr.scala -- SortKeyExpr.scala -- ExtendedSortKeyExpr.scala - */ - import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr // belongs to the package top level -private[dynamodb] final case class PartitionKey2[-From](keyName: String) { +private[dynamodb] final case class PartitionKey2[-From](keyName: String) { self => def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { val _ = ev - PartitionKeyExpr(this, to.toAttributeValue(value)) + PartitionKeyExpr(self, to.toAttributeValue(value)) } } import zio.dynamodb.KeyConditionExpr.SortKeyExpr import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr -private[dynamodb] final case class SortKey2[From](keyName: String) { +private[dynamodb] final case class SortKey2[From](keyName: String) { self => def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyExpr[From] = { val _ = ev - SortKeyExpr[From](this, to.toAttributeValue(value)) + SortKeyExpr[From](self, to.toAttributeValue(value)) } def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { val _ = ev - ExtendedSortKeyExpr.GreaterThan(this, to.toAttributeValue(value)) + ExtendedSortKeyExpr.GreaterThan(self, to.toAttributeValue(value)) } def <[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { val _ = ev - ExtendedSortKeyExpr.LessThan(this, to.toAttributeValue(value)) + ExtendedSortKeyExpr.LessThan(self, to.toAttributeValue(value)) + } + def <>[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + val _ = ev + ExtendedSortKeyExpr.NotEqual(self, to.toAttributeValue(value)) + } + def <=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + val _ = ev + ExtendedSortKeyExpr.LessThanOrEqual(self, to.toAttributeValue(value)) } + def >=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + val _ = ev + ExtendedSortKeyExpr.GreaterThanOrEqual(self, to.toAttributeValue(value)) + } + def between[To](min: To, max: To)(implicit t: ToAttributeValue[To]): ExtendedSortKeyExpr[From] = + ExtendedSortKeyExpr.Between[From](self, t.toAttributeValue(min), t.toAttributeValue(max)) // ... and so on for all the other extended operators } @@ -110,32 +111,43 @@ object KeyConditionExpr { } - /* - TODO: add all: - def <>[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = NotEqual(self, t.toAttributeValue(that)) - def <[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = LessThan(self, t.toAttributeValue(that)) - def <=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - LessThanOrEqual(self, t.toAttributeValue(that)) - def >[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - GreaterThanOrEqual(self, t.toAttributeValue(that)) - def >=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - GreaterThanOrEqual(self, t.toAttributeValue(that)) - def between[A](min: A, max: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - Between(self, t.toAttributeValue(min), t.toAttributeValue(max)) - def beginsWith[A](value: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - BeginsWith(self, t.toAttributeValue(value)) - */ sealed trait ExtendedSortKeyExpr[-From] { self => def render2: AliasMapRender[String] = self match { - case ExtendedSortKeyExpr.GreaterThan(sk, value) => + case ExtendedSortKeyExpr.GreaterThan(sk, value) => AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} > $v") - case ExtendedSortKeyExpr.LessThan(sk, value) => + case ExtendedSortKeyExpr.LessThan(sk, value) => AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} < $v") + case ExtendedSortKeyExpr.NotEqual(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} <> $v") + case ExtendedSortKeyExpr.LessThanOrEqual(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} <= $v") + case ExtendedSortKeyExpr.GreaterThanOrEqual(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} >= $v") + case ExtendedSortKeyExpr.Between(left, min, max) => + AliasMapRender + .getOrInsert(min) + .flatMap(min => + AliasMapRender.getOrInsert(max).map { max => + s"${left.keyName} BETWEEN $min AND $max" + } + ) + case ExtendedSortKeyExpr.BeginsWith(left, value) => + AliasMapRender + .getOrInsert(value) + .map { v => + s"begins_with(${left.keyName}, $v)" + } } } @@ -144,7 +156,16 @@ object KeyConditionExpr { extends ExtendedSortKeyExpr[From] private[dynamodb] final case class LessThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] - // TODO add other extended operators + private[dynamodb] final case class NotEqual[From](sortKey: SortKey2[From], value: AttributeValue) + extends ExtendedSortKeyExpr[From] + private[dynamodb] final case class LessThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) + extends ExtendedSortKeyExpr[From] + private[dynamodb] final case class GreaterThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) + extends ExtendedSortKeyExpr[From] + private[dynamodb] final case class Between[From](left: SortKey2[From], min: AttributeValue, max: AttributeValue) + extends ExtendedSortKeyExpr[From] + private[dynamodb] final case class BeginsWith[From](left: SortKey2[From], value: AttributeValue) + extends ExtendedSortKeyExpr[From] } } diff --git a/examples/src/main/scala/zio/dynamodb/examples/ConditionExpressionExamples.scala b/examples/src/main/scala/zio/dynamodb/examples/ConditionExpressionExamples.scala index 091500eb5..ae44587b1 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/ConditionExpressionExamples.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/ConditionExpressionExamples.scala @@ -24,6 +24,7 @@ object ConditionExpressionExamples { val peOpticNum1: ProjectionExpression[Student, Int] = Student.count1 val peOpticNum2: ProjectionExpression[Student, Int] = Student.count2 val ceOptic1: ConditionExpression[Student] = peOpticNum1 > peOpticNum2 + val ceOptic2: ConditionExpression[Student] = Student.subject.between("a", "b") val x: ProjectionExpression[Any, Unknown] = $("col2") val xx: Operand.Size[Any, Unknown] = x.size diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index 73d68fe5e..8249810c5 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -32,7 +32,7 @@ object KeyConditionExprExample extends App { def whereKey[From](k: KeyConditionExpr[From]) = k match { // PartitionKeyExpr - case PartitionKeyExpr(pk, value) => println(s"pk=$pk, value=$value") + case PartitionKeyExpr(pk, value) => println(s"pk=$pk, value=$value") // CompositePrimaryKeyExpr case CompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") // ExtendedCompositePrimaryKeyExpr @@ -66,13 +66,17 @@ object KeyConditionExprExample extends App { val (email, subject, age) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" - val sk1: SortKeyExpr[Student] = Student.subject.sortKey === "y" - val sk2: ExtendedSortKeyExpr[Student] = Student.subject.sortKey > "y" - val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" + val sk1: SortKeyExpr[Student] = Student.subject.sortKey === "y" + val sk2: ExtendedSortKeyExpr[Student] = Student.subject.sortKey > "y" + val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended: ExtendedCompositePrimaryKeyExpr[Student] = + val pkAndSkExtended1: ExtendedCompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + val pkAndSkExtended2: ExtendedCompositePrimaryKeyExpr[Student] = + Student.email.primaryKey === "x" && Student.subject.sortKey >= "y" + val pkAndSkExtended3: ExtendedCompositePrimaryKeyExpr[Student] = + Student.email.primaryKey === "x" && Student.subject.sortKey.between(1, 2) // GetItem Query will have three overridden versions // 1) takes AttrMap/PriamaryKey - for users of low level API @@ -86,7 +90,7 @@ object KeyConditionExprExample extends App { // println(pkAndSkExtended) // Render requirements - val (aliasMap, s) = pkAndSkExtended.render.execute + val (aliasMap, s) = pkAndSkExtended1.render.execute println(s"aliasMap=$aliasMap, s=$s") } From 7ed21aeac11740bd23a106e221fd368eaf3e53c6 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Wed, 12 Jul 2023 07:55:46 +0100 Subject: [PATCH 30/69] comment out extra ops for now --- .../scala/zio/dynamodb/keyConditionExpr.scala | 123 +++++++++--------- .../examples/KeyConditionExprExample.scala | 8 +- 2 files changed, 65 insertions(+), 66 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index e59539948..c7cb58d19 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -33,25 +33,24 @@ private[dynamodb] final case class SortKey2[From](keyName: String) { self => val _ = ev ExtendedSortKeyExpr.GreaterThan(self, to.toAttributeValue(value)) } - def <[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - val _ = ev - ExtendedSortKeyExpr.LessThan(self, to.toAttributeValue(value)) - } - def <>[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - val _ = ev - ExtendedSortKeyExpr.NotEqual(self, to.toAttributeValue(value)) - } - def <=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - val _ = ev - ExtendedSortKeyExpr.LessThanOrEqual(self, to.toAttributeValue(value)) - } - def >=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - val _ = ev - ExtendedSortKeyExpr.GreaterThanOrEqual(self, to.toAttributeValue(value)) - } - def between[To](min: To, max: To)(implicit t: ToAttributeValue[To]): ExtendedSortKeyExpr[From] = - ExtendedSortKeyExpr.Between[From](self, t.toAttributeValue(min), t.toAttributeValue(max)) - // ... and so on for all the other extended operators + // def <[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + // val _ = ev + // ExtendedSortKeyExpr.LessThan(self, to.toAttributeValue(value)) + // } + // def <>[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + // val _ = ev + // ExtendedSortKeyExpr.NotEqual(self, to.toAttributeValue(value)) + // } + // def <=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + // val _ = ev + // ExtendedSortKeyExpr.LessThanOrEqual(self, to.toAttributeValue(value)) + // } + // def >=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + // val _ = ev + // ExtendedSortKeyExpr.GreaterThanOrEqual(self, to.toAttributeValue(value)) + // } + // def between[To](min: To, max: To)(implicit t: ToAttributeValue[To]): ExtendedSortKeyExpr[From] = + // ExtendedSortKeyExpr.Between[From](self, t.toAttributeValue(min), t.toAttributeValue(max)) } sealed trait KeyConditionExpr[-From] extends Renderable { self => @@ -114,58 +113,58 @@ object KeyConditionExpr { sealed trait ExtendedSortKeyExpr[-From] { self => def render2: AliasMapRender[String] = self match { - case ExtendedSortKeyExpr.GreaterThan(sk, value) => + case ExtendedSortKeyExpr.GreaterThan(sk, value) => AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} > $v") - case ExtendedSortKeyExpr.LessThan(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} < $v") - case ExtendedSortKeyExpr.NotEqual(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} <> $v") - case ExtendedSortKeyExpr.LessThanOrEqual(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} <= $v") - case ExtendedSortKeyExpr.GreaterThanOrEqual(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} >= $v") - case ExtendedSortKeyExpr.Between(left, min, max) => - AliasMapRender - .getOrInsert(min) - .flatMap(min => - AliasMapRender.getOrInsert(max).map { max => - s"${left.keyName} BETWEEN $min AND $max" - } - ) - case ExtendedSortKeyExpr.BeginsWith(left, value) => - AliasMapRender - .getOrInsert(value) - .map { v => - s"begins_with(${left.keyName}, $v)" - } + // case ExtendedSortKeyExpr.LessThan(sk, value) => + // AliasMapRender + // .getOrInsert(value) + // .map(v => s"${sk.keyName} < $v") + // case ExtendedSortKeyExpr.NotEqual(sk, value) => + // AliasMapRender + // .getOrInsert(value) + // .map(v => s"${sk.keyName} <> $v") + // case ExtendedSortKeyExpr.LessThanOrEqual(sk, value) => + // AliasMapRender + // .getOrInsert(value) + // .map(v => s"${sk.keyName} <= $v") + // case ExtendedSortKeyExpr.GreaterThanOrEqual(sk, value) => + // AliasMapRender + // .getOrInsert(value) + // .map(v => s"${sk.keyName} >= $v") + // case ExtendedSortKeyExpr.Between(left, min, max) => + // AliasMapRender + // .getOrInsert(min) + // .flatMap(min => + // AliasMapRender.getOrInsert(max).map { max => + // s"${left.keyName} BETWEEN $min AND $max" + // } + // ) + // case ExtendedSortKeyExpr.BeginsWith(left, value) => + // AliasMapRender + // .getOrInsert(value) + // .map { v => + // s"begins_with(${left.keyName}, $v)" + // } } } object ExtendedSortKeyExpr { private[dynamodb] final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) extends ExtendedSortKeyExpr[From] - private[dynamodb] final case class LessThan[From](sortKey: SortKey2[From], value: AttributeValue) - extends ExtendedSortKeyExpr[From] - private[dynamodb] final case class NotEqual[From](sortKey: SortKey2[From], value: AttributeValue) - extends ExtendedSortKeyExpr[From] - private[dynamodb] final case class LessThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) - extends ExtendedSortKeyExpr[From] - private[dynamodb] final case class GreaterThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) - extends ExtendedSortKeyExpr[From] - private[dynamodb] final case class Between[From](left: SortKey2[From], min: AttributeValue, max: AttributeValue) - extends ExtendedSortKeyExpr[From] - private[dynamodb] final case class BeginsWith[From](left: SortKey2[From], value: AttributeValue) - extends ExtendedSortKeyExpr[From] + // private[dynamodb] final case class LessThan[From](sortKey: SortKey2[From], value: AttributeValue) + // extends ExtendedSortKeyExpr[From] + // private[dynamodb] final case class NotEqual[From](sortKey: SortKey2[From], value: AttributeValue) + // extends ExtendedSortKeyExpr[From] + // private[dynamodb] final case class LessThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) + // extends ExtendedSortKeyExpr[From] + // private[dynamodb] final case class GreaterThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) + // extends ExtendedSortKeyExpr[From] + // private[dynamodb] final case class Between[From](left: SortKey2[From], min: AttributeValue, max: AttributeValue) + // extends ExtendedSortKeyExpr[From] + // private[dynamodb] final case class BeginsWith[From](left: SortKey2[From], value: AttributeValue) + // extends ExtendedSortKeyExpr[From] } } diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index 8249810c5..b06f32cc7 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -73,10 +73,10 @@ object KeyConditionExprExample extends App { //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed val pkAndSkExtended1: ExtendedCompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - val pkAndSkExtended2: ExtendedCompositePrimaryKeyExpr[Student] = - Student.email.primaryKey === "x" && Student.subject.sortKey >= "y" - val pkAndSkExtended3: ExtendedCompositePrimaryKeyExpr[Student] = - Student.email.primaryKey === "x" && Student.subject.sortKey.between(1, 2) + // val pkAndSkExtended2: ExtendedCompositePrimaryKeyExpr[Student] = + // Student.email.primaryKey === "x" && Student.subject.sortKey >= "y" + // val pkAndSkExtended3: ExtendedCompositePrimaryKeyExpr[Student] = + // Student.email.primaryKey === "x" && Student.subject.sortKey.between(1, 2) // GetItem Query will have three overridden versions // 1) takes AttrMap/PriamaryKey - for users of low level API From d364265db14c3c8c27584067a9ae29ed31a81d7e Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 15 Jul 2023 07:49:48 +0100 Subject: [PATCH 31/69] added remaining ops --- .../zio/dynamodb/ProjectionExpression.scala | 16 +- .../scala/zio/dynamodb/keyConditionExpr.scala | 236 ++++++++++++------ .../proofs/CanSortKeyBeginsWith.scala | 24 ++ .../zio/dynamodb/proofs/IsPrimaryKey.scala | 6 +- .../examples/KeyConditionExprExample.scala | 58 +++-- 5 files changed, 219 insertions(+), 121 deletions(-) create mode 100644 dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 279fd5d8c..f6ad41ff9 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -134,17 +134,17 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { - def primaryKey(implicit ev: IsPrimaryKey[To]): PartitionKey2[From] = { + def primaryKey(implicit ev: IsPrimaryKey[To]): PartitionKey2[From, To] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => PartitionKey2[From](key) + case ProjectionExpression.MapElement(_, key) => PartitionKey2[From, To](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } - def sortKey(implicit ev: IsPrimaryKey[To]): SortKey2[From] = { + def sortKey(implicit ev: IsPrimaryKey[To]): SortKey2[From, To] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => SortKey2[From](key) + case ProjectionExpression.MapElement(_, key) => SortKey2[From, To](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } @@ -544,14 +544,14 @@ object ProjectionExpression extends ProjectionExpressionLowPriorityImplicits0 { implicit class ProjectionExpressionSyntax[From](self: ProjectionExpression[From, Unknown]) { - def primaryKey: PartitionKey2[From] = + def primaryKey: PartitionKey2[From, Unknown] = self match { - case ProjectionExpression.MapElement(_, key) => PartitionKey2[From](key) + case ProjectionExpression.MapElement(_, key) => PartitionKey2[From, Unknown](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: SortKey2[From] = + def sortKey: SortKey2[From, Unknown] = self match { - case ProjectionExpression.MapElement(_, key) => SortKey2[From](key) + case ProjectionExpression.MapElement(_, key) => SortKey2[From, Unknown](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index c7cb58d19..c2389b1be 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -1,7 +1,6 @@ package zio.dynamodb import zio.dynamodb.PrimaryKey -import zio.dynamodb.proofs.IsPrimaryKey /** * Typesafe KeyConditionExpr/primary key experiment @@ -12,45 +11,112 @@ import zio.dynamodb.proofs.IsPrimaryKey * - KeyConditionExpr.scala */ import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr +import zio.dynamodb.proofs.RefersTo +import zio.dynamodb.proofs.CanSortKeyBeginsWith // belongs to the package top level -private[dynamodb] final case class PartitionKey2[-From](keyName: String) { self => - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): PartitionKeyExpr[From] = { +private[dynamodb] final case class PartitionKey2[-From, To](keyName: String) { self => + def ===[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): PartitionKeyExpr[From, To] = { val _ = ev - PartitionKeyExpr(self, to.toAttributeValue(value)) + PartitionKeyExpr(self, implicitly[ToAttributeValue[To2]].toAttributeValue(value)) } } import zio.dynamodb.KeyConditionExpr.SortKeyExpr import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr -private[dynamodb] final case class SortKey2[From](keyName: String) { self => - def ===[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): SortKeyExpr[From] = { +private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self => + // all comparion ops apply to: Strings, Numbers, Binary values + def ===[To2: ToAttributeValue, IsPrimaryKey](value: To2)(implicit ev: RefersTo[To, To2]): SortKeyExpr[From, To2] = { val _ = ev - SortKeyExpr[From](self, to.toAttributeValue(value)) + SortKeyExpr[From, To2]( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) } - def >[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + def >[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev - ExtendedSortKeyExpr.GreaterThan(self, to.toAttributeValue(value)) + ExtendedSortKeyExpr.GreaterThan( + self.asInstanceOf[SortKey2[From, To2]], + implicitly(ToAttributeValue[To2]).toAttributeValue(value) + ) } - // def <[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - // val _ = ev - // ExtendedSortKeyExpr.LessThan(self, to.toAttributeValue(value)) - // } - // def <>[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - // val _ = ev - // ExtendedSortKeyExpr.NotEqual(self, to.toAttributeValue(value)) - // } - // def <=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { - // val _ = ev - // ExtendedSortKeyExpr.LessThanOrEqual(self, to.toAttributeValue(value)) - // } - // def >=[To](value: To)(implicit to: ToAttributeValue[To], ev: IsPrimaryKey[To]): ExtendedSortKeyExpr[From] = { + def <[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.LessThan( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) + } + def <>[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.NotEqual( + self.asInstanceOf[SortKey2[From, To2]], + implicitly(ToAttributeValue[To2]).toAttributeValue(value) + ) + } + def <=[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.LessThanOrEqual( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) + } + def >=[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.GreaterThanOrEqual( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) + } + // applies to all PK types + def between[To: ToAttributeValue, IsPrimaryKey](min: To, max: To): ExtendedSortKeyExpr[From, To] = + ExtendedSortKeyExpr.Between[From, To]( + self.asInstanceOf[SortKey2[From, To]], + implicitly[ToAttributeValue[To]].toAttributeValue(min), + implicitly[ToAttributeValue[To]].toAttributeValue(max) + ) + +/* + /** + * Applies to a String or Set + */ + def contains[A](av: A)(implicit ev: Containable[To, A], to: ToAttributeValue[A]): ConditionExpression[From] = { + val _ = ev + ConditionExpression.Contains(self, to.toAttributeValue(av)) + } + +*/ + // beginsWith applies to: Strings, Binary values + def beginsWith[To2: ToAttributeValue, IsPrimaryKey](prefix: To2)(implicit ev: CanSortKeyBeginsWith[To, To]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.BeginsWith[From, To2]( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(prefix) + ) + } + + // // beginsWith applies to: Strings, Binary values + // def beginsWith[To: ToAttributeValue, IsPrimaryKey](prefix: To)(implicit ev: RefersTo[String, To]): ExtendedSortKeyExpr[From, To] = { // val _ = ev - // ExtendedSortKeyExpr.GreaterThanOrEqual(self, to.toAttributeValue(value)) + // ExtendedSortKeyExpr.BeginsWith[From, To]( + // self.asInstanceOf[SortKey2[From, To]], + // implicitly[ToAttributeValue[To]].toAttributeValue(prefix) + // ) // } - // def between[To](min: To, max: To)(implicit t: ToAttributeValue[To]): ExtendedSortKeyExpr[From] = - // ExtendedSortKeyExpr.Between[From](self, t.toAttributeValue(min), t.toAttributeValue(max)) + } sealed trait KeyConditionExpr[-From] extends Renderable { self => @@ -62,13 +128,13 @@ object KeyConditionExpr { // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - private[dynamodb] final case class PartitionKeyExpr[-From](pk: PartitionKey2[From], value: AttributeValue) + private[dynamodb] final case class PartitionKeyExpr[-From, To](pk: PartitionKey2[From, To], value: AttributeValue) extends KeyConditionExpr[From] { self => - def &&[From1 <: From](other: SortKeyExpr[From1]): CompositePrimaryKeyExpr[From1] = - CompositePrimaryKeyExpr[From1](self, other) - def &&[From1 <: From](other: ExtendedSortKeyExpr[From1]): ExtendedCompositePrimaryKeyExpr[From1] = - ExtendedCompositePrimaryKeyExpr[From1](self, other) + def &&[From1 <: From, To2](other: SortKeyExpr[From1, To2]): CompositePrimaryKeyExpr[From1, To2] = + CompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyExpr[From1, To2]], other) + def &&[From1 <: From, To2](other: ExtendedSortKeyExpr[From1, To2]): ExtendedCompositePrimaryKeyExpr[From1, To2] = + ExtendedCompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyExpr[From1, To2]], other) def asAttrMap: AttrMap = AttrMap(pk.keyName -> value) @@ -76,15 +142,18 @@ object KeyConditionExpr { AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } - private[dynamodb] final case class SortKeyExpr[From](sortKey: SortKey2[From], value: AttributeValue) { self => + private[dynamodb] final case class SortKeyExpr[-From, To](sortKey: SortKey2[From, To], value: AttributeValue) { + self => def render2: AliasMapRender[String] = AliasMapRender .getOrInsert(value) .map(v => s"${sortKey.keyName} = $v") } - private[dynamodb] final case class CompositePrimaryKeyExpr[From](pk: PartitionKeyExpr[From], sk: SortKeyExpr[From]) - extends KeyConditionExpr[From] { + private[dynamodb] final case class CompositePrimaryKeyExpr[-From, To]( + pk: PartitionKeyExpr[From, To], + sk: SortKeyExpr[From, To] + ) extends KeyConditionExpr[From] { self => def asAttrMap: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) @@ -96,9 +165,9 @@ object KeyConditionExpr { } yield s"$pkStr AND $skStr" } - private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From]( - pk: PartitionKeyExpr[From], - sk: ExtendedSortKeyExpr[From] + private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, To]( + pk: PartitionKeyExpr[From, To], + sk: ExtendedSortKeyExpr[From, To] ) extends KeyConditionExpr[From] { self => @@ -110,61 +179,64 @@ object KeyConditionExpr { } - sealed trait ExtendedSortKeyExpr[-From] { self => + sealed trait ExtendedSortKeyExpr[-From, To] { self => def render2: AliasMapRender[String] = self match { - case ExtendedSortKeyExpr.GreaterThan(sk, value) => + case ExtendedSortKeyExpr.GreaterThan(sk, value) => AliasMapRender .getOrInsert(value) .map(v => s"${sk.keyName} > $v") - // case ExtendedSortKeyExpr.LessThan(sk, value) => - // AliasMapRender - // .getOrInsert(value) - // .map(v => s"${sk.keyName} < $v") - // case ExtendedSortKeyExpr.NotEqual(sk, value) => - // AliasMapRender - // .getOrInsert(value) - // .map(v => s"${sk.keyName} <> $v") - // case ExtendedSortKeyExpr.LessThanOrEqual(sk, value) => - // AliasMapRender - // .getOrInsert(value) - // .map(v => s"${sk.keyName} <= $v") - // case ExtendedSortKeyExpr.GreaterThanOrEqual(sk, value) => - // AliasMapRender - // .getOrInsert(value) - // .map(v => s"${sk.keyName} >= $v") - // case ExtendedSortKeyExpr.Between(left, min, max) => - // AliasMapRender - // .getOrInsert(min) - // .flatMap(min => - // AliasMapRender.getOrInsert(max).map { max => - // s"${left.keyName} BETWEEN $min AND $max" - // } - // ) - // case ExtendedSortKeyExpr.BeginsWith(left, value) => - // AliasMapRender - // .getOrInsert(value) - // .map { v => - // s"begins_with(${left.keyName}, $v)" - // } + case ExtendedSortKeyExpr.LessThan(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} < $v") + case ExtendedSortKeyExpr.NotEqual(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} <> $v") + case ExtendedSortKeyExpr.LessThanOrEqual(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} <= $v") + case ExtendedSortKeyExpr.GreaterThanOrEqual(sk, value) => + AliasMapRender + .getOrInsert(value) + .map(v => s"${sk.keyName} >= $v") + case ExtendedSortKeyExpr.Between(left, min, max) => + AliasMapRender + .getOrInsert(min) + .flatMap(min => + AliasMapRender.getOrInsert(max).map { max => + s"${left.keyName} BETWEEN $min AND $max" + } + ) + case ExtendedSortKeyExpr.BeginsWith(left, value) => + AliasMapRender + .getOrInsert(value) + .map { v => + s"begins_with(${left.keyName}, $v)" + } } } object ExtendedSortKeyExpr { - private[dynamodb] final case class GreaterThan[From](sortKey: SortKey2[From], value: AttributeValue) - extends ExtendedSortKeyExpr[From] - // private[dynamodb] final case class LessThan[From](sortKey: SortKey2[From], value: AttributeValue) - // extends ExtendedSortKeyExpr[From] - // private[dynamodb] final case class NotEqual[From](sortKey: SortKey2[From], value: AttributeValue) - // extends ExtendedSortKeyExpr[From] - // private[dynamodb] final case class LessThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) - // extends ExtendedSortKeyExpr[From] - // private[dynamodb] final case class GreaterThanOrEqual[From](sortKey: SortKey2[From], value: AttributeValue) - // extends ExtendedSortKeyExpr[From] - // private[dynamodb] final case class Between[From](left: SortKey2[From], min: AttributeValue, max: AttributeValue) - // extends ExtendedSortKeyExpr[From] - // private[dynamodb] final case class BeginsWith[From](left: SortKey2[From], value: AttributeValue) - // extends ExtendedSortKeyExpr[From] + private[dynamodb] final case class GreaterThan[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + extends ExtendedSortKeyExpr[From, To] + private[dynamodb] final case class LessThan[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + extends ExtendedSortKeyExpr[From, To] + private[dynamodb] final case class NotEqual[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + extends ExtendedSortKeyExpr[From, To] + private[dynamodb] final case class LessThanOrEqual[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + extends ExtendedSortKeyExpr[From, To] + private[dynamodb] final case class GreaterThanOrEqual[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + extends ExtendedSortKeyExpr[From, To] + private[dynamodb] final case class Between[From, To]( + left: SortKey2[From, To], + min: AttributeValue, + max: AttributeValue + ) extends ExtendedSortKeyExpr[From, To] + private[dynamodb] final case class BeginsWith[From, To](left: SortKey2[From, To], value: AttributeValue) + extends ExtendedSortKeyExpr[From, To] } } diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala new file mode 100644 index 000000000..7f24ed037 --- /dev/null +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala @@ -0,0 +1,24 @@ +package zio.dynamodb.proofs + +import zio.dynamodb.ProjectionExpression + +sealed trait CanSortKeyBeginsWith[X, -A] +trait CanSortKeyBeginsWith0 extends CanSortKeyBeginsWith1 { + implicit def unknownRight[X]: CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] = + new CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] {} +} +trait CanSortKeyBeginsWith1 { + implicit def bytes[A]: CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] = + new CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] {} + implicit def bytes2[A]: CanSortKeyBeginsWith[Array[Byte], Array[Byte]] = + new CanSortKeyBeginsWith[Array[Byte], Array[Byte]] {} + implicit def bytes3[A]: CanSortKeyBeginsWith[List[Byte], List[Byte]] = + new CanSortKeyBeginsWith[List[Byte], List[Byte]] {} + // TODO: Avi - other collection types + + implicit def string: CanSortKeyBeginsWith[String, String] = new CanSortKeyBeginsWith[String, String] {} +} +object CanSortKeyBeginsWith extends CanSortKeyBeginsWith0 { + implicit def unknownLeft[X]: CanSortKeyBeginsWith[ProjectionExpression.Unknown, X] = + new CanSortKeyBeginsWith[ProjectionExpression.Unknown, X] {} +} diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index b56768414..7f1589576 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -5,11 +5,15 @@ import scala.annotation.implicitNotFound @implicitNotFound("DynamoDB does not support primary key type ${A}") sealed trait IsPrimaryKey[A] +// Allowed types for partition and sort keys are: String, Number, Binary object IsPrimaryKey { implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} // TODO - String type in the DB is overloaded in Scala land eg Date in Scala is String in DB // so do we also allow Scala types that are strings in the DB? implicit val stringIsPrimaryKey = new IsPrimaryKey[String] {} - // todo binary data + + // binary data + implicit val binaryIsPrimaryKey = new IsPrimaryKey[Iterable[Byte]] {} + implicit val binaryIsPrimaryKey2 = new IsPrimaryKey[List[Byte]] {} } diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index b06f32cc7..6b42f5e5b 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -1,6 +1,5 @@ package zio.dynamodb.examples -import zio.dynamodb.PrimaryKey import zio.dynamodb.KeyConditionExpr import zio.dynamodb.ProjectionExpression @@ -15,19 +14,6 @@ object KeyConditionExprExample extends App { // DynamoDbQuery's still use PrimaryKey // typesafe API constructors only expose PartitionKeyEprn - // TODO: move to PartitionKeyExpr - def asPk[From](k: PartitionKeyExpr[From]): PrimaryKey = - k match { - case PartitionKeyExpr(pk, value) => PrimaryKey(pk.keyName -> value) - } - def asPk[From](k: CompositePrimaryKeyExpr[From]): PrimaryKey = - k match { - case CompositePrimaryKeyExpr(pk, sk) => - (pk, sk) match { - case (PartitionKeyExpr(pk, value), SortKeyExpr(sk, value2)) => - PrimaryKey(pk.keyName -> value, sk.keyName -> value2) - } - } def whereKey[From](k: KeyConditionExpr[From]) = k match { @@ -40,7 +26,7 @@ object KeyConditionExprExample extends App { } // in low level - non type safe land - // val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" + //val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" // val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" // val x3: CompositePrimaryKeyExpr[Nothing] = x1 && x2 // val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" @@ -52,31 +38,43 @@ object KeyConditionExprExample extends App { // val y2: ExtendedCompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" import zio.dynamodb.ProjectionExpression.$ - val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey === "y" - val x7: ExtendedCompositePrimaryKeyExpr[Any] = $("foo.bar").primaryKey === "x" && $("foo.baz").sortKey > "y" +// TODO: Avi +//inferred type arguments [String] do not conform to method ==='s type parameter bounds [To2 <: zio.dynamodb.ProjectionExpression.Unknown] + val x6: CompositePrimaryKeyExpr[Any, String] = $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey === "y" + val x7 = $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey > 1 + val x8: ExtendedCompositePrimaryKeyExpr[Any, Int] = + $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey.between(1, 2) + val x9 = + $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey.beginsWith(1L) final case class Elephant(email: String, subject: String, age: Int) object Elephant { implicit val schema: Schema.CaseClass3[String, String, Int, Elephant] = DeriveSchema.gen[Elephant] val (email, subject, age) = ProjectionExpression.accessors[Elephant] } - final case class Student(email: String, subject: String, age: Int) + final case class Student(email: String, subject: String, age: Long, binary: List[Byte]) object Student { - implicit val schema: Schema.CaseClass3[String, String, Int, Student] = DeriveSchema.gen[Student] - val (email, subject, age) = ProjectionExpression.accessors[Student] + implicit val schema: Schema.CaseClass4[String, String, Long, List[Byte], Student] = DeriveSchema.gen[Student] + val (email, subject, age, binary) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyExpr[Student] = Student.email.primaryKey === "x" - val sk1: SortKeyExpr[Student] = Student.subject.sortKey === "y" - val sk2: ExtendedSortKeyExpr[Student] = Student.subject.sortKey > "y" - val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pk: PartitionKeyExpr[Student, String] = Student.email.primaryKey === "x" +// val pkX: PartitionKeyExpr[Student, String] = Student.age.primaryKey === "x" // as expected does not compile + val sk1: SortKeyExpr[Student, String] = Student.subject.sortKey === "y" + val sk2: ExtendedSortKeyExpr[Student, String] = Student.subject.sortKey > "y" + val pkAndSk = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended1: ExtendedCompositePrimaryKeyExpr[Student] = + val pkAndSkExtended1 = Student.email.primaryKey === "x" && Student.subject.sortKey > "y" - // val pkAndSkExtended2: ExtendedCompositePrimaryKeyExpr[Student] = - // Student.email.primaryKey === "x" && Student.subject.sortKey >= "y" - // val pkAndSkExtended3: ExtendedCompositePrimaryKeyExpr[Student] = - // Student.email.primaryKey === "x" && Student.subject.sortKey.between(1, 2) + val pkAndSkExtended2 = + Student.email.primaryKey === "x" && Student.subject.sortKey < "y" + val pkAndSkExtended3 = + Student.email.primaryKey === "x" && Student.subject.sortKey.between("1", "2") + val pkAndSkExtended4 = + Student.email.primaryKey === "x" && Student.subject.sortKey.beginsWith("1") + val pkAndSkExtended5 = + Student.email.primaryKey === "x" && Student.binary.sortKey.beginsWith(List(1.toByte)) + // TODO: Avi - fix ToAttrubuteValue interop with Array[Byte] // GetItem Query will have three overridden versions // 1) takes AttrMap/PriamaryKey - for users of low level API @@ -101,7 +99,7 @@ object Foo { val (email, subject, age) = ProjectionExpression.accessors[Student] } - val pkAndSk: KeyConditionExpr.CompositePrimaryKeyExpr[Student] = + val pkAndSk = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" } From 7821d8abc38b3cef82399d4bbb623279eace7c45 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 15 Jul 2023 07:59:49 +0100 Subject: [PATCH 32/69] fix beginsWith --- .../src/main/scala/zio/dynamodb/keyConditionExpr.scala | 2 +- .../zio/dynamodb/proofs/CanSortKeyBeginsWith.scala | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index c2389b1be..9d487ddf9 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -100,7 +100,7 @@ private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self = */ // beginsWith applies to: Strings, Binary values - def beginsWith[To2: ToAttributeValue, IsPrimaryKey](prefix: To2)(implicit ev: CanSortKeyBeginsWith[To, To]): ExtendedSortKeyExpr[From, To2] = { + def beginsWith[To2: ToAttributeValue, IsPrimaryKey](prefix: To2)(implicit ev: CanSortKeyBeginsWith[To, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.BeginsWith[From, To2]( self.asInstanceOf[SortKey2[From, To2]], diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala index 7f24ed037..a2353a09c 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala @@ -1,22 +1,26 @@ package zio.dynamodb.proofs import zio.dynamodb.ProjectionExpression +import scala.annotation.implicitNotFound +@implicitNotFound( + "DynamoDB does not support 'beginsWith' on type ${A}. This operator only applies to binary and string fields" +) sealed trait CanSortKeyBeginsWith[X, -A] trait CanSortKeyBeginsWith0 extends CanSortKeyBeginsWith1 { implicit def unknownRight[X]: CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] = new CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] {} } trait CanSortKeyBeginsWith1 { - implicit def bytes[A]: CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] = - new CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] {} implicit def bytes2[A]: CanSortKeyBeginsWith[Array[Byte], Array[Byte]] = new CanSortKeyBeginsWith[Array[Byte], Array[Byte]] {} implicit def bytes3[A]: CanSortKeyBeginsWith[List[Byte], List[Byte]] = new CanSortKeyBeginsWith[List[Byte], List[Byte]] {} + implicit def bytes[A]: CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] = + new CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] {} // TODO: Avi - other collection types - implicit def string: CanSortKeyBeginsWith[String, String] = new CanSortKeyBeginsWith[String, String] {} + implicit def string: CanSortKeyBeginsWith[String, String] = new CanSortKeyBeginsWith[String, String] {} } object CanSortKeyBeginsWith extends CanSortKeyBeginsWith0 { implicit def unknownLeft[X]: CanSortKeyBeginsWith[ProjectionExpression.Unknown, X] = From c7d442e4f07e3da04a49e0afafe21a8faa84ad96 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 16 Jul 2023 07:23:50 +0100 Subject: [PATCH 33/69] minor clean up --- .../scala/zio/dynamodb/keyConditionExpr.scala | 21 +------------------ .../proofs/CanSortKeyBeginsWith.scala | 2 +- .../zio/dynamodb/proofs/IsPrimaryKey.scala | 6 +++--- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 9d487ddf9..feaf3b1d0 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -28,7 +28,7 @@ import zio.dynamodb.KeyConditionExpr.SortKeyExpr import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self => - // all comparion ops apply to: Strings, Numbers, Binary values + // all comparison ops apply to: Strings, Numbers, Binary values def ===[To2: ToAttributeValue, IsPrimaryKey](value: To2)(implicit ev: RefersTo[To, To2]): SortKeyExpr[From, To2] = { val _ = ev SortKeyExpr[From, To2]( @@ -89,16 +89,6 @@ private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self = implicitly[ToAttributeValue[To]].toAttributeValue(max) ) -/* - /** - * Applies to a String or Set - */ - def contains[A](av: A)(implicit ev: Containable[To, A], to: ToAttributeValue[A]): ConditionExpression[From] = { - val _ = ev - ConditionExpression.Contains(self, to.toAttributeValue(av)) - } - -*/ // beginsWith applies to: Strings, Binary values def beginsWith[To2: ToAttributeValue, IsPrimaryKey](prefix: To2)(implicit ev: CanSortKeyBeginsWith[To, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev @@ -108,15 +98,6 @@ private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self = ) } - // // beginsWith applies to: Strings, Binary values - // def beginsWith[To: ToAttributeValue, IsPrimaryKey](prefix: To)(implicit ev: RefersTo[String, To]): ExtendedSortKeyExpr[From, To] = { - // val _ = ev - // ExtendedSortKeyExpr.BeginsWith[From, To]( - // self.asInstanceOf[SortKey2[From, To]], - // implicitly[ToAttributeValue[To]].toAttributeValue(prefix) - // ) - // } - } sealed trait KeyConditionExpr[-From] extends Renderable { self => diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala index a2353a09c..e07b351dc 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala @@ -4,7 +4,7 @@ import zio.dynamodb.ProjectionExpression import scala.annotation.implicitNotFound @implicitNotFound( - "DynamoDB does not support 'beginsWith' on type ${A}. This operator only applies to binary and string fields" + "For fields of type ${X}, DynamoDB does not support 'beginsWith' for type ${A}." ) sealed trait CanSortKeyBeginsWith[X, -A] trait CanSortKeyBeginsWith0 extends CanSortKeyBeginsWith1 { diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index 7f1589576..ca85711f6 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -8,12 +8,12 @@ sealed trait IsPrimaryKey[A] // Allowed types for partition and sort keys are: String, Number, Binary object IsPrimaryKey { implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} + // TODO: Avi - support other numeric types - // TODO - String type in the DB is overloaded in Scala land eg Date in Scala is String in DB - // so do we also allow Scala types that are strings in the DB? implicit val stringIsPrimaryKey = new IsPrimaryKey[String] {} // binary data - implicit val binaryIsPrimaryKey = new IsPrimaryKey[Iterable[Byte]] {} + implicit val binaryIsPrimaryKey = new IsPrimaryKey[Iterable[Byte]] {} implicit val binaryIsPrimaryKey2 = new IsPrimaryKey[List[Byte]] {} + // TODO: Avi - other collection types } From 79170fe302f4603f090149a78d347e428afb920d Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 16 Jul 2023 07:27:55 +0100 Subject: [PATCH 34/69] improve error messgae --- .../main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala index e07b351dc..bbda2a893 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala @@ -4,7 +4,7 @@ import zio.dynamodb.ProjectionExpression import scala.annotation.implicitNotFound @implicitNotFound( - "For fields of type ${X}, DynamoDB does not support 'beginsWith' for type ${A}." + "Fields of type ${X} has 'beginsWith' argument of type ${A} - they must be the same type" ) sealed trait CanSortKeyBeginsWith[X, -A] trait CanSortKeyBeginsWith0 extends CanSortKeyBeginsWith1 { From c931770a878e20643d857734ffb1a0629c2d6a93 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Wed, 19 Jul 2023 07:56:54 +0100 Subject: [PATCH 35/69] extracted classes, implemeted get2 methods --- .../scala/zio/dynamodb/DynamoDBQuery.scala | 22 +-- .../scala/zio/dynamodb/PartitionKey2.scala | 15 ++ .../main/scala/zio/dynamodb/SortKey2.scala | 82 +++++++++++ .../scala/zio/dynamodb/keyConditionExpr.scala | 119 ++++------------ .../scala/zio/dynamodb/examples/Main.scala | 11 +- .../examples/SimpleDecodedExample.scala | 11 +- ...peSafeRoundTripSerialisationExample2.scala | 134 ++++++++++++++++++ ...StudentZioDynamoDbTypeSafeAPIExample.scala | 18 +-- 8 files changed, 293 insertions(+), 119 deletions(-) create mode 100644 dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala create mode 100644 dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala create mode 100644 examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 09fde0ca0..ef0511a0b 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -1,7 +1,7 @@ package zio.dynamodb import zio.dynamodb.DynamoDBError.ValueNotFound -import zio.dynamodb.proofs.{ CanFilter, CanWhere, CanWhereKey, IsPrimaryKey } +import zio.dynamodb.proofs.{ CanFilter, CanWhere, CanWhereKey } import zio.dynamodb.DynamoDBQuery.BatchGetItem.TableGet import zio.dynamodb.DynamoDBQuery.BatchWriteItem.{ Delete, Put } import zio.dynamodb.DynamoDBQuery.{ @@ -28,6 +28,7 @@ import zio.prelude.ForEachOps import zio.schema.Schema import zio.stream.Stream import zio.{ Chunk, NonEmptyChunk, Schedule, ZIO, Zippable => _, _ } +import zio.dynamodb.proofs.IsPrimaryKey sealed trait DynamoDBQuery[-In, +Out] { self => @@ -493,18 +494,23 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } - def get[A: Schema, B: IsPrimaryKey]( + def get2[A: Schema, B]( tableName: String, - partitionKey: UpdateExpression.Action.SetAction[A, B], + partitionKeyExpr: KeyConditionExpr.PartitionKeyExpr[A, B], projections: ProjectionExpression[_, _]* - ): DynamoDBQuery[A, Either[DynamoDBError, A]] = ??? + )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { + val _ = ev + get(tableName, partitionKeyExpr.asAttrMap, projections: _*) + } - def get[A: Schema, B: IsPrimaryKey, C: IsPrimaryKey]( + def get2[A: Schema, B]( tableName: String, - partitionKey: UpdateExpression.Action.SetAction[A, B], - sortKey: UpdateExpression.Action.SetAction[A, C], + compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[A, B], projections: ProjectionExpression[_, _]* - ): DynamoDBQuery[A, Either[DynamoDBError, A]] = ??? + )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { + val _ = ev + get(tableName, compositeKeyExpr.asAttrMap, projections: _*) + } private[dynamodb] def fromItem[A: Schema](item: Item): Either[DynamoDBError, A] = { val av = ToAttributeValue.attrMapToAttributeValue.toAttributeValue(item) diff --git a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala new file mode 100644 index 000000000..1aa557f73 --- /dev/null +++ b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala @@ -0,0 +1,15 @@ +package zio.dynamodb + +import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr +import zio.dynamodb.proofs.RefersTo + + +// belongs to the package top level +private[dynamodb] final case class PartitionKey2[-From, To](keyName: String) { self => + def ===[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): PartitionKeyExpr[From, To] = { + val _ = ev + PartitionKeyExpr(self, implicitly[ToAttributeValue[To2]].toAttributeValue(value)) + } +} diff --git a/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala new file mode 100644 index 000000000..8628c8c2f --- /dev/null +++ b/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala @@ -0,0 +1,82 @@ +package zio.dynamodb + +import zio.dynamodb.proofs.RefersTo +import zio.dynamodb.proofs.CanSortKeyBeginsWith + +import zio.dynamodb.KeyConditionExpr.SortKeyExpr +import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr + +private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self => + // all comparison ops apply to: Strings, Numbers, Binary values + def ===[To2: ToAttributeValue, IsPrimaryKey](value: To2)(implicit ev: RefersTo[To, To2]): SortKeyExpr[From, To2] = { + val _ = ev + SortKeyExpr[From, To2]( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) + } + def >[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.GreaterThan( + self.asInstanceOf[SortKey2[From, To2]], + implicitly(ToAttributeValue[To2]).toAttributeValue(value) + ) + } + def <[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.LessThan( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) + } + def <>[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.NotEqual( + self.asInstanceOf[SortKey2[From, To2]], + implicitly(ToAttributeValue[To2]).toAttributeValue(value) + ) + } + def <=[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.LessThanOrEqual( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) + } + def >=[To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.GreaterThanOrEqual( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(value) + ) + } + // applies to all PK types + def between[To: ToAttributeValue, IsPrimaryKey](min: To, max: To): ExtendedSortKeyExpr[From, To] = + ExtendedSortKeyExpr.Between[From, To]( + self.asInstanceOf[SortKey2[From, To]], + implicitly[ToAttributeValue[To]].toAttributeValue(min), + implicitly[ToAttributeValue[To]].toAttributeValue(max) + ) + + // beginsWith applies to: Strings, Binary values + def beginsWith[To2: ToAttributeValue, IsPrimaryKey]( + prefix: To2 + )(implicit ev: CanSortKeyBeginsWith[To, To2]): ExtendedSortKeyExpr[From, To2] = { + val _ = ev + ExtendedSortKeyExpr.BeginsWith[From, To2]( + self.asInstanceOf[SortKey2[From, To2]], + implicitly[ToAttributeValue[To2]].toAttributeValue(prefix) + ) + } + +} diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index feaf3b1d0..99462dbd3 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -4,102 +4,7 @@ import zio.dynamodb.PrimaryKey /** * Typesafe KeyConditionExpr/primary key experiment - * - * TODO break out files - * - PartitionKey2.scala - * - SortKey2.scala - * - KeyConditionExpr.scala */ -import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr -import zio.dynamodb.proofs.RefersTo -import zio.dynamodb.proofs.CanSortKeyBeginsWith - -// belongs to the package top level -private[dynamodb] final case class PartitionKey2[-From, To](keyName: String) { self => - def ===[To2: ToAttributeValue, IsPrimaryKey]( - value: To2 - )(implicit ev: RefersTo[To, To2]): PartitionKeyExpr[From, To] = { - val _ = ev - PartitionKeyExpr(self, implicitly[ToAttributeValue[To2]].toAttributeValue(value)) - } -} - -import zio.dynamodb.KeyConditionExpr.SortKeyExpr -import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr - -private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self => - // all comparison ops apply to: Strings, Numbers, Binary values - def ===[To2: ToAttributeValue, IsPrimaryKey](value: To2)(implicit ev: RefersTo[To, To2]): SortKeyExpr[From, To2] = { - val _ = ev - SortKeyExpr[From, To2]( - self.asInstanceOf[SortKey2[From, To2]], - implicitly[ToAttributeValue[To2]].toAttributeValue(value) - ) - } - def >[To2: ToAttributeValue, IsPrimaryKey]( - value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { - val _ = ev - ExtendedSortKeyExpr.GreaterThan( - self.asInstanceOf[SortKey2[From, To2]], - implicitly(ToAttributeValue[To2]).toAttributeValue(value) - ) - } - def <[To2: ToAttributeValue, IsPrimaryKey]( - value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { - val _ = ev - ExtendedSortKeyExpr.LessThan( - self.asInstanceOf[SortKey2[From, To2]], - implicitly[ToAttributeValue[To2]].toAttributeValue(value) - ) - } - def <>[To2: ToAttributeValue, IsPrimaryKey]( - value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { - val _ = ev - ExtendedSortKeyExpr.NotEqual( - self.asInstanceOf[SortKey2[From, To2]], - implicitly(ToAttributeValue[To2]).toAttributeValue(value) - ) - } - def <=[To2: ToAttributeValue, IsPrimaryKey]( - value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { - val _ = ev - ExtendedSortKeyExpr.LessThanOrEqual( - self.asInstanceOf[SortKey2[From, To2]], - implicitly[ToAttributeValue[To2]].toAttributeValue(value) - ) - } - def >=[To2: ToAttributeValue, IsPrimaryKey]( - value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { - val _ = ev - ExtendedSortKeyExpr.GreaterThanOrEqual( - self.asInstanceOf[SortKey2[From, To2]], - implicitly[ToAttributeValue[To2]].toAttributeValue(value) - ) - } - // applies to all PK types - def between[To: ToAttributeValue, IsPrimaryKey](min: To, max: To): ExtendedSortKeyExpr[From, To] = - ExtendedSortKeyExpr.Between[From, To]( - self.asInstanceOf[SortKey2[From, To]], - implicitly[ToAttributeValue[To]].toAttributeValue(min), - implicitly[ToAttributeValue[To]].toAttributeValue(max) - ) - - // beginsWith applies to: Strings, Binary values - def beginsWith[To2: ToAttributeValue, IsPrimaryKey](prefix: To2)(implicit ev: CanSortKeyBeginsWith[To, To2]): ExtendedSortKeyExpr[From, To2] = { - val _ = ev - ExtendedSortKeyExpr.BeginsWith[From, To2]( - self.asInstanceOf[SortKey2[From, To2]], - implicitly[ToAttributeValue[To2]].toAttributeValue(prefix) - ) - } - -} - sealed trait KeyConditionExpr[-From] extends Renderable { self => def render: AliasMapRender[String] } @@ -221,3 +126,27 @@ object KeyConditionExpr { } } + +// object Foo { +// import zio.schema.Schema +// import zio.dynamodb.DynamoDBQuery.get + +// def get2[A: Schema, B]( +// tableName: String, +// partitionKeyExpr: KeyConditionExpr.PartitionKeyExpr[A, B], +// projections: ProjectionExpression[_, _]* +// )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { +// val _ = ev +// get(tableName, partitionKeyExpr.asAttrMap, projections: _*) +// } + +// def get2[A: Schema, B]( +// tableName: String, +// compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[A, B], +// projections: ProjectionExpression[_, _]* +// )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { +// val _ = ev +// get(tableName, compositeKeyExpr.asAttrMap, projections: _*) +// } + +// } diff --git a/examples/src/main/scala/zio/dynamodb/examples/Main.scala b/examples/src/main/scala/zio/dynamodb/examples/Main.scala index 533e6cabc..8ed52afcf 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/Main.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/Main.scala @@ -2,22 +2,25 @@ package zio.dynamodb.examples import zio.aws.core.config import zio.aws.{ dynamodb, netty } -import zio.dynamodb.DynamoDBQuery.{ get, put } -import zio.dynamodb.{ DynamoDBExecutor, PrimaryKey } +import zio.dynamodb.DynamoDBQuery.{ get2, put } +import zio.dynamodb.{ DynamoDBExecutor } import zio.schema.{ DeriveSchema, Schema } import zio.ZIOAppDefault +import zio.dynamodb.ProjectionExpression object Main extends ZIOAppDefault { final case class Person(id: Int, firstName: String) object Person { - implicit lazy val schema: Schema[Person] = DeriveSchema.gen[Person] + implicit lazy val schema: Schema.CaseClass2[Int, String, Person] = DeriveSchema.gen[Person] + + val (id, firstName) = ProjectionExpression.accessors[Person] } val examplePerson = Person(1, "avi") private val program = for { _ <- put("personTable", examplePerson).execute - person <- get[Person]("personTable", PrimaryKey("id" -> 1)).execute + person <- get2("personTable", Person.id.primaryKey === 1).execute _ <- zio.Console.printLine(s"hello $person") } yield () diff --git a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala index c3b1b3be2..e6a8d62e1 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala @@ -1,17 +1,22 @@ package zio.dynamodb.examples import zio.dynamodb.DynamoDBQuery._ -import zio.dynamodb.{ DynamoDBExecutor, Item, PrimaryKey } +import zio.dynamodb.{ DynamoDBExecutor, Item } import zio.schema.{ DeriveSchema, Schema } import zio.ZIOAppDefault import zio.Console.printLine +import zio.dynamodb.ProjectionExpression object SimpleDecodedExample extends ZIOAppDefault { val nestedItem = Item("id" -> 2, "name" -> "Avi", "flag" -> true) val parentItem: Item = Item("id" -> 1, "nested" -> nestedItem) final case class NestedCaseClass2(id: Int, nested: SimpleCaseClass3) - implicit lazy val nestedCaseClass2: Schema[NestedCaseClass2] = DeriveSchema.gen[NestedCaseClass2] + object NestedCaseClass2 { + implicit val nestedCaseClass2: Schema.CaseClass2[Int, SimpleCaseClass3, NestedCaseClass2] = + DeriveSchema.gen[NestedCaseClass2] + val (id, nested) = ProjectionExpression.accessors[NestedCaseClass2] + } final case class SimpleCaseClass3(id: Int, name: String, flag: Boolean) implicit lazy val simpleCaseClass3: Schema[SimpleCaseClass3] = DeriveSchema.gen[SimpleCaseClass3] @@ -19,7 +24,7 @@ object SimpleDecodedExample extends ZIOAppDefault { private val program = for { _ <- put("table1", NestedCaseClass2(id = 1, SimpleCaseClass3(2, "Avi", flag = true))).execute // Save case class to DB - caseClass <- get[NestedCaseClass2]("table1", PrimaryKey("id" -> 1)).execute // read case class from DB + caseClass <- get2("table1", NestedCaseClass2.id.primaryKey === 2).execute // read case class from DB _ <- printLine(s"get: found $caseClass") either <- scanSome[NestedCaseClass2]("table1", 10).execute _ <- printLine(s"scanSome: found $either") diff --git a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala new file mode 100644 index 000000000..3bd87420d --- /dev/null +++ b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala @@ -0,0 +1,134 @@ +package zio.dynamodb.examples + +import zio.Console.printLine +import zio.ZIOAppDefault +import zio.dynamodb.Annotations.enumOfCaseObjects +import zio.dynamodb.examples.TypeSafeRoundTripSerialisationExample2.Invoice2.{ + Address, + Billed2, + LineItem, + PaymentType, + PreBilled2, + Product +} +import zio.dynamodb.{ DynamoDBExecutor, DynamoDBQuery, PrimaryKey } +import zio.schema.annotation.{ caseName, discriminatorName } +import zio.schema.{ DeriveSchema, Schema } + +import java.time.Instant +import zio.dynamodb.ProjectionExpression +import zio.ZIO +import zio.dynamodb.DynamoDBError + +/* +[info] item=Some(AttrMap(HashMap(lineItems -> List(Chunk(Map(Map(String(product) -> Map(Map(String(name) -> String(a), String(sku) -> String(sku1))), String(price) -> Number(1.0), String(itemId) -> String(lineItem1))),Map(Map(String(product) -> Map(Map(String(name) -> String(b), String(sku) -> String(sku2))), String(price) -> Number(2.0), String(itemId) -> String(lineItem2))))), categoryMap -> Map(Map(String(a) -> String(1), String(b) -> String(2))), paymentType -> String(debit), total -> Number(10.0), id -> String(1), sequence -> Number(1), isTest -> Bool(false), dueDate -> String(2023-07-16T08:50:00.898031Z), address -> Map(Map(String(country) -> String(UK), String(line1) -> String(line1))), accountSet -> StringSet(Set(account1, account2))))) + */ + +//import TypeSafeRoundTripSerialisationExample2.Invoice2.{ Billed2, PreBilled2 } + +object TypeSafeRoundTripSerialisationExample2 extends ZIOAppDefault { + + @discriminatorName("invoiceType") + sealed trait Invoice2 { + def id: String + } + object Invoice2 { + @enumOfCaseObjects + sealed trait PaymentType + object PaymentType { + @caseName("debit") + case object DebitCard extends PaymentType + @caseName("credit") + case object CreditCard extends PaymentType + } + + final case class Address(line1: String, line2: Option[String], country: String) + final case class Product(sku: String, name: String) + final case class LineItem(itemId: String, price: BigDecimal, product: Product) + + final case class Billed2( + id: String, + sequence: Int, + dueDate: Instant, + total: BigDecimal, + isTest: Boolean, + categoryMap: Map[String, String], + accountSet: Set[String], + address: Option[Address], + lineItems: List[LineItem], + paymentType: PaymentType + ) extends Invoice2 + object Billed2 { + implicit val schema: Schema.CaseClass10[String, Int, Instant, BigDecimal, Boolean, Map[String, String], Set[ + String + ], Option[Address], List[LineItem], PaymentType, Billed2] = DeriveSchema.gen[Billed2] + + val (id, sequence, dueDate, total, isTest, categoryMap, accountSet, address, lineItems, paymentType) = + ProjectionExpression.accessors[Billed2] + } + + final case class PreBilled2( + id: String, + sequence: Int, + dueDate: Instant, + total: BigDecimal + ) extends Invoice2 + object PreBilled2 { + implicit val schema: Schema.CaseClass4[String, Int, Instant, BigDecimal, PreBilled2] = + DeriveSchema.gen[PreBilled2] + val (id, sequence, dueDate, total) = ProjectionExpression.accessors[PreBilled2] + } + + implicit val schema: Schema[Invoice2] = DeriveSchema.gen[Invoice2] + } + + private val billedInvoice: Billed2 = Billed2( + id = "1", + sequence = 1, + dueDate = Instant.now(), + total = BigDecimal(10.0), + isTest = false, + categoryMap = Map("a" -> "1", "b" -> "2"), + accountSet = Set("account1", "account2"), + address = Some(Address("line1", None, "UK")), + lineItems = List( + LineItem("lineItem1", BigDecimal(1.0), Product("sku1", "a")), + LineItem("lineItem2", BigDecimal(2.0), Product("sku2", "b")) + ), + PaymentType.DebitCard + ) + private val preBilledInvoice: Invoice2.PreBilled2 = Invoice2.PreBilled2( + id = "2", + sequence = 2, + dueDate = Instant.now(), + total = BigDecimal(20.0) + ) + + import zio.dynamodb.KeyConditionExpr + + object Repository { + def genericFindById[A <: Invoice2]( + pkExpr: KeyConditionExpr.PartitionKeyExpr[A, String] + )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Either[DynamoDBError, Invoice2]] = + DynamoDBQuery.get2("table1", pkExpr).execute + + def genericSave[A <: Invoice2]( + invoice: A + )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Option[Invoice2]] = + DynamoDBQuery.put("table1", invoice).execute + } + + private val program = for { + _ <- Repository.genericSave(billedInvoice) + _ <- Repository.genericSave(preBilledInvoice) + found <- Repository.genericFindById(Billed2.id.primaryKey === "1") + found2 <- Repository.genericFindById(PreBilled2.id.primaryKey === "2") + item <- DynamoDBQuery.getItem("table1", PrimaryKey("id" -> "1")).execute + _ <- printLine(s"found=$found") + _ <- printLine(s"found2=$found2") + _ <- printLine(s"item=$item") + } yield () + + override def run = + program.provide(DynamoDBExecutor.test("table1" -> "id")) +} diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index bbe4b7030..6e76bd308 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -22,13 +22,13 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { val ceStudentWithElephant: ConditionExpression[Student with Elephant] = ce2 && elephantCe private val program = for { - _ <- createTable("student", KeySchema("email", "subject"), BillingMode.PayPerRequest)( - AttributeDefinition.attrDefnString("email"), - AttributeDefinition.attrDefnString("subject") - ).execute - _ <- batchWriteFromStream(ZStream(avi, adam)) { student => - put("student", student) - }.runDrain + _ <- createTable("student", KeySchema("email", "subject"), BillingMode.PayPerRequest)( + AttributeDefinition.attrDefnString("email"), + AttributeDefinition.attrDefnString("subject") + ).execute + _ <- batchWriteFromStream(ZStream(avi, adam)) { student => + put("student", student) + }.runDrain /* - must be a scalar - mandatory Partition Key and Optional sort key @@ -39,8 +39,8 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { Student.email.partitionKey === "x" && Student.subject.sortKey === "y" // qualifies as a both a PK and a WhereKeyExpression Student.email.partitionKey === "x" && Student.subject.sortKey > 1 // qualifies as only a WhereKeyExpression */ - found <- get("table", Student.email.set("x"), Student.subject.set("y")).execute - _ = println(s"XXXXXXXX found=$found") +// found <- get("table", Student.email.set("x"), Student.subject.set("y")).execute +// _ = println(s"XXXXXXXX found=$found") _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) From 67677e6f8390eaf130def53e0b67236ed1c43197 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Thu, 20 Jul 2023 06:03:50 +0100 Subject: [PATCH 36/69] make "To" type param covariant --- .../scala/zio/dynamodb/DynamoDBQuery.scala | 13 +++-- .../scala/zio/dynamodb/PartitionKey2.scala | 7 +-- .../main/scala/zio/dynamodb/SortKey2.scala | 30 +++++----- .../scala/zio/dynamodb/keyConditionExpr.scala | 56 ++++++------------- .../src/main/scala/zio/dynamodb/package.scala | 28 ++++++++++ .../proofs/CanSortKeyBeginsWith.scala | 4 +- .../examples/KeyConditionExprExample.scala | 19 +++---- .../scala/zio/dynamodb/examples/Main.scala | 4 +- .../examples/SimpleDecodedExample.scala | 2 +- ...peSafeRoundTripSerialisationExample2.scala | 2 +- 10 files changed, 85 insertions(+), 80 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index ef0511a0b..2c8795003 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -483,6 +483,7 @@ object DynamoDBQuery { ): DynamoDBQuery[Any, Option[Item]] = GetItem(TableName(tableName), key, projections.toList) + // TODO: Avi - make private? def get[A: Schema]( tableName: String, key: PrimaryKey, @@ -494,20 +495,20 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } - def get2[A: Schema, B]( + def get[From: Schema, To]( tableName: String, - partitionKeyExpr: KeyConditionExpr.PartitionKeyExpr[A, B], + partitionKeyExpr: KeyConditionExpr.PartitionKeyExpr[From, To], projections: ProjectionExpression[_, _]* - )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { + )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { val _ = ev get(tableName, partitionKeyExpr.asAttrMap, projections: _*) } - def get2[A: Schema, B]( + def get[From: Schema, To]( tableName: String, - compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[A, B], + compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From, To], projections: ProjectionExpression[_, _]* - )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { + )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { val _ = ev get(tableName, compositeKeyExpr.asAttrMap, projections: _*) } diff --git a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala index 1aa557f73..4c4b88a6f 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala @@ -2,13 +2,12 @@ package zio.dynamodb import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr import zio.dynamodb.proofs.RefersTo - // belongs to the package top level -private[dynamodb] final case class PartitionKey2[-From, To](keyName: String) { self => - def ===[To2: ToAttributeValue, IsPrimaryKey]( +private[dynamodb] final case class PartitionKey2[-From, +To](keyName: String) { self => + def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To, To2]): PartitionKeyExpr[From, To] = { + )(implicit ev: RefersTo[To1, To2]): PartitionKeyExpr[From, To] = { val _ = ev PartitionKeyExpr(self, implicitly[ToAttributeValue[To2]].toAttributeValue(value)) } diff --git a/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala index 8628c8c2f..208518ff0 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala @@ -6,54 +6,56 @@ import zio.dynamodb.proofs.CanSortKeyBeginsWith import zio.dynamodb.KeyConditionExpr.SortKeyExpr import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr -private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self => +private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self => // all comparison ops apply to: Strings, Numbers, Binary values - def ===[To2: ToAttributeValue, IsPrimaryKey](value: To2)(implicit ev: RefersTo[To, To2]): SortKeyExpr[From, To2] = { + def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( + value: To2 + )(implicit ev: RefersTo[To1, To2]): SortKeyExpr[From, To2] = { val _ = ev SortKeyExpr[From, To2]( self.asInstanceOf[SortKey2[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) } - def >[To2: ToAttributeValue, IsPrimaryKey]( + def >[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.GreaterThan( self.asInstanceOf[SortKey2[From, To2]], implicitly(ToAttributeValue[To2]).toAttributeValue(value) ) } - def <[To2: ToAttributeValue, IsPrimaryKey]( + def <[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.LessThan( self.asInstanceOf[SortKey2[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) } - def <>[To2: ToAttributeValue, IsPrimaryKey]( + def <>[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.NotEqual( self.asInstanceOf[SortKey2[From, To2]], implicitly(ToAttributeValue[To2]).toAttributeValue(value) ) } - def <=[To2: ToAttributeValue, IsPrimaryKey]( + def <=[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.LessThanOrEqual( self.asInstanceOf[SortKey2[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) } - def >=[To2: ToAttributeValue, IsPrimaryKey]( + def >=[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To, To2]): ExtendedSortKeyExpr[From, To2] = { + )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.GreaterThanOrEqual( self.asInstanceOf[SortKey2[From, To2]], @@ -69,9 +71,9 @@ private[dynamodb] final case class SortKey2[-From, To](keyName: String) { self = ) // beginsWith applies to: Strings, Binary values - def beginsWith[To2: ToAttributeValue, IsPrimaryKey]( + def beginsWith[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( prefix: To2 - )(implicit ev: CanSortKeyBeginsWith[To, To2]): ExtendedSortKeyExpr[From, To2] = { + )(implicit ev: CanSortKeyBeginsWith[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.BeginsWith[From, To2]( self.asInstanceOf[SortKey2[From, To2]], diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 99462dbd3..57e63448e 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -5,7 +5,7 @@ import zio.dynamodb.PrimaryKey /** * Typesafe KeyConditionExpr/primary key experiment */ -sealed trait KeyConditionExpr[-From] extends Renderable { self => +sealed trait KeyConditionExpr[-From, +To] extends Renderable { self => def render: AliasMapRender[String] } @@ -14,8 +14,8 @@ object KeyConditionExpr { // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - private[dynamodb] final case class PartitionKeyExpr[-From, To](pk: PartitionKey2[From, To], value: AttributeValue) - extends KeyConditionExpr[From] { self => + private[dynamodb] final case class PartitionKeyExpr[-From, +To](pk: PartitionKey2[From, To], value: AttributeValue) + extends KeyConditionExpr[From, To] { self => def &&[From1 <: From, To2](other: SortKeyExpr[From1, To2]): CompositePrimaryKeyExpr[From1, To2] = CompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyExpr[From1, To2]], other) @@ -28,7 +28,7 @@ object KeyConditionExpr { AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } - private[dynamodb] final case class SortKeyExpr[-From, To](sortKey: SortKey2[From, To], value: AttributeValue) { + private[dynamodb] final case class SortKeyExpr[-From, +To](sortKey: SortKey2[From, To], value: AttributeValue) { self => def render2: AliasMapRender[String] = AliasMapRender @@ -36,10 +36,10 @@ object KeyConditionExpr { .map(v => s"${sortKey.keyName} = $v") } - private[dynamodb] final case class CompositePrimaryKeyExpr[-From, To]( + private[dynamodb] final case class CompositePrimaryKeyExpr[-From, +To]( pk: PartitionKeyExpr[From, To], sk: SortKeyExpr[From, To] - ) extends KeyConditionExpr[From] { + ) extends KeyConditionExpr[From, To] { self => def asAttrMap: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) @@ -51,10 +51,10 @@ object KeyConditionExpr { } yield s"$pkStr AND $skStr" } - private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, To]( + private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, +To]( pk: PartitionKeyExpr[From, To], sk: ExtendedSortKeyExpr[From, To] - ) extends KeyConditionExpr[From] { + ) extends KeyConditionExpr[From, To] { self => def render: AliasMapRender[String] = @@ -65,7 +65,7 @@ object KeyConditionExpr { } - sealed trait ExtendedSortKeyExpr[-From, To] { self => + sealed trait ExtendedSortKeyExpr[-From, +To] { self => def render2: AliasMapRender[String] = self match { case ExtendedSortKeyExpr.GreaterThan(sk, value) => @@ -106,47 +106,23 @@ object KeyConditionExpr { } object ExtendedSortKeyExpr { - private[dynamodb] final case class GreaterThan[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class GreaterThan[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class LessThan[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class LessThan[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class NotEqual[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class NotEqual[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class LessThanOrEqual[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class LessThanOrEqual[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class GreaterThanOrEqual[From, To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class GreaterThanOrEqual[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class Between[From, To]( + private[dynamodb] final case class Between[From, +To]( left: SortKey2[From, To], min: AttributeValue, max: AttributeValue ) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class BeginsWith[From, To](left: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class BeginsWith[From, +To](left: SortKey2[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] } } - -// object Foo { -// import zio.schema.Schema -// import zio.dynamodb.DynamoDBQuery.get - -// def get2[A: Schema, B]( -// tableName: String, -// partitionKeyExpr: KeyConditionExpr.PartitionKeyExpr[A, B], -// projections: ProjectionExpression[_, _]* -// )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { -// val _ = ev -// get(tableName, partitionKeyExpr.asAttrMap, projections: _*) -// } - -// def get2[A: Schema, B]( -// tableName: String, -// compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[A, B], -// projections: ProjectionExpression[_, _]* -// )(implicit ev: IsPrimaryKey[B]): DynamoDBQuery[A, Either[DynamoDBError, A]] = { -// val _ = ev -// get(tableName, compositeKeyExpr.asAttrMap, projections: _*) -// } - -// } diff --git a/dynamodb/src/main/scala/zio/dynamodb/package.scala b/dynamodb/src/main/scala/zio/dynamodb/package.scala index d0776ff0a..dcd3a9eb6 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/package.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/package.scala @@ -2,6 +2,7 @@ package zio import zio.schema.Schema import zio.stream.{ ZSink, ZStream } +import zio.dynamodb.proofs.IsPrimaryKey package object dynamodb { // Filter expression is the same as a ConditionExpression but when used with Query but does not allow key attributes @@ -126,4 +127,31 @@ package object dynamodb { } .flattenChunks + + def batchReadFromStream2[R, A, From: Schema, To: IsPrimaryKey]( + tableName: String, + stream: ZStream[R, Throwable, A], + mPar: Int = 10 + )( + pk: A => KeyConditionExpr.PartitionKeyExpr[From, To] + ): ZStream[R with DynamoDBExecutor, Throwable, Either[DynamoDBError.DecodingError, (A, Option[From])]] = + stream + .aggregateAsync(ZSink.collectAllN[A](100)) + .mapZIOPar(mPar) { chunk => + val batchGetItem: DynamoDBQuery[From, Chunk[Either[DynamoDBError.DecodingError, (A, Option[From])]]] = DynamoDBQuery + .forEach(chunk) { a => + DynamoDBQuery.get(tableName, pk(a)).map { + case Right(b) => Right((a, Some(b))) + case Left(DynamoDBError.ValueNotFound(_)) => Right((a, None)) + case Left(e @ DynamoDBError.DecodingError(_)) => Left(e) + } + } + .map(Chunk.fromIterable) + for { + r <- ZIO.environment[DynamoDBExecutor] + list <- batchGetItem.execute.provideEnvironment(r) + } yield list + } + .flattenChunks + } diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala index bbda2a893..8471b06a2 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala @@ -6,13 +6,13 @@ import scala.annotation.implicitNotFound @implicitNotFound( "Fields of type ${X} has 'beginsWith' argument of type ${A} - they must be the same type" ) -sealed trait CanSortKeyBeginsWith[X, -A] +sealed trait CanSortKeyBeginsWith[X, -A] // TODO: Avi - If X was contavariant we would not need all examples trait CanSortKeyBeginsWith0 extends CanSortKeyBeginsWith1 { implicit def unknownRight[X]: CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] = new CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] {} } trait CanSortKeyBeginsWith1 { - implicit def bytes2[A]: CanSortKeyBeginsWith[Array[Byte], Array[Byte]] = + implicit def bytes2[A]: CanSortKeyBeginsWith[Array[Byte], Array[Byte]] = // bytes2[X <: Iterable[Byte]] would be better new CanSortKeyBeginsWith[Array[Byte], Array[Byte]] {} implicit def bytes3[A]: CanSortKeyBeginsWith[List[Byte], List[Byte]] = new CanSortKeyBeginsWith[List[Byte], List[Byte]] {} diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index 6b42f5e5b..ed57591ef 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -1,6 +1,5 @@ package zio.dynamodb.examples -import zio.dynamodb.KeyConditionExpr import zio.dynamodb.ProjectionExpression import zio.schema.Schema @@ -15,15 +14,15 @@ object KeyConditionExprExample extends App { // typesafe API constructors only expose PartitionKeyEprn - def whereKey[From](k: KeyConditionExpr[From]) = - k match { - // PartitionKeyExpr - case PartitionKeyExpr(pk, value) => println(s"pk=$pk, value=$value") - // CompositePrimaryKeyExpr - case CompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") - // ExtendedCompositePrimaryKeyExpr - case ExtendedCompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") - } + // def whereKey[From](k: KeyConditionExpr[From]) = + // k match { + // // PartitionKeyExpr + // case PartitionKeyExpr(pk, value) => println(s"pk=$pk, value=$value") + // // CompositePrimaryKeyExpr + // case CompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") + // // ExtendedCompositePrimaryKeyExpr + // case ExtendedCompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") + // } // in low level - non type safe land //val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" diff --git a/examples/src/main/scala/zio/dynamodb/examples/Main.scala b/examples/src/main/scala/zio/dynamodb/examples/Main.scala index 8ed52afcf..159020d33 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/Main.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/Main.scala @@ -2,7 +2,7 @@ package zio.dynamodb.examples import zio.aws.core.config import zio.aws.{ dynamodb, netty } -import zio.dynamodb.DynamoDBQuery.{ get2, put } +import zio.dynamodb.DynamoDBQuery.{ get, put } import zio.dynamodb.{ DynamoDBExecutor } import zio.schema.{ DeriveSchema, Schema } import zio.ZIOAppDefault @@ -20,7 +20,7 @@ object Main extends ZIOAppDefault { private val program = for { _ <- put("personTable", examplePerson).execute - person <- get2("personTable", Person.id.primaryKey === 1).execute + person <- get("personTable", Person.id.primaryKey === 1).execute _ <- zio.Console.printLine(s"hello $person") } yield () diff --git a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala index e6a8d62e1..579e31b2d 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala @@ -24,7 +24,7 @@ object SimpleDecodedExample extends ZIOAppDefault { private val program = for { _ <- put("table1", NestedCaseClass2(id = 1, SimpleCaseClass3(2, "Avi", flag = true))).execute // Save case class to DB - caseClass <- get2("table1", NestedCaseClass2.id.primaryKey === 2).execute // read case class from DB + caseClass <- get("table1", NestedCaseClass2.id.primaryKey === 2).execute // read case class from DB _ <- printLine(s"get: found $caseClass") either <- scanSome[NestedCaseClass2]("table1", 10).execute _ <- printLine(s"scanSome: found $either") diff --git a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala index 3bd87420d..5b864b8b9 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala @@ -110,7 +110,7 @@ object TypeSafeRoundTripSerialisationExample2 extends ZIOAppDefault { def genericFindById[A <: Invoice2]( pkExpr: KeyConditionExpr.PartitionKeyExpr[A, String] )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Either[DynamoDBError, Invoice2]] = - DynamoDBQuery.get2("table1", pkExpr).execute + DynamoDBQuery.get("table1", pkExpr).execute def genericSave[A <: Invoice2]( invoice: A From b17cf96f463a348a5a6feaad1c6ce46534b0c62c Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Thu, 20 Jul 2023 06:27:38 +0100 Subject: [PATCH 37/69] cleanup TypeSafeRoundTripSerialisationExample --- ...ypeSafeRoundTripSerialisationExample.scala | 54 ++++++- ...peSafeRoundTripSerialisationExample2.scala | 134 ------------------ 2 files changed, 48 insertions(+), 140 deletions(-) delete mode 100644 examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala diff --git a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala index 0f095b0d7..5e0dd7c1f 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala @@ -8,6 +8,7 @@ import zio.dynamodb.examples.TypeSafeRoundTripSerialisationExample.Invoice.{ Billed, LineItem, PaymentType, + PreBilled, Product } import zio.dynamodb.{ DynamoDBExecutor, DynamoDBQuery, PrimaryKey } @@ -15,6 +16,10 @@ import zio.schema.annotation.{ caseName, discriminatorName } import zio.schema.{ DeriveSchema, Schema } import java.time.Instant +import zio.dynamodb.ProjectionExpression +import zio.ZIO +import zio.dynamodb.DynamoDBError + object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { @@ -48,17 +53,31 @@ object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { lineItems: List[LineItem], paymentType: PaymentType ) extends Invoice + object Billed { + implicit val schema: Schema.CaseClass10[String, Int, Instant, BigDecimal, Boolean, Map[String, String], Set[ + String + ], Option[Address], List[LineItem], PaymentType, Billed] = DeriveSchema.gen[Billed] + + val (id, sequence, dueDate, total, isTest, categoryMap, accountSet, address, lineItems, paymentType) = + ProjectionExpression.accessors[Billed] + } + final case class PreBilled( id: String, sequence: Int, dueDate: Instant, total: BigDecimal ) extends Invoice + object PreBilled { + implicit val schema: Schema.CaseClass4[String, Int, Instant, BigDecimal, PreBilled] = + DeriveSchema.gen[PreBilled] + val (id, sequence, dueDate, total) = ProjectionExpression.accessors[PreBilled] + } implicit val schema: Schema[Invoice] = DeriveSchema.gen[Invoice] } - private val invoice1 = Billed( + private val billedInvoice: Billed = Billed( id = "1", sequence = 1, dueDate = Instant.now(), @@ -73,13 +92,36 @@ object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { ), PaymentType.DebitCard ) + private val preBilledInvoice: Invoice.PreBilled = Invoice.PreBilled( + id = "2", + sequence = 2, + dueDate = Instant.now(), + total = BigDecimal(20.0) + ) + + import zio.dynamodb.KeyConditionExpr + + object Repository { + def genericFindById[A <: Invoice]( + pkExpr: KeyConditionExpr.PartitionKeyExpr[A, String] + )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Either[DynamoDBError, Invoice]] = + DynamoDBQuery.get("table1", pkExpr).execute + + def genericSave[A <: Invoice]( + invoice: A + )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Option[Invoice]] = + DynamoDBQuery.put("table1", invoice).execute + } private val program = for { - _ <- DynamoDBQuery.put[Invoice]("table1", invoice1).execute - found <- DynamoDBQuery.get[Invoice]("table1", PrimaryKey("id" -> "1")).execute - item <- DynamoDBQuery.getItem("table1", PrimaryKey("id" -> "1")).execute - _ <- printLine(s"found=$found") - _ <- printLine(s"item=$item") + _ <- Repository.genericSave(billedInvoice) + _ <- Repository.genericSave(preBilledInvoice) + found <- Repository.genericFindById(Billed.id.primaryKey === "1") + found2 <- Repository.genericFindById(PreBilled.id.primaryKey === "2") + item <- DynamoDBQuery.getItem("table1", PrimaryKey("id" -> "1")).execute + _ <- printLine(s"found=$found") + _ <- printLine(s"found2=$found2") + _ <- printLine(s"item=$item") } yield () override def run = diff --git a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala deleted file mode 100644 index 5b864b8b9..000000000 --- a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample2.scala +++ /dev/null @@ -1,134 +0,0 @@ -package zio.dynamodb.examples - -import zio.Console.printLine -import zio.ZIOAppDefault -import zio.dynamodb.Annotations.enumOfCaseObjects -import zio.dynamodb.examples.TypeSafeRoundTripSerialisationExample2.Invoice2.{ - Address, - Billed2, - LineItem, - PaymentType, - PreBilled2, - Product -} -import zio.dynamodb.{ DynamoDBExecutor, DynamoDBQuery, PrimaryKey } -import zio.schema.annotation.{ caseName, discriminatorName } -import zio.schema.{ DeriveSchema, Schema } - -import java.time.Instant -import zio.dynamodb.ProjectionExpression -import zio.ZIO -import zio.dynamodb.DynamoDBError - -/* -[info] item=Some(AttrMap(HashMap(lineItems -> List(Chunk(Map(Map(String(product) -> Map(Map(String(name) -> String(a), String(sku) -> String(sku1))), String(price) -> Number(1.0), String(itemId) -> String(lineItem1))),Map(Map(String(product) -> Map(Map(String(name) -> String(b), String(sku) -> String(sku2))), String(price) -> Number(2.0), String(itemId) -> String(lineItem2))))), categoryMap -> Map(Map(String(a) -> String(1), String(b) -> String(2))), paymentType -> String(debit), total -> Number(10.0), id -> String(1), sequence -> Number(1), isTest -> Bool(false), dueDate -> String(2023-07-16T08:50:00.898031Z), address -> Map(Map(String(country) -> String(UK), String(line1) -> String(line1))), accountSet -> StringSet(Set(account1, account2))))) - */ - -//import TypeSafeRoundTripSerialisationExample2.Invoice2.{ Billed2, PreBilled2 } - -object TypeSafeRoundTripSerialisationExample2 extends ZIOAppDefault { - - @discriminatorName("invoiceType") - sealed trait Invoice2 { - def id: String - } - object Invoice2 { - @enumOfCaseObjects - sealed trait PaymentType - object PaymentType { - @caseName("debit") - case object DebitCard extends PaymentType - @caseName("credit") - case object CreditCard extends PaymentType - } - - final case class Address(line1: String, line2: Option[String], country: String) - final case class Product(sku: String, name: String) - final case class LineItem(itemId: String, price: BigDecimal, product: Product) - - final case class Billed2( - id: String, - sequence: Int, - dueDate: Instant, - total: BigDecimal, - isTest: Boolean, - categoryMap: Map[String, String], - accountSet: Set[String], - address: Option[Address], - lineItems: List[LineItem], - paymentType: PaymentType - ) extends Invoice2 - object Billed2 { - implicit val schema: Schema.CaseClass10[String, Int, Instant, BigDecimal, Boolean, Map[String, String], Set[ - String - ], Option[Address], List[LineItem], PaymentType, Billed2] = DeriveSchema.gen[Billed2] - - val (id, sequence, dueDate, total, isTest, categoryMap, accountSet, address, lineItems, paymentType) = - ProjectionExpression.accessors[Billed2] - } - - final case class PreBilled2( - id: String, - sequence: Int, - dueDate: Instant, - total: BigDecimal - ) extends Invoice2 - object PreBilled2 { - implicit val schema: Schema.CaseClass4[String, Int, Instant, BigDecimal, PreBilled2] = - DeriveSchema.gen[PreBilled2] - val (id, sequence, dueDate, total) = ProjectionExpression.accessors[PreBilled2] - } - - implicit val schema: Schema[Invoice2] = DeriveSchema.gen[Invoice2] - } - - private val billedInvoice: Billed2 = Billed2( - id = "1", - sequence = 1, - dueDate = Instant.now(), - total = BigDecimal(10.0), - isTest = false, - categoryMap = Map("a" -> "1", "b" -> "2"), - accountSet = Set("account1", "account2"), - address = Some(Address("line1", None, "UK")), - lineItems = List( - LineItem("lineItem1", BigDecimal(1.0), Product("sku1", "a")), - LineItem("lineItem2", BigDecimal(2.0), Product("sku2", "b")) - ), - PaymentType.DebitCard - ) - private val preBilledInvoice: Invoice2.PreBilled2 = Invoice2.PreBilled2( - id = "2", - sequence = 2, - dueDate = Instant.now(), - total = BigDecimal(20.0) - ) - - import zio.dynamodb.KeyConditionExpr - - object Repository { - def genericFindById[A <: Invoice2]( - pkExpr: KeyConditionExpr.PartitionKeyExpr[A, String] - )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Either[DynamoDBError, Invoice2]] = - DynamoDBQuery.get("table1", pkExpr).execute - - def genericSave[A <: Invoice2]( - invoice: A - )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Option[Invoice2]] = - DynamoDBQuery.put("table1", invoice).execute - } - - private val program = for { - _ <- Repository.genericSave(billedInvoice) - _ <- Repository.genericSave(preBilledInvoice) - found <- Repository.genericFindById(Billed2.id.primaryKey === "1") - found2 <- Repository.genericFindById(PreBilled2.id.primaryKey === "2") - item <- DynamoDBQuery.getItem("table1", PrimaryKey("id" -> "1")).execute - _ <- printLine(s"found=$found") - _ <- printLine(s"found2=$found2") - _ <- printLine(s"item=$item") - } yield () - - override def run = - program.provide(DynamoDBExecutor.test("table1" -> "id")) -} From 9a85ba7d697069f85930e3dd54f19103e379aabb Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Thu, 20 Jul 2023 07:41:50 +0100 Subject: [PATCH 38/69] improve CanSortKeyBeginsWith variance --- .../proofs/CanSortKeyBeginsWith.scala | 13 ++-- .../zio/dynamodb/proofs/IsPrimaryKey.scala | 1 + .../examples/KeyConditionExprExample.scala | 63 +++---------------- 3 files changed, 12 insertions(+), 65 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala index 8471b06a2..8c818206c 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala @@ -6,21 +6,16 @@ import scala.annotation.implicitNotFound @implicitNotFound( "Fields of type ${X} has 'beginsWith' argument of type ${A} - they must be the same type" ) -sealed trait CanSortKeyBeginsWith[X, -A] // TODO: Avi - If X was contavariant we would not need all examples +sealed trait CanSortKeyBeginsWith[-X, -A] trait CanSortKeyBeginsWith0 extends CanSortKeyBeginsWith1 { implicit def unknownRight[X]: CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] = new CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] {} } trait CanSortKeyBeginsWith1 { - implicit def bytes2[A]: CanSortKeyBeginsWith[Array[Byte], Array[Byte]] = // bytes2[X <: Iterable[Byte]] would be better - new CanSortKeyBeginsWith[Array[Byte], Array[Byte]] {} - implicit def bytes3[A]: CanSortKeyBeginsWith[List[Byte], List[Byte]] = - new CanSortKeyBeginsWith[List[Byte], List[Byte]] {} - implicit def bytes[A]: CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] = - new CanSortKeyBeginsWith[Iterable[Byte], Iterable[Byte]] {} - // TODO: Avi - other collection types + implicit def bytes[A <: Iterable[Byte]]: CanSortKeyBeginsWith[A, A] = + new CanSortKeyBeginsWith[A, A] {} - implicit def string: CanSortKeyBeginsWith[String, String] = new CanSortKeyBeginsWith[String, String] {} + implicit def string: CanSortKeyBeginsWith[String, String] = new CanSortKeyBeginsWith[String, String] {} } object CanSortKeyBeginsWith extends CanSortKeyBeginsWith0 { implicit def unknownLeft[X]: CanSortKeyBeginsWith[ProjectionExpression.Unknown, X] = diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index ca85711f6..7602bac85 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -15,5 +15,6 @@ object IsPrimaryKey { // binary data implicit val binaryIsPrimaryKey = new IsPrimaryKey[Iterable[Byte]] {} implicit val binaryIsPrimaryKey2 = new IsPrimaryKey[List[Byte]] {} + implicit val binaryIsPrimaryKey3 = new IsPrimaryKey[Vector[Byte]] {} // TODO: Avi - other collection types } diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index ed57591ef..a67f30638 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -9,36 +9,8 @@ object KeyConditionExprExample extends App { import zio.dynamodb.KeyConditionExpr._ import zio.dynamodb.KeyConditionExpr.SortKeyExpr - - // DynamoDbQuery's still use PrimaryKey - // typesafe API constructors only expose PartitionKeyEprn - - - // def whereKey[From](k: KeyConditionExpr[From]) = - // k match { - // // PartitionKeyExpr - // case PartitionKeyExpr(pk, value) => println(s"pk=$pk, value=$value") - // // CompositePrimaryKeyExpr - // case CompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") - // // ExtendedCompositePrimaryKeyExpr - // case ExtendedCompositePrimaryKeyExpr(pk, sk) => println(s"pk=$pk, sk=$sk") - // } - - // in low level - non type safe land - //val x1: PartitionKeyExpr[Nothing] = PartitionKey("email") === "x" - // val x2: SortKeyEprn[Nothing] = SortKey("subject") === "y" - // val x3: CompositePrimaryKeyExpr[Nothing] = x1 && x2 - // val x4 = PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") === "y" - // val x5: ExtendedCompositePrimaryKeyExpr[Nothing] = - // PartitionKey[Nothing]("email") === "x" && SortKey[Nothing]("subject") > "y" - - // val y0: PartitionKeyExpr[Any] = PartitionKey("email") === "x" - // val y1: CompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") === "y" - // val y2: ExtendedCompositePrimaryKeyExpr[Any] = PartitionKey("email") === "x" && SortKey("subject") > "y" - import zio.dynamodb.ProjectionExpression.$ -// TODO: Avi -//inferred type arguments [String] do not conform to method ==='s type parameter bounds [To2 <: zio.dynamodb.ProjectionExpression.Unknown] + val x6: CompositePrimaryKeyExpr[Any, String] = $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey === "y" val x7 = $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey > 1 val x8: ExtendedCompositePrimaryKeyExpr[Any, Int] = @@ -51,10 +23,11 @@ object KeyConditionExprExample extends App { implicit val schema: Schema.CaseClass3[String, String, Int, Elephant] = DeriveSchema.gen[Elephant] val (email, subject, age) = ProjectionExpression.accessors[Elephant] } - final case class Student(email: String, subject: String, age: Long, binary: List[Byte]) + final case class Student(email: String, subject: String, age: Long, binary: List[Byte], binary2: Vector[Byte]) object Student { - implicit val schema: Schema.CaseClass4[String, String, Long, List[Byte], Student] = DeriveSchema.gen[Student] - val (email, subject, age, binary) = ProjectionExpression.accessors[Student] + implicit val schema: Schema.CaseClass5[String, String, Long, List[Byte], Vector[Byte], Student] = + DeriveSchema.gen[Student] + val (email, subject, age, binary, binary2) = ProjectionExpression.accessors[Student] } val pk: PartitionKeyExpr[Student, String] = Student.email.primaryKey === "x" @@ -73,32 +46,10 @@ object KeyConditionExprExample extends App { Student.email.primaryKey === "x" && Student.subject.sortKey.beginsWith("1") val pkAndSkExtended5 = Student.email.primaryKey === "x" && Student.binary.sortKey.beginsWith(List(1.toByte)) + val pkAndSkExtended6 = + Student.email.primaryKey === "x" && Student.binary2.sortKey.beginsWith(List(1.toByte)) // TODO: Avi - fix ToAttrubuteValue interop with Array[Byte] - // GetItem Query will have three overridden versions - // 1) takes AttrMap/PriamaryKey - for users of low level API - // 2) takes PartitionKeyExpr - internally this can be converted to AttrMap/PriamaryKey - // 3) takes CompositePrimaryKeyExpr - internally this can b converted to AttrMap/PriamaryKey - - // whereKey function (for Query) - // 1) takes a KeyConditionExpression - // 2) down side is that users of low level API will have to construct case class instances manually - but they have to do that now anyway - // println(asPk(pkAndSk)) - // println(pkAndSkExtended) - - // Render requirements val (aliasMap, s) = pkAndSkExtended1.render.execute println(s"aliasMap=$aliasMap, s=$s") } - -object Foo { - final case class Student(email: String, subject: String, age: Int) - object Student { - implicit val schema: Schema.CaseClass3[String, String, Int, Student] = DeriveSchema.gen[Student] - val (email, subject, age) = ProjectionExpression.accessors[Student] - } - - val pkAndSk = - Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - -} From e7f623e59ba1602be0672765226a435a7da1fbf4 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 07:24:09 +0100 Subject: [PATCH 39/69] wired in keyConditionExpr field into queries --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 118 +++------------ .../zio/dynamodb/DynamoDBExecutorImpl.scala | 4 +- .../scala/zio/dynamodb/DynamoDBQuery.scala | 140 ++++++++++-------- .../dynamodb/TestDynamoDBExecutorImpl.scala | 4 +- .../examples/KeyConditionExprExample.scala | 3 + .../examples/QueryAndScanExamples.scala | 10 +- .../StudentZioDynamoDbExampleWithOptics.scala | 4 +- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 2 +- .../TypeSafeAPIExampleWithDiscriminator.scala | 2 +- ...peSafeAPIExampleWithoutDiscriminator.scala | 2 +- 10 files changed, 115 insertions(+), 174 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index e89c386fc..97120971e 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -7,10 +7,6 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider import software.amazon.awssdk.services.dynamodb.model.{ DynamoDbException, IdempotentParameterMismatchException } import zio.dynamodb.UpdateExpression.Action.SetAction import zio.dynamodb.UpdateExpression.SetOperand -import zio.dynamodb.PartitionKeyExpression.PartitionKey -import zio.dynamodb.KeyConditionExpression.partitionKey -import zio.dynamodb.KeyConditionExpression.sortKey -import zio.dynamodb.SortKeyExpression.SortKey import zio.aws.{ dynamodb, netty } import zio._ import zio.dynamodb.DynamoDBQuery._ @@ -118,12 +114,6 @@ object LiveSpec extends ZIOSpecDefault { AttributeDefinition.attrDefnString(name) ) - def sortKeyStringTableWithKeywords(tableName: String) = - createTable(tableName, KeySchema("and", "source"), BillingMode.PayPerRequest)( - AttributeDefinition.attrDefnString("and"), - AttributeDefinition.attrDefnString("source") - ) - private def managedTable(tableDefinition: String => CreateTable) = ZIO .acquireRelease( @@ -158,17 +148,6 @@ object LiveSpec extends ZIOSpecDefault { } } - def withPkKeywordsTable( - f: String => ZIO[DynamoDBExecutor, Throwable, TestResult] - ) = - ZIO.scoped { - managedTable(sortKeyStringTableWithKeywords).flatMap { table => - for { - result <- f(table.value) - } yield result - } - } - def withDefaultAndNumberTables( f: (String, String) => ZIO[DynamoDBExecutor, Throwable, TestResult] ) = @@ -198,65 +177,8 @@ object LiveSpec extends ZIOSpecDefault { val (id, num, ttl) = ProjectionExpression.accessors[ExpressionAttrNames] } - final case class ExpressionAttrNamesPkKeywords(and: String, source: String, ttl: Option[Long]) - object ExpressionAttrNamesPkKeywords { - implicit val schema: Schema.CaseClass3[String, String, Option[Long], ExpressionAttrNamesPkKeywords] = - DeriveSchema.gen[ExpressionAttrNamesPkKeywords] - val (and, source, ttl) = ProjectionExpression.accessors[ExpressionAttrNamesPkKeywords] - } - val mainSuite: Spec[TestEnvironment, Any] = suite("live test")( - suite("key words in Key Condition Expressions")( - test("queryAll should handle keywords in primary key names using high level API") { - withPkKeywordsTable { tableName => - val query = DynamoDBQuery - .queryAll[ExpressionAttrNamesPkKeywords](tableName) - .whereKey( - ExpressionAttrNamesPkKeywords.and === "and1" && ExpressionAttrNamesPkKeywords.source === "source1" - ) - .filter(ExpressionAttrNamesPkKeywords.ttl.notExists) - query.execute.flatMap(_.runDrain).exit.map { result => - assert(result)(succeeds(isUnit)) - } - } - }, - test("queryAll should handle keywords in primary key name using low level API") { - withPkKeywordsTable { tableName => - val query = DynamoDBQuery - .queryAll[ExpressionAttrNamesPkKeywords](tableName) - .whereKey(partitionKey("and") === "and1" && sortKey("source") === "source1") - .filter(ExpressionAttrNamesPkKeywords.ttl.notExists) - query.execute.flatMap(_.runDrain).exit.map { result => - assert(result)(succeeds(isUnit)) - } - } - }, - test("querySome should handle keywords in primary key name using high level API") { - withPkKeywordsTable { tableName => - val query = DynamoDBQuery - .querySome[ExpressionAttrNamesPkKeywords](tableName, 1) - .whereKey( - ExpressionAttrNamesPkKeywords.and === "and1" && ExpressionAttrNamesPkKeywords.source === "source1" - ) - .filter(ExpressionAttrNamesPkKeywords.ttl.notExists) - for { - result <- query.execute - } yield assert(result._1)(hasSize(equalTo(0))) - } - }, - test("querySome should handle keywords in primary key name using low level API") { - withPkKeywordsTable { tableName => - val query = DynamoDBQuery - .querySome[ExpressionAttrNames](tableName, 1) - .whereKey(partitionKey("and") === "and1" && sortKey("source") === "source1") - .filter(ExpressionAttrNames.ttl.notExists) - for { - result <- query.execute - } yield assert(result._1)(hasSize(equalTo(0))) - } - } - ), suite("keywords in expression attribute names")( suite("using high level api")( test("scanAll should handle keyword") { @@ -273,7 +195,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .queryAll[ExpressionAttrNames](tableName) - .whereKey(ExpressionAttrNames.id === "id") + .whereKey(ExpressionAttrNames.id.primaryKey === "id") .filter(ExpressionAttrNames.ttl.notExists) query.execute.flatMap(_.runDrain).exit.map { result => assert(result)(succeeds(isUnit)) @@ -297,7 +219,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .querySome[ExpressionAttrNames](tableName, 1) - .whereKey(PartitionKey(id) === second && SortKey(number) > 0) + .whereKey($(id).primaryKey === second && $(number).sortKey > 0) .filter(ExpressionAttrNames.ttl.notExists) for { @@ -438,7 +360,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .queryAllItem(tableName) - .whereKey($("id") === "id") + .whereKey($("id").primaryKey === "id") .filter($("ttl").notExists) query.execute.flatMap(_.runDrain).exit.map { result => assert(result)(succeeds(isUnit)) @@ -449,7 +371,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .querySomeItem(tableName, 1) - .whereKey($("id") === "id") + .whereKey($("id").primaryKey === "id") .filter($("ttl").notExists) query.execute.exit.map { result => assert(result.isSuccess)(isTrue) @@ -460,7 +382,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .querySomeItem(tableName, 1, $("ttl")) - .whereKey($("id") === "id") + .whereKey($("id").primaryKey === "id") query.execute.exit.map { result => assert(result.isSuccess)(isTrue) } @@ -683,7 +605,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = queryAllItem(tableName, $(name), $("ttl")).whereKey( - PartitionKey(id) === first && SortKey(number) > 0 + $(id).primaryKey === first && $(number).sortKey > 0 ) query.execute.flatMap(_.runDrain).map { _ => @@ -695,7 +617,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name), $("ttl")) - .whereKey(PartitionKey(id) === first && SortKey(number) > 0) + .whereKey($(id).primaryKey === first && $(number).sortKey > 0) .execute .map(_._1) } yield assert(chunk)( @@ -707,7 +629,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey(PartitionKey(id) === first && SortKey(number) < 2) + .whereKey($(id).primaryKey === first && $(number).sortKey < 2) .execute .map(_._1) } yield assert(chunk)( @@ -719,7 +641,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey(PartitionKey(id) === first && SortKey(number) > 0) + .whereKey($(id).primaryKey === first && $(number).sortKey > 0) .execute .map(_._1) } yield assert(chunk)( @@ -732,7 +654,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey(PartitionKey(id) === first && SortKey(number) >= 4) + .whereKey($(id).primaryKey === first && $(number).sortKey >= 4) .execute .map(_._1) } yield assert(chunk)( @@ -744,7 +666,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey(PartitionKey(id) === first && SortKey(number) <= 4) + .whereKey($(id).primaryKey === first && $(number).sortKey <= 4) .execute .map(_._1) } yield assert(chunk)( @@ -756,7 +678,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey(PartitionKey(id) === "nowhere" && SortKey(number) > 0) + .whereKey($(id).primaryKey === "nowhere" && $(number).sortKey > 0) .execute .map(_._1) } yield assert(chunk)(isEmpty) @@ -766,7 +688,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 1, $(name)) - .whereKey(PartitionKey(id) === first) + .whereKey($(id).primaryKey === first) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(Item(name -> avi)))) @@ -776,7 +698,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 3, $(name)) - .whereKey(PartitionKey(id) === first) + .whereKey($(id).primaryKey === first) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(Item(name -> avi), Item(name -> avi2), Item(name -> avi3)))) @@ -786,7 +708,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 4, $(name)) - .whereKey(PartitionKey(id) === first) + .whereKey($(id).primaryKey === first) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(Item(name -> avi), Item(name -> avi2), Item(name -> avi3)))) @@ -796,11 +718,11 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { startKey <- querySomeItem(tableName, 2, $(id), $(number)) - .whereKey(PartitionKey(id) === first) + .whereKey($(id).primaryKey === first) .execute .map(_._2) chunk <- querySomeItem(tableName, 5, $(name)) - .whereKey(PartitionKey(id) === first) + .whereKey($(id).primaryKey === first) .startKey(startKey) .execute .map(_._1) @@ -811,7 +733,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { stream <- queryAllItem(tableName) - .whereKey(PartitionKey(id) === second) + .whereKey($(id).primaryKey === second) .execute chunk <- stream.run(ZSink.collectAll[Item]) } yield assert(chunk)( @@ -834,7 +756,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey(PartitionKey(id) === first && SortKey(number).between(3, 8)) + .whereKey($(id).primaryKey === first && $(number).sortKey.between(3, 8)) .execute .map(_._1) } yield assert(chunk)( @@ -849,7 +771,7 @@ object LiveSpec extends ZIOSpecDefault { for { _ <- putItem(tableName, stringSortKeyItem).execute chunk <- querySomeItem(tableName, 10) - .whereKey(PartitionKey(id) === adam && SortKey(name).beginsWith("ad")) + .whereKey($(id).primaryKey === adam && $(name).sortKey.beginsWith("ad")) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(stringSortKeyItem))) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala index cf135a325..047c92213 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala @@ -773,7 +773,7 @@ case object DynamoDBExecutorImpl { def awsQueryRequest(queryAll: QueryAll): QueryRequest = { val (aliasMap, (maybeFilterExpr, maybeKeyExpr, projections)) = (for { filter <- AliasMapRender.collectAll(queryAll.filterExpression.map(_.render)) - keyExpr <- AliasMapRender.collectAll(queryAll.keyConditionExpression.map(_.render)) + keyExpr <- AliasMapRender.collectAll(queryAll.keyConditionExpression2.map(_.render)) projections <- AliasMapRender.forEach(queryAll.projections) } yield (filter, keyExpr, projections)).execute @@ -797,7 +797,7 @@ case object DynamoDBExecutorImpl { private def awsQueryRequest(querySome: QuerySome): QueryRequest = { val (aliasMap, (maybeFilterExpr, maybeKeyExpr, projections)) = (for { filter <- AliasMapRender.collectAll(querySome.filterExpression.map(_.render)) - keyExpr <- AliasMapRender.collectAll(querySome.keyConditionExpression.map(_.render)) + keyExpr <- AliasMapRender.collectAll(querySome.keyConditionExpression2.map(_.render)) projections <- AliasMapRender.forEach(querySome.projections) } yield (filter, keyExpr, projections)).execute diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 2c8795003..a0a2cce35 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -1,7 +1,7 @@ package zio.dynamodb import zio.dynamodb.DynamoDBError.ValueNotFound -import zio.dynamodb.proofs.{ CanFilter, CanWhere, CanWhereKey } +import zio.dynamodb.proofs.{ CanFilter, CanWhere } import zio.dynamodb.DynamoDBQuery.BatchGetItem.TableGet import zio.dynamodb.DynamoDBQuery.BatchWriteItem.{ Delete, Put } import zio.dynamodb.DynamoDBQuery.{ @@ -317,13 +317,8 @@ sealed trait DynamoDBQuery[-In, +Out] { self => def selectSpecificAttributes: DynamoDBQuery[In, Out] = select(Select.SpecificAttributes) def selectCount: DynamoDBQuery[In, Out] = select(Select.Count) - /** - * Adds a KeyConditionExpression to a DynamoDBQuery. Example: - * {{{ - * val newQuery = query.whereKey(partitionKey("email") === "avi@gmail.com" && sortKey("subject") === "maths") - * }}} - */ - def whereKey(keyConditionExpression: KeyConditionExpression): DynamoDBQuery[In, Out] = + // TOOD: Avi - maybe we can use In? + def whereKey[From, To](keyConditionExpression: KeyConditionExpr[From, To]): DynamoDBQuery[In, Out] = self match { case Zip(left, right, zippable) => Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) @@ -331,45 +326,65 @@ sealed trait DynamoDBQuery[-In, +Out] { self => case Absolve(query) => Absolve(query.whereKey(keyConditionExpression)) case s: QuerySome => - s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + s.copy(keyConditionExpression2 = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] case s: QueryAll => - s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + s.copy(keyConditionExpression2 = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] case _ => self } - /** - * Adds a KeyConditionExpression from a ConditionExpression to a DynamoDBQuery - * Must be in the form of ` && ` where format of `` is: - * {{{ === }}} - * and the format of `` is: - * {{{ }}} where op can be one of `===`, `>`, `>=`, `<`, `<=`, `between`, `beginsWith` - * - * Example using type safe API: - * {{{ - * // email and subject are partition and sort keys respectively - * val (email, subject, enrollmentDate, payment) = ProjectionExpression.accessors[Student] - * // ... - * val newQuery = query.whereKey(email === "avi@gmail.com" && subject === "maths") - * }}} - */ - def whereKey[B]( - conditionExpression: ConditionExpression[B] - )(implicit ev: CanWhereKey[B, Out]): DynamoDBQuery[In, Out] = { - val _ = ev - val keyConditionExpression: KeyConditionExpression = - KeyConditionExpression.fromConditionExpressionUnsafe(conditionExpression) - self match { - case Zip(left, right, zippable) => - Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) - case Map(query, mapper) => Map(query.whereKey(keyConditionExpression), mapper) - case Absolve(query) => Absolve(query.whereKey(keyConditionExpression)) - case s: QuerySome => - s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] - case s: QueryAll => - s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] - case _ => self - } - } + // /** + // * Adds a KeyConditionExpression to a DynamoDBQuery. Example: + // * {{{ + // * val newQuery = query.whereKey(partitionKey("email") === "avi@gmail.com" && sortKey("subject") === "maths") + // * }}} + // */ + // def whereKey(keyConditionExpression: KeyConditionExpression): DynamoDBQuery[In, Out] = + // self match { + // case Zip(left, right, zippable) => + // Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) + // case Map(query, mapper) => Map(query.whereKey(keyConditionExpression), mapper) + // case Absolve(query) => Absolve(query.whereKey(keyConditionExpression)) + + // case s: QuerySome => + // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + // case s: QueryAll => + // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + // case _ => self + // } + + // /** + // * Adds a KeyConditionExpression from a ConditionExpression to a DynamoDBQuery + // * Must be in the form of ` && ` where format of `` is: + // * {{{ === }}} + // * and the format of `` is: + // * {{{ }}} where op can be one of `===`, `>`, `>=`, `<`, `<=`, `between`, `beginsWith` + // * + // * Example using type safe API: + // * {{{ + // * // email and subject are partition and sort keys respectively + // * val (email, subject, enrollmentDate, payment) = ProjectionExpression.accessors[Student] + // * // ... + // * val newQuery = query.whereKey(email === "avi@gmail.com" && subject === "maths") + // * }}} + // */ + // def whereKey[B]( + // conditionExpression: ConditionExpression[B] + // )(implicit ev: CanWhereKey[B, Out]): DynamoDBQuery[In, Out] = { + // val _ = ev + // val keyConditionExpression: KeyConditionExpression = + // KeyConditionExpression.fromConditionExpressionUnsafe(conditionExpression) + // self match { + // case Zip(left, right, zippable) => + // Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) + // case Map(query, mapper) => Map(query.whereKey(keyConditionExpression), mapper) + // case Absolve(query) => Absolve(query.whereKey(keyConditionExpression)) + // case s: QuerySome => + // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + // case s: QueryAll => + // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + // case _ => self + // } + // } def withRetryPolicy(retryPolicy: Schedule[Any, Throwable, Any]): DynamoDBQuery[In, Out] = self match { @@ -867,6 +882,7 @@ object DynamoDBQuery { None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, keyConditionExpression: Option[KeyConditionExpression] = None, + keyConditionExpression2: Option[KeyConditionExpr[_, _]] = None, projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes @@ -897,12 +913,14 @@ object DynamoDBQuery { limit: Option[Int] = None, consistency: ConsistencyMode = ConsistencyMode.Weak, exclusiveStartKey: LastEvaluatedKey = - None, // allows client to control start position - eg for client managed paging + None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, + // TODO: Avi - delete keyConditionExpression: Option[KeyConditionExpression] = None, - projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned + keyConditionExpression2: Option[KeyConditionExpr[_, _]] = None, // TODO: Avi - rename + projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, - select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes + select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes ascending: Boolean = true ) extends Constructor[Any, Stream[Throwable, Item]] @@ -1062,7 +1080,7 @@ object DynamoDBQuery { case Succeed(value) => (Chunk.empty, _ => value()) - case batchGetItem @ BatchGetItem(_, _, _, _) => + case batchGetItem @ BatchGetItem(_, _, _, _) => ( Chunk(batchGetItem), (results: Chunk[Any]) => { @@ -1070,7 +1088,7 @@ object DynamoDBQuery { } ) - case batchWriteItem @ BatchWriteItem(_, _, _, _, _) => + case batchWriteItem @ BatchWriteItem(_, _, _, _, _) => ( Chunk(batchWriteItem), (results: Chunk[Any]) => { @@ -1078,7 +1096,7 @@ object DynamoDBQuery { } ) - case deleteTable @ DeleteTable(_) => + case deleteTable @ DeleteTable(_) => ( Chunk(deleteTable), (results: Chunk[Any]) => { @@ -1086,7 +1104,7 @@ object DynamoDBQuery { } ) - case describeTable @ DescribeTable(_) => + case describeTable @ DescribeTable(_) => ( Chunk(describeTable), (results: Chunk[Any]) => { @@ -1095,7 +1113,7 @@ object DynamoDBQuery { ) // condition check is not a real query, it is only used in transactions - case _ @ConditionCheck(_, _, _) => + case _ @ConditionCheck(_, _, _) => ( Chunk[Constructor[In, Any]](), (_: Chunk[Any]) => { @@ -1103,7 +1121,7 @@ object DynamoDBQuery { } ) - case getItem @ GetItem(_, _, _, _, _) => + case getItem @ GetItem(_, _, _, _, _) => ( Chunk(getItem), (results: Chunk[Any]) => { @@ -1111,7 +1129,7 @@ object DynamoDBQuery { } ) - case putItem @ PutItem(_, _, _, _, _, _) => + case putItem @ PutItem(_, _, _, _, _, _) => ( Chunk(putItem), (results: Chunk[Any]) => { @@ -1119,7 +1137,7 @@ object DynamoDBQuery { } ) - case transaction @ Transaction(_, _, _, _) => + case transaction @ Transaction(_, _, _, _) => ( Chunk(transaction), (results: Chunk[Any]) => { @@ -1127,7 +1145,7 @@ object DynamoDBQuery { } ) - case updateItem @ UpdateItem(_, _, _, _, _, _, _) => + case updateItem @ UpdateItem(_, _, _, _, _, _, _) => ( Chunk(updateItem), (results: Chunk[Any]) => { @@ -1135,7 +1153,7 @@ object DynamoDBQuery { } ) - case deleteItem @ DeleteItem(_, _, _, _, _, _) => + case deleteItem @ DeleteItem(_, _, _, _, _, _) => ( Chunk(deleteItem), (results: Chunk[Any]) => { @@ -1143,7 +1161,7 @@ object DynamoDBQuery { } ) - case scan @ ScanSome(_, _, _, _, _, _, _, _, _) => + case scan @ ScanSome(_, _, _, _, _, _, _, _, _) => ( Chunk(scan), (results: Chunk[Any]) => { @@ -1151,7 +1169,7 @@ object DynamoDBQuery { } ) - case scan @ ScanAll(_, _, _, _, _, _, _, _, _, _) => + case scan @ ScanAll(_, _, _, _, _, _, _, _, _, _) => ( Chunk(scan), (results: Chunk[Any]) => { @@ -1159,7 +1177,7 @@ object DynamoDBQuery { } ) - case query @ QuerySome(_, _, _, _, _, _, _, _, _, _, _) => + case query @ QuerySome(_, _, _, _, _, _, _, _, _, _, _, _) => ( Chunk(query), (results: Chunk[Any]) => { @@ -1167,7 +1185,7 @@ object DynamoDBQuery { } ) - case query @ QueryAll(_, _, _, _, _, _, _, _, _, _, _) => + case query @ QueryAll(_, _, _, _, _, _, _, _, _, _, _, _) => ( Chunk(query), (results: Chunk[Any]) => { @@ -1175,7 +1193,7 @@ object DynamoDBQuery { } ) - case createTable @ CreateTable(_, _, _, _, _, _, _, _) => + case createTable @ CreateTable(_, _, _, _, _, _, _, _) => ( Chunk(createTable), (results: Chunk[Any]) => { diff --git a/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala b/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala index 29d4e71c7..7d90f6d5f 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala @@ -71,10 +71,10 @@ private[dynamodb] final case class TestDynamoDBExecutorImpl private[dynamodb] ( case ScanAll(tableName, _, maybeLimit, _, _, _, _, _, _, _) => fakeScanAll(tableName.value, maybeLimit) - case QuerySome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _, _, _) => + case QuerySome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _, _, _, _) => fakeScanSome(tableName.value, exclusiveStartKey, Some(limit)) - case QueryAll(tableName, _, maybeLimit, _, _, _, _, _, _, _, _) => + case QueryAll(tableName, _, maybeLimit, _, _, _, _, _, _, _, _, _) => fakeScanAll(tableName.value, maybeLimit) // TODO: implement CreateTable diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index a67f30638..8e60bc739 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -4,6 +4,7 @@ import zio.dynamodb.ProjectionExpression import zio.schema.Schema import zio.schema.DeriveSchema +import zio.dynamodb.DynamoDBQuery object KeyConditionExprExample extends App { @@ -52,4 +53,6 @@ object KeyConditionExprExample extends App { val (aliasMap, s) = pkAndSkExtended1.render.execute println(s"aliasMap=$aliasMap, s=$s") + + val get = DynamoDBQuery.queryAllItem("table").whereKey($("foo.bar").primaryKey === 1 && $("foo.baz").sortKey > 1) } diff --git a/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala b/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala index 0c70dffbf..e5b763237 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala @@ -1,9 +1,7 @@ package zio.dynamodb.examples import zio.dynamodb.DynamoDBQuery._ -import zio.dynamodb.PartitionKeyExpression.PartitionKey import zio.dynamodb.ProjectionExpression.$ -import zio.dynamodb.SortKeyExpression.SortKey import zio.dynamodb._ import zio.stream.Stream import zio.{ Chunk, ZIO } @@ -19,15 +17,15 @@ object QueryAndScanExamples extends App { val queryAll: ZIO[DynamoDBExecutor, Throwable, Stream[Throwable, Item]] = queryAllItem("tableName1", $("A"), $("B"), $("C")) .whereKey( - PartitionKey("partitionKey1") === "x" && - SortKey("sortKey1") > "X" + $("partitionKey1").primaryKey === "x" && + $("sortKey1").sortKey > "X" ) .execute val querySome: ZIO[DynamoDBExecutor, Throwable, (Chunk[Item], LastEvaluatedKey)] = querySomeItem("tableName1", limit = 10, $("A"), $("B"), $("C")) .sortOrder(ascending = false) - .whereKey(PartitionKey("partitionKey1") === "x" && SortKey("sortKey1") > "X") + .whereKey($("partitionKey1").primaryKey === "x" && $("sortKey1").sortKey > "X") .selectCount .execute @@ -40,7 +38,7 @@ object QueryAndScanExamples extends App { $("B"), $("C") ) - .whereKey(PartitionKey("partitionKey1") === "x" && SortKey("sortKey1") > "X") + .whereKey($("partitionKey1").primaryKey === "x" && $("sortKey1").sortKey > "X") .selectCount).sortOrder(ascending = true) } diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala index f5b088856..734001084 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala @@ -39,7 +39,7 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { .filter( enrollmentDate === Some(enrolDate) && payment === Payment.CreditCard ) - .whereKey(email === "avi@gmail.com" && subject === "maths") + .whereKey(email.primaryKey === "avi@gmail.com" && subject.sortKey === "maths") .execute .map(_.runCollect) _ <- put[Student]("student", avi) @@ -59,7 +59,7 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { .where( enrollmentDate === Some( enrolDate - ) && payment === Payment.CreditCard // && zio.dynamodb.examples.Elephant.email === "elephant@gmail.com" + ) && payment === Payment.CreditCard // && zio.dynamodb.amples.Elephant.email === "elephant@gmail.com" ) .execute _ <- scanAll[Student]("student").execute diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index 6e76bd308..3193fccd1 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -59,7 +59,7 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { .filter( enrollmentDate === Some(enrolDate) && payment === Payment.PayPal //&& elephantCe ) - .whereKey(email === "avi@gmail.com" && subject === "maths" /* && elephantCe */ ) + .whereKey(email.primaryKey === "avi@gmail.com" && subject.sortKey === "maths" /* && elephantCe */ ) .execute .map(_.runCollect) _ <- put[Student]("student", avi) diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala index 18efd1049..1963021a2 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala @@ -58,7 +58,7 @@ object TypeSafeAPIExampleWithDiscriminator extends ZIOAppDefault { _ <- put[Box]("box", boxOfGreen).execute _ <- put[Box]("box", boxOfAmber).execute query = queryAll[Box]("box") - .whereKey(Box.id === 1) + .whereKey(Box.id.primaryKey === 1) .filter(Box.trafficLightColour >>> TrafficLight.green >>> Green.rgb === 1) stream <- query.execute list <- stream.runCollect diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala index b9fb85f35..439f3bda9 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala @@ -49,7 +49,7 @@ object TypeSafeAPIExampleWithoutDiscriminator extends ZIOAppDefault { _ <- put[Box]("box", boxOfGreen).execute _ <- put[Box]("box", boxOfAmber).execute query = queryAll[Box]("box") - .whereKey(Box.id === 1) + .whereKey(Box.id.primaryKey === 1) .filter(Box.trafficLightColour >>> TrafficLight.green >>> Green.rgb === 1) stream <- query.execute list <- stream.runCollect From f1648488baf0ff08f3487fe558c589d6508c737c Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 07:48:55 +0100 Subject: [PATCH 40/69] removed KeyConditionExpression field, rename of PE method --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 2 +- .../zio/dynamodb/DynamoDBExecutorImpl.scala | 2 +- .../scala/zio/dynamodb/DynamoDBQuery.scala | 79 ++++--------------- .../zio/dynamodb/ProjectionExpression.scala | 2 +- .../dynamodb/TestDynamoDBExecutorImpl.scala | 18 ++--- .../examples/KeyConditionExprExample.scala | 16 ++-- .../scala/zio/dynamodb/examples/Main.scala | 2 +- .../examples/SimpleDecodedExample.scala | 2 +- ...ypeSafeRoundTripSerialisationExample.scala | 9 +-- .../StudentZioDynamoDbExampleWithOptics.scala | 2 +- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 2 +- .../TypeSafeAPIExampleWithDiscriminator.scala | 2 +- ...peSafeAPIExampleWithoutDiscriminator.scala | 2 +- 13 files changed, 46 insertions(+), 94 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 97120971e..4599393a7 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -195,7 +195,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .queryAll[ExpressionAttrNames](tableName) - .whereKey(ExpressionAttrNames.id.primaryKey === "id") + .whereKey(ExpressionAttrNames.id.partitionKey === "id") .filter(ExpressionAttrNames.ttl.notExists) query.execute.flatMap(_.runDrain).exit.map { result => assert(result)(succeeds(isUnit)) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala index 047c92213..85e3770ec 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala @@ -773,7 +773,7 @@ case object DynamoDBExecutorImpl { def awsQueryRequest(queryAll: QueryAll): QueryRequest = { val (aliasMap, (maybeFilterExpr, maybeKeyExpr, projections)) = (for { filter <- AliasMapRender.collectAll(queryAll.filterExpression.map(_.render)) - keyExpr <- AliasMapRender.collectAll(queryAll.keyConditionExpression2.map(_.render)) + keyExpr <- AliasMapRender.collectAll(queryAll.keyConditionExpression.map(_.render)) projections <- AliasMapRender.forEach(queryAll.projections) } yield (filter, keyExpr, projections)).execute diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index a0a2cce35..be5cfc8ae 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -317,7 +317,16 @@ sealed trait DynamoDBQuery[-In, +Out] { self => def selectSpecificAttributes: DynamoDBQuery[In, Out] = select(Select.SpecificAttributes) def selectCount: DynamoDBQuery[In, Out] = select(Select.Count) - // TOOD: Avi - maybe we can use In? + /** + * Adds a KeyConditionExpr to a DynamoDBQuery. Example: + * {{{ + * // high level type safe API where "email" and "subject" keys are defined using ProjectionExpression.accessors[Student] + * val newQuery = query.whereKey(email.partitionKey === "avi@gmail.com" && subject.sortKey === "maths") + * + * // low level API + * val newQuery = query.whereKey($("email").partitionKey === "avi@gmail.com" && $("subject").sortKey === "maths") + * }}} + */ def whereKey[From, To](keyConditionExpression: KeyConditionExpr[From, To]): DynamoDBQuery[In, Out] = self match { case Zip(left, right, zippable) => @@ -328,64 +337,10 @@ sealed trait DynamoDBQuery[-In, +Out] { self => case s: QuerySome => s.copy(keyConditionExpression2 = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] case s: QueryAll => - s.copy(keyConditionExpression2 = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] case _ => self } - // /** - // * Adds a KeyConditionExpression to a DynamoDBQuery. Example: - // * {{{ - // * val newQuery = query.whereKey(partitionKey("email") === "avi@gmail.com" && sortKey("subject") === "maths") - // * }}} - // */ - // def whereKey(keyConditionExpression: KeyConditionExpression): DynamoDBQuery[In, Out] = - // self match { - // case Zip(left, right, zippable) => - // Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) - // case Map(query, mapper) => Map(query.whereKey(keyConditionExpression), mapper) - // case Absolve(query) => Absolve(query.whereKey(keyConditionExpression)) - - // case s: QuerySome => - // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] - // case s: QueryAll => - // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] - // case _ => self - // } - - // /** - // * Adds a KeyConditionExpression from a ConditionExpression to a DynamoDBQuery - // * Must be in the form of ` && ` where format of `` is: - // * {{{ === }}} - // * and the format of `` is: - // * {{{ }}} where op can be one of `===`, `>`, `>=`, `<`, `<=`, `between`, `beginsWith` - // * - // * Example using type safe API: - // * {{{ - // * // email and subject are partition and sort keys respectively - // * val (email, subject, enrollmentDate, payment) = ProjectionExpression.accessors[Student] - // * // ... - // * val newQuery = query.whereKey(email === "avi@gmail.com" && subject === "maths") - // * }}} - // */ - // def whereKey[B]( - // conditionExpression: ConditionExpression[B] - // )(implicit ev: CanWhereKey[B, Out]): DynamoDBQuery[In, Out] = { - // val _ = ev - // val keyConditionExpression: KeyConditionExpression = - // KeyConditionExpression.fromConditionExpressionUnsafe(conditionExpression) - // self match { - // case Zip(left, right, zippable) => - // Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) - // case Map(query, mapper) => Map(query.whereKey(keyConditionExpression), mapper) - // case Absolve(query) => Absolve(query.whereKey(keyConditionExpression)) - // case s: QuerySome => - // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] - // case s: QueryAll => - // s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] - // case _ => self - // } - // } - def withRetryPolicy(retryPolicy: Schedule[Any, Throwable, Any]): DynamoDBQuery[In, Out] = self match { case Zip(left, right, zippable) => @@ -913,14 +868,12 @@ object DynamoDBQuery { limit: Option[Int] = None, consistency: ConsistencyMode = ConsistencyMode.Weak, exclusiveStartKey: LastEvaluatedKey = - None, // allows client to control start position - eg for client managed paging + None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, - // TODO: Avi - delete - keyConditionExpression: Option[KeyConditionExpression] = None, - keyConditionExpression2: Option[KeyConditionExpr[_, _]] = None, // TODO: Avi - rename - projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned + keyConditionExpression: Option[KeyConditionExpr[_, _]] = None, + projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, - select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes + select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes ascending: Boolean = true ) extends Constructor[Any, Stream[Throwable, Item]] @@ -1185,7 +1138,7 @@ object DynamoDBQuery { } ) - case query @ QueryAll(_, _, _, _, _, _, _, _, _, _, _, _) => + case query @ QueryAll(_, _, _, _, _, _, _, _, _, _, _) => ( Chunk(query), (results: Chunk[Any]) => { diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index f6ad41ff9..1861f96ab 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -134,7 +134,7 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { - def primaryKey(implicit ev: IsPrimaryKey[To]): PartitionKey2[From, To] = { + def partitionKey(implicit ev: IsPrimaryKey[To]): PartitionKey2[From, To] = { val _ = ev self match { case ProjectionExpression.MapElement(_, key) => PartitionKey2[From, To](key) diff --git a/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala b/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala index 7d90f6d5f..4902b17c0 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala @@ -16,7 +16,7 @@ private[dynamodb] final case class TestDynamoDBExecutorImpl private[dynamodb] ( override def execute[A](atomicQuery: DynamoDBQuery[_, A]): ZIO[Any, Exception, A] = atomicQuery match { - case BatchGetItem(requestItemsMap, _, _, _) => + case BatchGetItem(requestItemsMap, _, _, _) => val requestItems: Seq[(TableName, TableGet)] = requestItemsMap.toList val foundItems: IO[DatabaseError, Seq[(TableName, Option[Item])]] = @@ -39,7 +39,7 @@ private[dynamodb] final case class TestDynamoDBExecutorImpl private[dynamodb] ( response - case BatchWriteItem(requestItems, _, _, _, _) => + case BatchWriteItem(requestItems, _, _, _, _) => val results: ZIO[Any, DatabaseError, Unit] = ZIO.foreachDiscard(requestItems.toList) { case (tableName, setOfWrite) => ZIO.foreachDiscard(setOfWrite) { write => @@ -54,32 +54,32 @@ private[dynamodb] final case class TestDynamoDBExecutorImpl private[dynamodb] ( } results.map(_ => BatchWriteItem.Response(None)) - case GetItem(tableName, key, _, _, _) => + case GetItem(tableName, key, _, _, _) => fakeGetItem(tableName.value, key) - case PutItem(tableName, item, _, _, _, _) => + case PutItem(tableName, item, _, _, _, _) => fakePut(tableName.value, item) // TODO Note UpdateItem is not currently supported as it uses an UpdateExpression - case DeleteItem(tableName, key, _, _, _, _) => + case DeleteItem(tableName, key, _, _, _, _) => fakeDelete(tableName.value, key) - case ScanSome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _) => + case ScanSome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _) => fakeScanSome(tableName.value, exclusiveStartKey, Some(limit)) - case ScanAll(tableName, _, maybeLimit, _, _, _, _, _, _, _) => + case ScanAll(tableName, _, maybeLimit, _, _, _, _, _, _, _) => fakeScanAll(tableName.value, maybeLimit) case QuerySome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _, _, _, _) => fakeScanSome(tableName.value, exclusiveStartKey, Some(limit)) - case QueryAll(tableName, _, maybeLimit, _, _, _, _, _, _, _, _, _) => + case QueryAll(tableName, _, maybeLimit, _, _, _, _, _, _, _, _) => fakeScanAll(tableName.value, maybeLimit) // TODO: implement CreateTable - case unknown => + case unknown => ZIO.fail(new Exception(s"Constructor $unknown not implemented yet")) } diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index 8e60bc739..a5ed16fcd 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -31,24 +31,24 @@ object KeyConditionExprExample extends App { val (email, subject, age, binary, binary2) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyExpr[Student, String] = Student.email.primaryKey === "x" + val pk: PartitionKeyExpr[Student, String] = Student.email.partitionKey === "x" // val pkX: PartitionKeyExpr[Student, String] = Student.age.primaryKey === "x" // as expected does not compile val sk1: SortKeyExpr[Student, String] = Student.subject.sortKey === "y" val sk2: ExtendedSortKeyExpr[Student, String] = Student.subject.sortKey > "y" - val pkAndSk = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" + val pkAndSk = Student.email.partitionKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed val pkAndSkExtended1 = - Student.email.primaryKey === "x" && Student.subject.sortKey > "y" + Student.email.partitionKey === "x" && Student.subject.sortKey > "y" val pkAndSkExtended2 = - Student.email.primaryKey === "x" && Student.subject.sortKey < "y" + Student.email.partitionKey === "x" && Student.subject.sortKey < "y" val pkAndSkExtended3 = - Student.email.primaryKey === "x" && Student.subject.sortKey.between("1", "2") + Student.email.partitionKey === "x" && Student.subject.sortKey.between("1", "2") val pkAndSkExtended4 = - Student.email.primaryKey === "x" && Student.subject.sortKey.beginsWith("1") + Student.email.partitionKey === "x" && Student.subject.sortKey.beginsWith("1") val pkAndSkExtended5 = - Student.email.primaryKey === "x" && Student.binary.sortKey.beginsWith(List(1.toByte)) + Student.email.partitionKey === "x" && Student.binary.sortKey.beginsWith(List(1.toByte)) val pkAndSkExtended6 = - Student.email.primaryKey === "x" && Student.binary2.sortKey.beginsWith(List(1.toByte)) + Student.email.partitionKey === "x" && Student.binary2.sortKey.beginsWith(List(1.toByte)) // TODO: Avi - fix ToAttrubuteValue interop with Array[Byte] val (aliasMap, s) = pkAndSkExtended1.render.execute diff --git a/examples/src/main/scala/zio/dynamodb/examples/Main.scala b/examples/src/main/scala/zio/dynamodb/examples/Main.scala index 159020d33..cca9d9db5 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/Main.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/Main.scala @@ -20,7 +20,7 @@ object Main extends ZIOAppDefault { private val program = for { _ <- put("personTable", examplePerson).execute - person <- get("personTable", Person.id.primaryKey === 1).execute + person <- get("personTable", Person.id.partitionKey === 1).execute _ <- zio.Console.printLine(s"hello $person") } yield () diff --git a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala index 579e31b2d..5f8264538 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala @@ -24,7 +24,7 @@ object SimpleDecodedExample extends ZIOAppDefault { private val program = for { _ <- put("table1", NestedCaseClass2(id = 1, SimpleCaseClass3(2, "Avi", flag = true))).execute // Save case class to DB - caseClass <- get("table1", NestedCaseClass2.id.primaryKey === 2).execute // read case class from DB + caseClass <- get("table1", NestedCaseClass2.id.partitionKey === 2).execute // read case class from DB _ <- printLine(s"get: found $caseClass") either <- scanSome[NestedCaseClass2]("table1", 10).execute _ <- printLine(s"scanSome: found $either") diff --git a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala index 5e0dd7c1f..0fd448a3a 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala @@ -20,7 +20,6 @@ import zio.dynamodb.ProjectionExpression import zio.ZIO import zio.dynamodb.DynamoDBError - object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { @discriminatorName("invoiceType") @@ -71,13 +70,13 @@ object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { object PreBilled { implicit val schema: Schema.CaseClass4[String, Int, Instant, BigDecimal, PreBilled] = DeriveSchema.gen[PreBilled] - val (id, sequence, dueDate, total) = ProjectionExpression.accessors[PreBilled] + val (id, sequence, dueDate, total) = ProjectionExpression.accessors[PreBilled] } implicit val schema: Schema[Invoice] = DeriveSchema.gen[Invoice] } - private val billedInvoice: Billed = Billed( + private val billedInvoice: Billed = Billed( id = "1", sequence = 1, dueDate = Instant.now(), @@ -116,8 +115,8 @@ object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { private val program = for { _ <- Repository.genericSave(billedInvoice) _ <- Repository.genericSave(preBilledInvoice) - found <- Repository.genericFindById(Billed.id.primaryKey === "1") - found2 <- Repository.genericFindById(PreBilled.id.primaryKey === "2") + found <- Repository.genericFindById(Billed.id.partitionKey === "1") + found2 <- Repository.genericFindById(PreBilled.id.partitionKey === "2") item <- DynamoDBQuery.getItem("table1", PrimaryKey("id" -> "1")).execute _ <- printLine(s"found=$found") _ <- printLine(s"found2=$found2") diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala index 734001084..a276ba149 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala @@ -39,7 +39,7 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { .filter( enrollmentDate === Some(enrolDate) && payment === Payment.CreditCard ) - .whereKey(email.primaryKey === "avi@gmail.com" && subject.sortKey === "maths") + .whereKey(email.partitionKey === "avi@gmail.com" && subject.sortKey === "maths") .execute .map(_.runCollect) _ <- put[Student]("student", avi) diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index 3193fccd1..abe499304 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -59,7 +59,7 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { .filter( enrollmentDate === Some(enrolDate) && payment === Payment.PayPal //&& elephantCe ) - .whereKey(email.primaryKey === "avi@gmail.com" && subject.sortKey === "maths" /* && elephantCe */ ) + .whereKey(email.partitionKey === "avi@gmail.com" && subject.sortKey === "maths" /* && elephantCe */ ) .execute .map(_.runCollect) _ <- put[Student]("student", avi) diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala index 1963021a2..1c2a8c4f2 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithDiscriminator.scala @@ -58,7 +58,7 @@ object TypeSafeAPIExampleWithDiscriminator extends ZIOAppDefault { _ <- put[Box]("box", boxOfGreen).execute _ <- put[Box]("box", boxOfAmber).execute query = queryAll[Box]("box") - .whereKey(Box.id.primaryKey === 1) + .whereKey(Box.id.partitionKey === 1) .filter(Box.trafficLightColour >>> TrafficLight.green >>> Green.rgb === 1) stream <- query.execute list <- stream.runCollect diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala index 439f3bda9..2408a81a7 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/TypeSafeAPIExampleWithoutDiscriminator.scala @@ -49,7 +49,7 @@ object TypeSafeAPIExampleWithoutDiscriminator extends ZIOAppDefault { _ <- put[Box]("box", boxOfGreen).execute _ <- put[Box]("box", boxOfAmber).execute query = queryAll[Box]("box") - .whereKey(Box.id.primaryKey === 1) + .whereKey(Box.id.partitionKey === 1) .filter(Box.trafficLightColour >>> TrafficLight.green >>> Green.rgb === 1) stream <- query.execute list <- stream.runCollect From 4f80df1493e5e856be45b1305352e49ca583920b Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 07:56:23 +0100 Subject: [PATCH 41/69] removed KeyConditionExpression field --- .../scala/zio/dynamodb/DynamoDBQuery.scala | 33 +++++++++---------- .../dynamodb/TestDynamoDBExecutorImpl.scala | 20 +++++------ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index be5cfc8ae..df4c82112 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -221,7 +221,7 @@ sealed trait DynamoDBQuery[-In, +Out] { self => } /** - * Parallel executes a DynamoDB Scan in parallel. + * Executes a DynamoDB Scan in parallel. * There are no guarantees on order of returned items. * * @param n The number of parallel requests to make to DynamoDB @@ -836,7 +836,6 @@ object DynamoDBQuery { exclusiveStartKey: LastEvaluatedKey = None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, - keyConditionExpression: Option[KeyConditionExpression] = None, keyConditionExpression2: Option[KeyConditionExpr[_, _]] = None, projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, @@ -1033,7 +1032,7 @@ object DynamoDBQuery { case Succeed(value) => (Chunk.empty, _ => value()) - case batchGetItem @ BatchGetItem(_, _, _, _) => + case batchGetItem @ BatchGetItem(_, _, _, _) => ( Chunk(batchGetItem), (results: Chunk[Any]) => { @@ -1041,7 +1040,7 @@ object DynamoDBQuery { } ) - case batchWriteItem @ BatchWriteItem(_, _, _, _, _) => + case batchWriteItem @ BatchWriteItem(_, _, _, _, _) => ( Chunk(batchWriteItem), (results: Chunk[Any]) => { @@ -1049,7 +1048,7 @@ object DynamoDBQuery { } ) - case deleteTable @ DeleteTable(_) => + case deleteTable @ DeleteTable(_) => ( Chunk(deleteTable), (results: Chunk[Any]) => { @@ -1057,7 +1056,7 @@ object DynamoDBQuery { } ) - case describeTable @ DescribeTable(_) => + case describeTable @ DescribeTable(_) => ( Chunk(describeTable), (results: Chunk[Any]) => { @@ -1066,7 +1065,7 @@ object DynamoDBQuery { ) // condition check is not a real query, it is only used in transactions - case _ @ConditionCheck(_, _, _) => + case _ @ConditionCheck(_, _, _) => ( Chunk[Constructor[In, Any]](), (_: Chunk[Any]) => { @@ -1074,7 +1073,7 @@ object DynamoDBQuery { } ) - case getItem @ GetItem(_, _, _, _, _) => + case getItem @ GetItem(_, _, _, _, _) => ( Chunk(getItem), (results: Chunk[Any]) => { @@ -1082,7 +1081,7 @@ object DynamoDBQuery { } ) - case putItem @ PutItem(_, _, _, _, _, _) => + case putItem @ PutItem(_, _, _, _, _, _) => ( Chunk(putItem), (results: Chunk[Any]) => { @@ -1090,7 +1089,7 @@ object DynamoDBQuery { } ) - case transaction @ Transaction(_, _, _, _) => + case transaction @ Transaction(_, _, _, _) => ( Chunk(transaction), (results: Chunk[Any]) => { @@ -1098,7 +1097,7 @@ object DynamoDBQuery { } ) - case updateItem @ UpdateItem(_, _, _, _, _, _, _) => + case updateItem @ UpdateItem(_, _, _, _, _, _, _) => ( Chunk(updateItem), (results: Chunk[Any]) => { @@ -1106,7 +1105,7 @@ object DynamoDBQuery { } ) - case deleteItem @ DeleteItem(_, _, _, _, _, _) => + case deleteItem @ DeleteItem(_, _, _, _, _, _) => ( Chunk(deleteItem), (results: Chunk[Any]) => { @@ -1114,7 +1113,7 @@ object DynamoDBQuery { } ) - case scan @ ScanSome(_, _, _, _, _, _, _, _, _) => + case scan @ ScanSome(_, _, _, _, _, _, _, _, _) => ( Chunk(scan), (results: Chunk[Any]) => { @@ -1122,7 +1121,7 @@ object DynamoDBQuery { } ) - case scan @ ScanAll(_, _, _, _, _, _, _, _, _, _) => + case scan @ ScanAll(_, _, _, _, _, _, _, _, _, _) => ( Chunk(scan), (results: Chunk[Any]) => { @@ -1130,7 +1129,7 @@ object DynamoDBQuery { } ) - case query @ QuerySome(_, _, _, _, _, _, _, _, _, _, _, _) => + case query @ QuerySome(_, _, _, _, _, _, _, _, _, _, _) => ( Chunk(query), (results: Chunk[Any]) => { @@ -1138,7 +1137,7 @@ object DynamoDBQuery { } ) - case query @ QueryAll(_, _, _, _, _, _, _, _, _, _, _) => + case query @ QueryAll(_, _, _, _, _, _, _, _, _, _, _) => ( Chunk(query), (results: Chunk[Any]) => { @@ -1146,7 +1145,7 @@ object DynamoDBQuery { } ) - case createTable @ CreateTable(_, _, _, _, _, _, _, _) => + case createTable @ CreateTable(_, _, _, _, _, _, _, _) => ( Chunk(createTable), (results: Chunk[Any]) => { diff --git a/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala b/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala index 4902b17c0..29d4e71c7 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/TestDynamoDBExecutorImpl.scala @@ -16,7 +16,7 @@ private[dynamodb] final case class TestDynamoDBExecutorImpl private[dynamodb] ( override def execute[A](atomicQuery: DynamoDBQuery[_, A]): ZIO[Any, Exception, A] = atomicQuery match { - case BatchGetItem(requestItemsMap, _, _, _) => + case BatchGetItem(requestItemsMap, _, _, _) => val requestItems: Seq[(TableName, TableGet)] = requestItemsMap.toList val foundItems: IO[DatabaseError, Seq[(TableName, Option[Item])]] = @@ -39,7 +39,7 @@ private[dynamodb] final case class TestDynamoDBExecutorImpl private[dynamodb] ( response - case BatchWriteItem(requestItems, _, _, _, _) => + case BatchWriteItem(requestItems, _, _, _, _) => val results: ZIO[Any, DatabaseError, Unit] = ZIO.foreachDiscard(requestItems.toList) { case (tableName, setOfWrite) => ZIO.foreachDiscard(setOfWrite) { write => @@ -54,32 +54,32 @@ private[dynamodb] final case class TestDynamoDBExecutorImpl private[dynamodb] ( } results.map(_ => BatchWriteItem.Response(None)) - case GetItem(tableName, key, _, _, _) => + case GetItem(tableName, key, _, _, _) => fakeGetItem(tableName.value, key) - case PutItem(tableName, item, _, _, _, _) => + case PutItem(tableName, item, _, _, _, _) => fakePut(tableName.value, item) // TODO Note UpdateItem is not currently supported as it uses an UpdateExpression - case DeleteItem(tableName, key, _, _, _, _) => + case DeleteItem(tableName, key, _, _, _, _) => fakeDelete(tableName.value, key) - case ScanSome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _) => + case ScanSome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _) => fakeScanSome(tableName.value, exclusiveStartKey, Some(limit)) - case ScanAll(tableName, _, maybeLimit, _, _, _, _, _, _, _) => + case ScanAll(tableName, _, maybeLimit, _, _, _, _, _, _, _) => fakeScanAll(tableName.value, maybeLimit) - case QuerySome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _, _, _, _) => + case QuerySome(tableName, limit, _, _, exclusiveStartKey, _, _, _, _, _, _) => fakeScanSome(tableName.value, exclusiveStartKey, Some(limit)) - case QueryAll(tableName, _, maybeLimit, _, _, _, _, _, _, _, _) => + case QueryAll(tableName, _, maybeLimit, _, _, _, _, _, _, _, _) => fakeScanAll(tableName.value, maybeLimit) // TODO: implement CreateTable - case unknown => + case unknown => ZIO.fail(new Exception(s"Constructor $unknown not implemented yet")) } From 8ca23679c787fa781d685c4c8bbdef080d087b2a Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 08:41:03 +0100 Subject: [PATCH 42/69] deleted KeyConditionExpression --- .../scala/zio/dynamodb/DynamoDBQuery.scala | 2 +- .../zio/dynamodb/KeyConditionExpression.scala | 252 ------------------ .../scala/zio/dynamodb/PartitionKey2.scala | 8 +- .../main/scala/zio/dynamodb/SortKey2.scala | 6 +- .../scala/zio/dynamodb/keyConditionExpr.scala | 16 +- .../src/main/scala/zio/dynamodb/package.scala | 20 +- .../zio/dynamodb/AliasMapRenderSpec.scala | 125 ++++----- .../dynamodb/KeyConditionExpressionSpec.scala | 168 ------------ .../examples/KeyConditionExprExample.scala | 6 +- .../KeyConditionExpressionExamples.scala | 15 -- ...ypeSafeRoundTripSerialisationExample.scala | 2 +- 11 files changed, 83 insertions(+), 537 deletions(-) delete mode 100644 dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression.scala delete mode 100644 dynamodb/src/test/scala/zio/dynamodb/KeyConditionExpressionSpec.scala delete mode 100644 examples/src/main/scala/zio/dynamodb/examples/KeyConditionExpressionExamples.scala diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index df4c82112..1e4eb4264 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -467,7 +467,7 @@ object DynamoDBQuery { def get[From: Schema, To]( tableName: String, - partitionKeyExpr: KeyConditionExpr.PartitionKeyExpr[From, To], + partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To], projections: ProjectionExpression[_, _]* )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { val _ = ev diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression.scala deleted file mode 100644 index db74996a3..000000000 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpression.scala +++ /dev/null @@ -1,252 +0,0 @@ -package zio.dynamodb - -import zio.dynamodb.ConditionExpression.Operand.ProjectionExpressionOperand -import zio.dynamodb.PartitionKeyExpression.PartitionKey -import zio.dynamodb.ProjectionExpression.{ MapElement, Root } -import zio.dynamodb.SortKeyExpression.SortKey - -/* -KeyCondition expression is a restricted version of ConditionExpression where by -- partition exprn is required and can only use "=" equals comparison -- optionally AND can be used to add a sort key expression - -eg partitionKeyName = :partitionkeyval AND sortKeyName = :sortkeyval -comparisons operators are the same as for Condition - - */ - -sealed trait KeyConditionExpression extends Renderable { self => - def render: AliasMapRender[String] = - self match { - case KeyConditionExpression.And(left, right) => - left.render - .zipWith( - right.render - ) { case (l, r) => s"$l AND $r" } - case expression: PartitionKeyExpression => expression.render - } - -} - -object KeyConditionExpression { - - def getOrInsert[From, To](primaryKeyName: String): AliasMapRender[String] = - AliasMapRender.getOrInsert(ProjectionExpression.MapElement[From, To](Root, primaryKeyName)) - private[dynamodb] final case class And(left: PartitionKeyExpression, right: SortKeyExpression) - extends KeyConditionExpression - def partitionKey(key: String): PartitionKey = PartitionKey(key) - def sortKey(key: String): SortKey = SortKey(key) - - /** - * Create a KeyConditionExpression from a ConditionExpression - * Must be in the form of ` && ` where format of `` is: - * {{{ === }}} - * and the format of `` is: - * {{{ }}} where op can be one of `===`, `>`, `>=`, `<`, `<=`, `between`, `beginsWith` - * - * Example using type API: - * {{{ - * val (email, subject, enrollmentDate, payment) = ProjectionExpression.accessors[Student] - * // ... - * val keyConditionExprn = filterKey(email === "avi@gmail.com" && subject === "maths") - * }}} - */ - private[dynamodb] def fromConditionExpressionUnsafe(c: ConditionExpression[_]): KeyConditionExpression = - KeyConditionExpression(c).getOrElse( - throw new IllegalStateException(s"Error: invalid key condition expression $c") - ) - - private[dynamodb] def apply(c: ConditionExpression[_]): Either[String, KeyConditionExpression] = - c match { - case ConditionExpression.Equals( - ProjectionExpressionOperand(MapElement(Root, partitionKey)), - ConditionExpression.Operand.ValueOperand(av) - ) => - Right(PartitionKeyExpression.Equals(PartitionKey(partitionKey), av)) - case ConditionExpression.And( - ConditionExpression.Equals( - ProjectionExpressionOperand(MapElement(Root, partitionKey)), - ConditionExpression.Operand.ValueOperand(avL) - ), - rhs - ) => - rhs match { - case ConditionExpression.Equals( - ProjectionExpressionOperand(MapElement(Root, sortKey)), - ConditionExpression.Operand.ValueOperand(avR) - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.Equals(SortKey(sortKey), avR)) - ) - case ConditionExpression.NotEqual( - ProjectionExpressionOperand(MapElement(Root, sortKey)), - ConditionExpression.Operand.ValueOperand(avR) - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.NotEqual(SortKey(sortKey), avR)) - ) - case ConditionExpression.GreaterThan( - ProjectionExpressionOperand(MapElement(Root, sortKey)), - ConditionExpression.Operand.ValueOperand(avR) - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.GreaterThan(SortKey(sortKey), avR)) - ) - case ConditionExpression.LessThan( - ProjectionExpressionOperand(MapElement(Root, sortKey)), - ConditionExpression.Operand.ValueOperand(avR) - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.LessThan(SortKey(sortKey), avR)) - ) - case ConditionExpression.GreaterThanOrEqual( - ProjectionExpressionOperand(MapElement(Root, sortKey)), - ConditionExpression.Operand.ValueOperand(avR) - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.GreaterThanOrEqual(SortKey(sortKey), avR)) - ) - case ConditionExpression.LessThanOrEqual( - ProjectionExpressionOperand(MapElement(Root, sortKey)), - ConditionExpression.Operand.ValueOperand(avR) - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.LessThanOrEqual(SortKey(sortKey), avR)) - ) - case ConditionExpression.Between( - ProjectionExpressionOperand(MapElement(Root, sortKey)), - avMin, - avMax - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.Between(SortKey(sortKey), avMin, avMax)) - ) - case ConditionExpression.BeginsWith( - MapElement(Root, sortKey), - av - ) => - Right( - PartitionKeyExpression - .Equals(PartitionKey(partitionKey), avL) - .&&(SortKeyExpression.BeginsWith(SortKey(sortKey), av)) - ) - case c => Left(s"condition '$c' is not a valid sort condition expression") - } - - case c => Left(s"condition $c is not a valid key condition expression") - } - -} - -sealed trait PartitionKeyExpression extends KeyConditionExpression { self => - import KeyConditionExpression.And - - def &&(that: SortKeyExpression): KeyConditionExpression = And(self, that) - - override def render: AliasMapRender[String] = - self match { - case PartitionKeyExpression.Equals(left, right) => - for { - v <- AliasMapRender.getOrInsert(right) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} = $v" - } -} -object PartitionKeyExpression { - final case class PartitionKey(keyName: String) { self => - def ===[A](that: A)(implicit t: ToAttributeValue[A]): PartitionKeyExpression = - Equals(self, t.toAttributeValue(that)) - } - final case class Equals(left: PartitionKey, right: AttributeValue) extends PartitionKeyExpression -} - -sealed trait SortKeyExpression { self => - def render: AliasMapRender[String] = - self match { - case SortKeyExpression.Equals(left, right) => - for { - v <- AliasMapRender.getOrInsert(right) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} = $v" - case SortKeyExpression.LessThan(left, right) => - for { - v <- AliasMapRender.getOrInsert(right) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} < $v" - case SortKeyExpression.NotEqual(left, right) => - for { - v <- AliasMapRender.getOrInsert(right) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} <> $v" - case SortKeyExpression.GreaterThan(left, right) => - for { - v <- AliasMapRender.getOrInsert(right) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} > $v" - case SortKeyExpression.LessThanOrEqual(left, right) => - for { - v <- AliasMapRender.getOrInsert(right) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} <= $v" - case SortKeyExpression.GreaterThanOrEqual(left, right) => - for { - v <- AliasMapRender.getOrInsert(right) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} >= $v" - case SortKeyExpression.Between(left, min, max) => - for { - min2 <- AliasMapRender.getOrInsert(min) - max2 <- AliasMapRender.getOrInsert(max) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"${keyName} BETWEEN $min2 AND $max2" - case SortKeyExpression.BeginsWith(left, value) => - for { - v <- AliasMapRender.getOrInsert(value) - keyName <- KeyConditionExpression.getOrInsert(left.keyName) - } yield s"begins_with(${keyName}, $v)" - } - -} - -object SortKeyExpression { - - final case class SortKey(keyName: String) { self => - def ===[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = Equals(self, t.toAttributeValue(that)) - def <>[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = NotEqual(self, t.toAttributeValue(that)) - def <[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = LessThan(self, t.toAttributeValue(that)) - def <=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - LessThanOrEqual(self, t.toAttributeValue(that)) - def >[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - GreaterThanOrEqual(self, t.toAttributeValue(that)) - def >=[A](that: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - GreaterThanOrEqual(self, t.toAttributeValue(that)) - def between[A](min: A, max: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - Between(self, t.toAttributeValue(min), t.toAttributeValue(max)) - def beginsWith[A](value: A)(implicit t: ToAttributeValue[A]): SortKeyExpression = - BeginsWith(self, t.toAttributeValue(value)) - } - - private[dynamodb] final case class Equals(left: SortKey, right: AttributeValue) extends SortKeyExpression - private[dynamodb] final case class NotEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression - private[dynamodb] final case class LessThan(left: SortKey, right: AttributeValue) extends SortKeyExpression - private[dynamodb] final case class GreaterThan(left: SortKey, right: AttributeValue) extends SortKeyExpression - private[dynamodb] final case class LessThanOrEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression - private[dynamodb] final case class GreaterThanOrEqual(left: SortKey, right: AttributeValue) extends SortKeyExpression - private[dynamodb] final case class Between(left: SortKey, min: AttributeValue, max: AttributeValue) - extends SortKeyExpression - private[dynamodb] final case class BeginsWith(left: SortKey, value: AttributeValue) extends SortKeyExpression -} diff --git a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala index 4c4b88a6f..e21d6dbb6 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala @@ -1,14 +1,14 @@ package zio.dynamodb -import zio.dynamodb.KeyConditionExpr.PartitionKeyExpr +import zio.dynamodb.KeyConditionExpr.PartitionKeyEquals import zio.dynamodb.proofs.RefersTo // belongs to the package top level private[dynamodb] final case class PartitionKey2[-From, +To](keyName: String) { self => - def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( + def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To1, To2]): PartitionKeyExpr[From, To] = { + )(implicit ev: RefersTo[To1, To2]): PartitionKeyEquals[From, To] = { val _ = ev - PartitionKeyExpr(self, implicitly[ToAttributeValue[To2]].toAttributeValue(value)) + PartitionKeyEquals(self, implicitly[ToAttributeValue[To2]].toAttributeValue(value)) } } diff --git a/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala index 208518ff0..3e4c3af9d 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala @@ -3,16 +3,16 @@ package zio.dynamodb import zio.dynamodb.proofs.RefersTo import zio.dynamodb.proofs.CanSortKeyBeginsWith -import zio.dynamodb.KeyConditionExpr.SortKeyExpr +import zio.dynamodb.KeyConditionExpr.SortKeyEquals import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self => // all comparison ops apply to: Strings, Numbers, Binary values def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 - )(implicit ev: RefersTo[To1, To2]): SortKeyExpr[From, To2] = { + )(implicit ev: RefersTo[To1, To2]): SortKeyEquals[From, To2] = { val _ = ev - SortKeyExpr[From, To2]( + SortKeyEquals[From, To2]( self.asInstanceOf[SortKey2[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 57e63448e..6d263b1c2 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -14,13 +14,13 @@ object KeyConditionExpr { // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - private[dynamodb] final case class PartitionKeyExpr[-From, +To](pk: PartitionKey2[From, To], value: AttributeValue) + private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey2[From, To], value: AttributeValue) extends KeyConditionExpr[From, To] { self => - def &&[From1 <: From, To2](other: SortKeyExpr[From1, To2]): CompositePrimaryKeyExpr[From1, To2] = - CompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyExpr[From1, To2]], other) + def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1, To2] = + CompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) def &&[From1 <: From, To2](other: ExtendedSortKeyExpr[From1, To2]): ExtendedCompositePrimaryKeyExpr[From1, To2] = - ExtendedCompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyExpr[From1, To2]], other) + ExtendedCompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) def asAttrMap: AttrMap = AttrMap(pk.keyName -> value) @@ -28,7 +28,7 @@ object KeyConditionExpr { AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } - private[dynamodb] final case class SortKeyExpr[-From, +To](sortKey: SortKey2[From, To], value: AttributeValue) { + private[dynamodb] final case class SortKeyEquals[-From, +To](sortKey: SortKey2[From, To], value: AttributeValue) { self => def render2: AliasMapRender[String] = AliasMapRender @@ -37,8 +37,8 @@ object KeyConditionExpr { } private[dynamodb] final case class CompositePrimaryKeyExpr[-From, +To]( - pk: PartitionKeyExpr[From, To], - sk: SortKeyExpr[From, To] + pk: PartitionKeyEquals[From, To], + sk: SortKeyEquals[From, To] ) extends KeyConditionExpr[From, To] { self => @@ -52,7 +52,7 @@ object KeyConditionExpr { } private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, +To]( - pk: PartitionKeyExpr[From, To], + pk: PartitionKeyEquals[From, To], sk: ExtendedSortKeyExpr[From, To] ) extends KeyConditionExpr[From, To] { self => diff --git a/dynamodb/src/main/scala/zio/dynamodb/package.scala b/dynamodb/src/main/scala/zio/dynamodb/package.scala index dcd3a9eb6..bf69d1a2b 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/package.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/package.scala @@ -127,26 +127,26 @@ package object dynamodb { } .flattenChunks - def batchReadFromStream2[R, A, From: Schema, To: IsPrimaryKey]( tableName: String, stream: ZStream[R, Throwable, A], mPar: Int = 10 )( - pk: A => KeyConditionExpr.PartitionKeyExpr[From, To] + pk: A => KeyConditionExpr.PartitionKeyEquals[From, To] ): ZStream[R with DynamoDBExecutor, Throwable, Either[DynamoDBError.DecodingError, (A, Option[From])]] = stream .aggregateAsync(ZSink.collectAllN[A](100)) .mapZIOPar(mPar) { chunk => - val batchGetItem: DynamoDBQuery[From, Chunk[Either[DynamoDBError.DecodingError, (A, Option[From])]]] = DynamoDBQuery - .forEach(chunk) { a => - DynamoDBQuery.get(tableName, pk(a)).map { - case Right(b) => Right((a, Some(b))) - case Left(DynamoDBError.ValueNotFound(_)) => Right((a, None)) - case Left(e @ DynamoDBError.DecodingError(_)) => Left(e) + val batchGetItem: DynamoDBQuery[From, Chunk[Either[DynamoDBError.DecodingError, (A, Option[From])]]] = + DynamoDBQuery + .forEach(chunk) { a => + DynamoDBQuery.get(tableName, pk(a)).map { + case Right(b) => Right((a, Some(b))) + case Left(DynamoDBError.ValueNotFound(_)) => Right((a, None)) + case Left(e @ DynamoDBError.DecodingError(_)) => Left(e) + } } - } - .map(Chunk.fromIterable) + .map(Chunk.fromIterable) for { r <- ZIO.environment[DynamoDBExecutor] list <- batchGetItem.execute.provideEnvironment(r) diff --git a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala index 0f32a793d..72f18a7b7 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala @@ -1,6 +1,8 @@ package zio.dynamodb import zio.Chunk +import zio.dynamodb.KeyConditionExpr +import zio.dynamodb.ProjectionExpression.$ import zio.dynamodb.ProjectionExpression._ import zio.test.Assertion._ import zio.test.{ ZIOSpecDefault, _ } @@ -318,152 +320,131 @@ object AliasMapRenderSpec extends ZIOSpecDefault { } ) ), - suite("KeyConditionExpression")( - suite("SortKeyExpression")( + // TODO: Avi - create new tests suite just for KeyConditionExpr ??? + suite("KeyConditionExpr")( + suite("Sort key expressions")( test("Equals") { val map = Map( - avKey(one) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(one) -> ":v0" ) val (aliasMap, expression) = - SortKeyExpression.Equals(SortKeyExpression.SortKey("num"), one).render.execute + KeyConditionExpr.SortKeyEquals($("num").sortKey, one).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("#n1 = :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("num = :v0")) }, test("LessThan") { val map = Map( - avKey(one) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(one) -> ":v0" ) val (aliasMap, expression) = - SortKeyExpression.LessThan(SortKeyExpression.SortKey("num"), one).render.execute + KeyConditionExpr.ExtendedSortKeyExpr.LessThan($("num").sortKey, one).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("#n1 < :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("num < :v0")) }, test("NotEqual") { val map = Map( - avKey(one) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(one) -> ":v0" ) val (aliasMap, expression) = - SortKeyExpression.NotEqual(SortKeyExpression.SortKey("num"), one).render.execute + KeyConditionExpr.ExtendedSortKeyExpr.NotEqual($("num").sortKey, one).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("#n1 <> :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("num <> :v0")) }, test("GreaterThan") { val map = Map( - avKey(one) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(one) -> ":v0" ) val (aliasMap, expression) = - SortKeyExpression.GreaterThan(SortKeyExpression.SortKey("num"), one).render.execute + KeyConditionExpr.ExtendedSortKeyExpr.GreaterThan($("num").sortKey, one).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("#n1 > :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("num > :v0")) }, test("LessThanOrEqual") { val map = Map( - avKey(one) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(one) -> ":v0" ) val (aliasMap, expression) = - SortKeyExpression.LessThanOrEqual(SortKeyExpression.SortKey("num"), one).render.execute + KeyConditionExpr.ExtendedSortKeyExpr.LessThanOrEqual($("num").sortKey, one).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("#n1 <= :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("num <= :v0")) }, test("GreaterThanOrEqual") { val map = Map( - avKey(one) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(one) -> ":v0" ) val (aliasMap, expression) = - SortKeyExpression.GreaterThanOrEqual(SortKeyExpression.SortKey("num"), one).render.execute + KeyConditionExpr.ExtendedSortKeyExpr.GreaterThanOrEqual($("num").sortKey, one).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("#n1 >= :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("num >= :v0")) }, test("Between") { val map = Map( - avKey(one) -> ":v0", - avKey(two) -> ":v1", - pathSegment(Root, "num") -> "#n2", - fullPath($("num")) -> "#n2" + avKey(one) -> ":v0", + avKey(two) -> ":v1" ) val (aliasMap, expression) = - SortKeyExpression.Between(SortKeyExpression.SortKey("num"), one, two).render.execute + KeyConditionExpr.ExtendedSortKeyExpr.Between($("num").sortKey, one, two).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 3))) && - assert(expression)(equalTo("#n2 BETWEEN :v0 AND :v1")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("num BETWEEN :v0 AND :v1")) }, test("BeginsWith") { val map = Map( - avKey(name) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(name) -> ":v0" ) val (aliasMap, expression) = - SortKeyExpression.BeginsWith(SortKeyExpression.SortKey("num"), name).render.execute + KeyConditionExpr.ExtendedSortKeyExpr.BeginsWith($("num").sortKey, name).render2.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("begins_with(#n1, :v0)")) + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("begins_with(num, :v0)")) } ), suite("PartitionKeyExpression")( test("Equals") { val map = Map( - avKey(one) -> ":v0", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1" + avKey(one) -> ":v0" ) - val (aliasMap, expression) = PartitionKeyExpression - .Equals(PartitionKeyExpression.PartitionKey("num"), one) + val (aliasMap, expression) = KeyConditionExpr + .PartitionKeyEquals($("num").primaryKey, one) .render .execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("#n1 = :v0")) + + assert(aliasMap)(equalTo(AliasMap(map, 1))) && + assert(expression)(equalTo("num = :v0")) } ), test("And") { val map = Map( - avKey(two) -> ":v0", - avKey(one) -> ":v2", - avKey(three) -> ":v3", - pathSegment(Root, "num") -> "#n1", - fullPath($("num")) -> "#n1", - pathSegment(Root, "num2") -> "#n4", - fullPath($("num2")) -> "#n4" + avKey(two) -> ":v0", + avKey(one) -> ":v1", + avKey(three) -> ":v2" ) - val (aliasMap, expression) = KeyConditionExpression - .And( - PartitionKeyExpression - .Equals(PartitionKeyExpression.PartitionKey("num"), two), - SortKeyExpression.Between(SortKeyExpression.SortKey("num2"), one, three) + val (aliasMap, expression) = KeyConditionExpr + .ExtendedCompositePrimaryKeyExpr( + KeyConditionExpr.PartitionKeyEquals($("num").primaryKey, two), + KeyConditionExpr.ExtendedSortKeyExpr.Between($("num").sortKey, one, three) ) .render .execute - assert(aliasMap)(equalTo(AliasMap(map, 5))) && - assert(expression)(equalTo("#n1 = :v0 AND #n4 BETWEEN :v2 AND :v3")) + assert(aliasMap)(equalTo(AliasMap(map, 3))) && + assert(expression)(equalTo("num = :v0 AND num BETWEEN :v1 AND :v2")) } ), suite("AttributeValueType")( diff --git a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExpressionSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExpressionSpec.scala deleted file mode 100644 index 108bca7fa..000000000 --- a/dynamodb/src/test/scala/zio/dynamodb/KeyConditionExpressionSpec.scala +++ /dev/null @@ -1,168 +0,0 @@ -package zio.dynamodb - -import zio.dynamodb.ConditionExpression.Operand.ProjectionExpressionOperand -import zio.dynamodb.ProjectionExpression.{ MapElement, Root } -import zio.schema.DeriveSchema -import zio.test.Assertion.{ isLeft, isRight } -import zio.test._ - -import java.time.Instant -import zio.schema.Schema - -object KeyConditionExpressionSpec extends ZIOSpecDefault { - - final case class Student( - email: String, - subject: String, - enrollmentDate: Option[Instant] - ) - object Student { - implicit val schema: Schema.CaseClass3[String, String, Option[Instant], Student] = - DeriveSchema.gen[Student] - } - - val (email, subject, enrollmentDate) = ProjectionExpression.accessors[Student] - - override def spec = - suite("KeyConditionExpression from a ConditionExpression")(happyPathSuite, unhappyPathSuite, pbtSuite) - - val happyPathSuite = - suite("returns a Right for")( - test(""" email === "avi@gmail.com" """) { - val actual = KeyConditionExpression(email === "avi@gmail.com") - zio.test.assert(actual)(isRight) - }, - test(""" email === "avi@gmail.com" && subject === "maths" """) { - val actual = KeyConditionExpression(email === "avi@gmail.com" && subject === "maths") - zio.test.assert(actual)(isRight) - }, - test(""" email === "avi@gmail.com" && subject.beginsWith("ma") """) { - val actual = - KeyConditionExpression( - email === "avi@gmail.com" && subject.beginsWith("ma") - ) - zio.test.assert(actual)(isRight) - } - ) - - val unhappyPathSuite = - suite("returns a Left for")( - test(""" email > "avi@gmail.com" && subject === "maths" """) { - val actual = KeyConditionExpression(email > "avi@gmail.com" && subject === "maths") - zio.test.assert(actual)(isLeft) - }, - test(""" email >= "avi@gmail.com" && subject.beginsWith("ma") """) { - val actual = - KeyConditionExpression( - email >= "avi@gmail.com" && subject.beginsWith("ma") - ) - zio.test.assert(actual)(isLeft) - }, - test(""" email === "avi@gmail.com" && subject.beginsWith("ma") && subject.beginsWith("ma") """) { - val actual = - KeyConditionExpression( - email === "avi@gmail.com" && subject.beginsWith("ma") && subject.beginsWith("ma") - ) - zio.test.assert(actual)(isLeft) - } - ) - - import Generators._ - - val pbtSuite = - suite("property based suite")(test("conversion of ConditionExpression to KeyConditionExpression must be valid") { - check(genConditionExpression) { - case (condExprn, seedDataList) => assertConditionExpression(condExprn, seedDataList) - } - }) - - def assertConditionExpression( - condExprn: ConditionExpression[_], - seedDataList: List[SeedData], - printCondExprn: Boolean = false - ): TestResult = { - val errorOrkeyCondExprn: Either[String, KeyConditionExpression] = KeyConditionExpression(condExprn) - - if (printCondExprn) println(condExprn) else () - - assert(errorOrkeyCondExprn) { - if ( - seedDataList.length == 0 || seedDataList.length > maxNumOfTerms || seedDataList.head._2 != ComparisonOp.Equals - ) - isLeft - else - isRight - } - } - - object Generators { - sealed trait ComparisonOp - object ComparisonOp { - case object Equals extends ComparisonOp - case object NotEquals extends ComparisonOp - case object LessThan extends ComparisonOp - case object GreaterThan extends ComparisonOp - case object LessThanOrEqual extends ComparisonOp - case object GreaterThanOrEqual extends ComparisonOp - - val set = Set(Equals, NotEquals, LessThan, GreaterThan, LessThanOrEqual, GreaterThanOrEqual) - } - - val maxNumOfTerms = 2 - val genOP = Gen.fromIterable(ComparisonOp.set) - private val genFieldName: Gen[Sized, String] = Gen.alphaNumericStringBounded(1, 5) - val genFieldNameAndOpList: Gen[Sized, List[(String, ComparisonOp)]] = - Gen.listOfBounded(0, maxNumOfTerms + 1)( // ensure we generate more that max number of terms - genFieldName zip genOP - ) - final case class FieldNameAndComparisonOp(fieldName: String, op: ComparisonOp) - type SeedData = (String, ComparisonOp) - val genConditionExpression: Gen[Sized, (ConditionExpression[_], List[SeedData])] = - genFieldNameAndOpList.filter(_.nonEmpty).map { xs => - val (name, op) = xs.head - val first = conditionExpression(name, op) - // TODO: generate joining ops - val condEx = xs.tail.foldRight(first) { - case ((name, op), acc) => - acc.asInstanceOf[ConditionExpression[Any]] && conditionExpression(name, op) - .asInstanceOf[ConditionExpression[Any]] - } - (condEx, xs) - } - - def conditionExpression(name: String, op: ComparisonOp): ConditionExpression[_] = - op match { - case ComparisonOp.Equals => - ConditionExpression.Equals( - ProjectionExpressionOperand(MapElement(Root, name)), - ConditionExpression.Operand.ValueOperand(AttributeValue.String("SOME_VALUE")) - ) - case ComparisonOp.NotEquals => - ConditionExpression.NotEqual( - ProjectionExpressionOperand(MapElement(Root, name)), - ConditionExpression.Operand.ValueOperand(AttributeValue.String("SOME_VALUE")) - ) - case ComparisonOp.LessThan => - ConditionExpression.LessThan( - ProjectionExpressionOperand(MapElement(Root, name)), - ConditionExpression.Operand.ValueOperand(AttributeValue.String("SOME_VALUE")) - ) - case ComparisonOp.GreaterThan => - ConditionExpression.GreaterThan( - ProjectionExpressionOperand(MapElement(Root, name)), - ConditionExpression.Operand.ValueOperand(AttributeValue.String("SOME_VALUE")) - ) - case ComparisonOp.LessThanOrEqual => - ConditionExpression.LessThanOrEqual( - ProjectionExpressionOperand(MapElement(Root, name)), - ConditionExpression.Operand.ValueOperand(AttributeValue.String("SOME_VALUE")) - ) - case ComparisonOp.GreaterThanOrEqual => - ConditionExpression.GreaterThanOrEqual( - ProjectionExpressionOperand(MapElement(Root, name)), - ConditionExpression.Operand.ValueOperand(AttributeValue.String("SOME_VALUE")) - ) - } - } - -} diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index a5ed16fcd..86edbb3a8 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -9,7 +9,7 @@ import zio.dynamodb.DynamoDBQuery object KeyConditionExprExample extends App { import zio.dynamodb.KeyConditionExpr._ - import zio.dynamodb.KeyConditionExpr.SortKeyExpr + import zio.dynamodb.KeyConditionExpr.SortKeyEquals import zio.dynamodb.ProjectionExpression.$ val x6: CompositePrimaryKeyExpr[Any, String] = $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey === "y" @@ -31,9 +31,9 @@ object KeyConditionExprExample extends App { val (email, subject, age, binary, binary2) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyExpr[Student, String] = Student.email.partitionKey === "x" + val pk: PartitionKeyEquals[Student, String] = Student.email.partitionKey === "x" // val pkX: PartitionKeyExpr[Student, String] = Student.age.primaryKey === "x" // as expected does not compile - val sk1: SortKeyExpr[Student, String] = Student.subject.sortKey === "y" + val sk1: SortKeyEquals[Student, String] = Student.subject.sortKey === "y" val sk2: ExtendedSortKeyExpr[Student, String] = Student.subject.sortKey > "y" val pkAndSk = Student.email.partitionKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExpressionExamples.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExpressionExamples.scala deleted file mode 100644 index a490b6b2e..000000000 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExpressionExamples.scala +++ /dev/null @@ -1,15 +0,0 @@ -package zio.dynamodb.examples - -import zio.dynamodb.PartitionKeyExpression._ -import zio.dynamodb.SortKeyExpression._ -import zio.dynamodb._ - -object KeyConditionExpressionExamples extends App { - - val exprn1: KeyConditionExpression = PartitionKey("partitionKey1") === "x" - - val exprn2: KeyConditionExpression = PartitionKey("partitionKey1") === "x" && SortKey("sortKey1") > "X" - - val exprn3: KeyConditionExpression = PartitionKey("partitionKey1") === "x" && SortKey("sortKey1") === "X" - -} diff --git a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala index 0fd448a3a..29d8576b9 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala @@ -102,7 +102,7 @@ object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { object Repository { def genericFindById[A <: Invoice]( - pkExpr: KeyConditionExpr.PartitionKeyExpr[A, String] + pkExpr: KeyConditionExpr.PartitionKeyEquals[A, String] )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Either[DynamoDBError, Invoice]] = DynamoDBQuery.get("table1", pkExpr).execute From d0c10145419a60ade64618ad00ec6a9684103e8b Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 08:45:58 +0100 Subject: [PATCH 43/69] renames from tmp names to actual --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 38 +++++++++---------- ...PartitionKey2.scala => PartitionKey.scala} | 2 +- .../zio/dynamodb/ProjectionExpression.scala | 16 ++++---- .../{SortKey2.scala => SortKey.scala} | 18 ++++----- .../scala/zio/dynamodb/keyConditionExpr.scala | 18 ++++----- .../zio/dynamodb/AliasMapRenderSpec.scala | 4 +- .../examples/KeyConditionExprExample.scala | 10 ++--- .../examples/QueryAndScanExamples.scala | 6 +-- 8 files changed, 56 insertions(+), 56 deletions(-) rename dynamodb/src/main/scala/zio/dynamodb/{PartitionKey2.scala => PartitionKey.scala} (82%) rename dynamodb/src/main/scala/zio/dynamodb/{SortKey2.scala => SortKey.scala} (85%) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 4599393a7..037be50cc 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -219,7 +219,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .querySome[ExpressionAttrNames](tableName, 1) - .whereKey($(id).primaryKey === second && $(number).sortKey > 0) + .whereKey($(id).partitionKey === second && $(number).sortKey > 0) .filter(ExpressionAttrNames.ttl.notExists) for { @@ -360,7 +360,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .queryAllItem(tableName) - .whereKey($("id").primaryKey === "id") + .whereKey($("id").partitionKey === "id") .filter($("ttl").notExists) query.execute.flatMap(_.runDrain).exit.map { result => assert(result)(succeeds(isUnit)) @@ -371,7 +371,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .querySomeItem(tableName, 1) - .whereKey($("id").primaryKey === "id") + .whereKey($("id").partitionKey === "id") .filter($("ttl").notExists) query.execute.exit.map { result => assert(result.isSuccess)(isTrue) @@ -382,7 +382,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .querySomeItem(tableName, 1, $("ttl")) - .whereKey($("id").primaryKey === "id") + .whereKey($("id").partitionKey === "id") query.execute.exit.map { result => assert(result.isSuccess)(isTrue) } @@ -605,7 +605,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = queryAllItem(tableName, $(name), $("ttl")).whereKey( - $(id).primaryKey === first && $(number).sortKey > 0 + $(id).partitionKey === first && $(number).sortKey > 0 ) query.execute.flatMap(_.runDrain).map { _ => @@ -617,7 +617,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name), $("ttl")) - .whereKey($(id).primaryKey === first && $(number).sortKey > 0) + .whereKey($(id).partitionKey === first && $(number).sortKey > 0) .execute .map(_._1) } yield assert(chunk)( @@ -629,7 +629,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey($(id).primaryKey === first && $(number).sortKey < 2) + .whereKey($(id).partitionKey === first && $(number).sortKey < 2) .execute .map(_._1) } yield assert(chunk)( @@ -641,7 +641,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey($(id).primaryKey === first && $(number).sortKey > 0) + .whereKey($(id).partitionKey === first && $(number).sortKey > 0) .execute .map(_._1) } yield assert(chunk)( @@ -654,7 +654,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey($(id).primaryKey === first && $(number).sortKey >= 4) + .whereKey($(id).partitionKey === first && $(number).sortKey >= 4) .execute .map(_._1) } yield assert(chunk)( @@ -666,7 +666,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey($(id).primaryKey === first && $(number).sortKey <= 4) + .whereKey($(id).partitionKey === first && $(number).sortKey <= 4) .execute .map(_._1) } yield assert(chunk)( @@ -678,7 +678,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey($(id).primaryKey === "nowhere" && $(number).sortKey > 0) + .whereKey($(id).partitionKey === "nowhere" && $(number).sortKey > 0) .execute .map(_._1) } yield assert(chunk)(isEmpty) @@ -688,7 +688,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 1, $(name)) - .whereKey($(id).primaryKey === first) + .whereKey($(id).partitionKey === first) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(Item(name -> avi)))) @@ -698,7 +698,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 3, $(name)) - .whereKey($(id).primaryKey === first) + .whereKey($(id).partitionKey === first) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(Item(name -> avi), Item(name -> avi2), Item(name -> avi3)))) @@ -708,7 +708,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 4, $(name)) - .whereKey($(id).primaryKey === first) + .whereKey($(id).partitionKey === first) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(Item(name -> avi), Item(name -> avi2), Item(name -> avi3)))) @@ -718,11 +718,11 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { startKey <- querySomeItem(tableName, 2, $(id), $(number)) - .whereKey($(id).primaryKey === first) + .whereKey($(id).partitionKey === first) .execute .map(_._2) chunk <- querySomeItem(tableName, 5, $(name)) - .whereKey($(id).primaryKey === first) + .whereKey($(id).partitionKey === first) .startKey(startKey) .execute .map(_._1) @@ -733,7 +733,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { stream <- queryAllItem(tableName) - .whereKey($(id).primaryKey === second) + .whereKey($(id).partitionKey === second) .execute chunk <- stream.run(ZSink.collectAll[Item]) } yield assert(chunk)( @@ -756,7 +756,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => for { chunk <- querySomeItem(tableName, 10, $(name)) - .whereKey($(id).primaryKey === first && $(number).sortKey.between(3, 8)) + .whereKey($(id).partitionKey === first && $(number).sortKey.between(3, 8)) .execute .map(_._1) } yield assert(chunk)( @@ -771,7 +771,7 @@ object LiveSpec extends ZIOSpecDefault { for { _ <- putItem(tableName, stringSortKeyItem).execute chunk <- querySomeItem(tableName, 10) - .whereKey($(id).primaryKey === adam && $(name).sortKey.beginsWith("ad")) + .whereKey($(id).partitionKey === adam && $(name).sortKey.beginsWith("ad")) .execute .map(_._1) } yield assert(chunk)(equalTo(Chunk(stringSortKeyItem))) diff --git a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey.scala similarity index 82% rename from dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala rename to dynamodb/src/main/scala/zio/dynamodb/PartitionKey.scala index e21d6dbb6..77c214e05 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey2.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey.scala @@ -4,7 +4,7 @@ import zio.dynamodb.KeyConditionExpr.PartitionKeyEquals import zio.dynamodb.proofs.RefersTo // belongs to the package top level -private[dynamodb] final case class PartitionKey2[-From, +To](keyName: String) { self => +private[dynamodb] final case class PartitionKey[-From, +To](keyName: String) { self => def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 )(implicit ev: RefersTo[To1, To2]): PartitionKeyEquals[From, To] = { diff --git a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala index 1861f96ab..e8437b1ec 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/ProjectionExpression.scala @@ -134,17 +134,17 @@ sealed trait ProjectionExpression[-From, +To] { self => trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 { implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) { - def partitionKey(implicit ev: IsPrimaryKey[To]): PartitionKey2[From, To] = { + def partitionKey(implicit ev: IsPrimaryKey[To]): PartitionKey[From, To] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => PartitionKey2[From, To](key) + case ProjectionExpression.MapElement(_, key) => PartitionKey[From, To](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } - def sortKey(implicit ev: IsPrimaryKey[To]): SortKey2[From, To] = { + def sortKey(implicit ev: IsPrimaryKey[To]): SortKey[From, To] = { val _ = ev self match { - case ProjectionExpression.MapElement(_, key) => SortKey2[From, To](key) + case ProjectionExpression.MapElement(_, key) => SortKey[From, To](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } } @@ -544,14 +544,14 @@ object ProjectionExpression extends ProjectionExpressionLowPriorityImplicits0 { implicit class ProjectionExpressionSyntax[From](self: ProjectionExpression[From, Unknown]) { - def primaryKey: PartitionKey2[From, Unknown] = + def partitionKey: PartitionKey[From, Unknown] = self match { - case ProjectionExpression.MapElement(_, key) => PartitionKey2[From, Unknown](key) + case ProjectionExpression.MapElement(_, key) => PartitionKey[From, Unknown](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } - def sortKey: SortKey2[From, Unknown] = + def sortKey: SortKey[From, Unknown] = self match { - case ProjectionExpression.MapElement(_, key) => SortKey2[From, Unknown](key) + case ProjectionExpression.MapElement(_, key) => SortKey[From, Unknown](key) case _ => throw new IllegalArgumentException("Not a partition key") // should not happen } diff --git a/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala b/dynamodb/src/main/scala/zio/dynamodb/SortKey.scala similarity index 85% rename from dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala rename to dynamodb/src/main/scala/zio/dynamodb/SortKey.scala index 3e4c3af9d..85bfa1d34 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/SortKey2.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/SortKey.scala @@ -6,14 +6,14 @@ import zio.dynamodb.proofs.CanSortKeyBeginsWith import zio.dynamodb.KeyConditionExpr.SortKeyEquals import zio.dynamodb.KeyConditionExpr.ExtendedSortKeyExpr -private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self => +private[dynamodb] final case class SortKey[-From, +To](keyName: String) { self => // all comparison ops apply to: Strings, Numbers, Binary values def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 )(implicit ev: RefersTo[To1, To2]): SortKeyEquals[From, To2] = { val _ = ev SortKeyEquals[From, To2]( - self.asInstanceOf[SortKey2[From, To2]], + self.asInstanceOf[SortKey[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) } @@ -22,7 +22,7 @@ private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.GreaterThan( - self.asInstanceOf[SortKey2[From, To2]], + self.asInstanceOf[SortKey[From, To2]], implicitly(ToAttributeValue[To2]).toAttributeValue(value) ) } @@ -31,7 +31,7 @@ private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.LessThan( - self.asInstanceOf[SortKey2[From, To2]], + self.asInstanceOf[SortKey[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) } @@ -40,7 +40,7 @@ private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.NotEqual( - self.asInstanceOf[SortKey2[From, To2]], + self.asInstanceOf[SortKey[From, To2]], implicitly(ToAttributeValue[To2]).toAttributeValue(value) ) } @@ -49,7 +49,7 @@ private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.LessThanOrEqual( - self.asInstanceOf[SortKey2[From, To2]], + self.asInstanceOf[SortKey[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) } @@ -58,14 +58,14 @@ private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self )(implicit ev: RefersTo[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.GreaterThanOrEqual( - self.asInstanceOf[SortKey2[From, To2]], + self.asInstanceOf[SortKey[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(value) ) } // applies to all PK types def between[To: ToAttributeValue, IsPrimaryKey](min: To, max: To): ExtendedSortKeyExpr[From, To] = ExtendedSortKeyExpr.Between[From, To]( - self.asInstanceOf[SortKey2[From, To]], + self.asInstanceOf[SortKey[From, To]], implicitly[ToAttributeValue[To]].toAttributeValue(min), implicitly[ToAttributeValue[To]].toAttributeValue(max) ) @@ -76,7 +76,7 @@ private[dynamodb] final case class SortKey2[-From, +To](keyName: String) { self )(implicit ev: CanSortKeyBeginsWith[To1, To2]): ExtendedSortKeyExpr[From, To2] = { val _ = ev ExtendedSortKeyExpr.BeginsWith[From, To2]( - self.asInstanceOf[SortKey2[From, To2]], + self.asInstanceOf[SortKey[From, To2]], implicitly[ToAttributeValue[To2]].toAttributeValue(prefix) ) } diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 6d263b1c2..86c2152c9 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -14,7 +14,7 @@ object KeyConditionExpr { // email.primaryKey === "x" // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" - private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey2[From, To], value: AttributeValue) + private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey[From, To], value: AttributeValue) extends KeyConditionExpr[From, To] { self => def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1, To2] = @@ -28,7 +28,7 @@ object KeyConditionExpr { AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") } - private[dynamodb] final case class SortKeyEquals[-From, +To](sortKey: SortKey2[From, To], value: AttributeValue) { + private[dynamodb] final case class SortKeyEquals[-From, +To](sortKey: SortKey[From, To], value: AttributeValue) { self => def render2: AliasMapRender[String] = AliasMapRender @@ -106,22 +106,22 @@ object KeyConditionExpr { } object ExtendedSortKeyExpr { - private[dynamodb] final case class GreaterThan[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class GreaterThan[From, +To](sortKey: SortKey[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class LessThan[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class LessThan[From, +To](sortKey: SortKey[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class NotEqual[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class NotEqual[From, +To](sortKey: SortKey[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class LessThanOrEqual[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class LessThanOrEqual[From, +To](sortKey: SortKey[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class GreaterThanOrEqual[From, +To](sortKey: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class GreaterThanOrEqual[From, +To](sortKey: SortKey[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] private[dynamodb] final case class Between[From, +To]( - left: SortKey2[From, To], + left: SortKey[From, To], min: AttributeValue, max: AttributeValue ) extends ExtendedSortKeyExpr[From, To] - private[dynamodb] final case class BeginsWith[From, +To](left: SortKey2[From, To], value: AttributeValue) + private[dynamodb] final case class BeginsWith[From, +To](left: SortKey[From, To], value: AttributeValue) extends ExtendedSortKeyExpr[From, To] } diff --git a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala index 72f18a7b7..4879ae664 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala @@ -420,7 +420,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = KeyConditionExpr - .PartitionKeyEquals($("num").primaryKey, one) + .PartitionKeyEquals($("num").partitionKey, one) .render .execute @@ -437,7 +437,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { val (aliasMap, expression) = KeyConditionExpr .ExtendedCompositePrimaryKeyExpr( - KeyConditionExpr.PartitionKeyEquals($("num").primaryKey, two), + KeyConditionExpr.PartitionKeyEquals($("num").partitionKey, two), KeyConditionExpr.ExtendedSortKeyExpr.Between($("num").sortKey, one, three) ) .render diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index 86edbb3a8..c92b0176c 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -12,12 +12,12 @@ object KeyConditionExprExample extends App { import zio.dynamodb.KeyConditionExpr.SortKeyEquals import zio.dynamodb.ProjectionExpression.$ - val x6: CompositePrimaryKeyExpr[Any, String] = $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey === "y" - val x7 = $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey > 1 + val x6: CompositePrimaryKeyExpr[Any, String] = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey === "y" + val x7 = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey > 1 val x8: ExtendedCompositePrimaryKeyExpr[Any, Int] = - $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey.between(1, 2) + $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey.between(1, 2) val x9 = - $("foo.bar").primaryKey === 1 && $("foo.baz").sortKey.beginsWith(1L) + $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey.beginsWith(1L) final case class Elephant(email: String, subject: String, age: Int) object Elephant { @@ -54,5 +54,5 @@ object KeyConditionExprExample extends App { val (aliasMap, s) = pkAndSkExtended1.render.execute println(s"aliasMap=$aliasMap, s=$s") - val get = DynamoDBQuery.queryAllItem("table").whereKey($("foo.bar").primaryKey === 1 && $("foo.baz").sortKey > 1) + val get = DynamoDBQuery.queryAllItem("table").whereKey($("foo.bar").partitionKey === 1 && $("foo.baz").sortKey > 1) } diff --git a/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala b/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala index e5b763237..6e09340d9 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/QueryAndScanExamples.scala @@ -17,7 +17,7 @@ object QueryAndScanExamples extends App { val queryAll: ZIO[DynamoDBExecutor, Throwable, Stream[Throwable, Item]] = queryAllItem("tableName1", $("A"), $("B"), $("C")) .whereKey( - $("partitionKey1").primaryKey === "x" && + $("partitionKey1").partitionKey === "x" && $("sortKey1").sortKey > "X" ) .execute @@ -25,7 +25,7 @@ object QueryAndScanExamples extends App { val querySome: ZIO[DynamoDBExecutor, Throwable, (Chunk[Item], LastEvaluatedKey)] = querySomeItem("tableName1", limit = 10, $("A"), $("B"), $("C")) .sortOrder(ascending = false) - .whereKey($("partitionKey1").primaryKey === "x" && $("sortKey1").sortKey > "X") + .whereKey($("partitionKey1").partitionKey === "x" && $("sortKey1").sortKey > "X") .selectCount .execute @@ -38,7 +38,7 @@ object QueryAndScanExamples extends App { $("B"), $("C") ) - .whereKey($("partitionKey1").primaryKey === "x" && $("sortKey1").sortKey > "X") + .whereKey($("partitionKey1").partitionKey === "x" && $("sortKey1").sortKey > "X") .selectCount).sortOrder(ascending = true) } From 8d18e86ad76de44af250d7ba9a1d6ceff807c134 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 09:08:51 +0100 Subject: [PATCH 44/69] minor clean up --- .../src/main/scala/zio/dynamodb/DynamoDBQuery.scala | 2 +- .../src/main/scala/zio/dynamodb/PartitionKey.scala | 1 - .../main/scala/zio/dynamodb/keyConditionExpr.scala | 11 +++++++---- .../StudentZioDynamoDbTypeSafeAPIExample.scala | 13 ------------- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 1e4eb4264..f2ba8f8d9 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -465,7 +465,7 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } - def get[From: Schema, To]( + def get[From: Schema, To]( tableName: String, partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To], projections: ProjectionExpression[_, _]* diff --git a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey.scala b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey.scala index 77c214e05..e623d9a68 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/PartitionKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/PartitionKey.scala @@ -3,7 +3,6 @@ package zio.dynamodb import zio.dynamodb.KeyConditionExpr.PartitionKeyEquals import zio.dynamodb.proofs.RefersTo -// belongs to the package top level private[dynamodb] final case class PartitionKey[-From, +To](keyName: String) { self => def ===[To1 >: To, To2: ToAttributeValue, IsPrimaryKey]( value: To2 diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala index 86c2152c9..0a6db9bc9 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala @@ -3,16 +3,19 @@ package zio.dynamodb import zio.dynamodb.PrimaryKey /** - * Typesafe KeyConditionExpr/primary key experiment + * Models: + * 1) partition key equality expressions + * 2) composite primary key expressions where sort key expression is equality + * 3) extended composite primary key expressions where sort key is not equality eg >, <, >=, <=, between, begins_with + * + * Note 1), 2) and 3) are all valid key condition expressions + * BUT only 1) and 2) are valid primary key expressions that can be used in GetItem, UpdateItem and DeleteItem DynamoDB queries */ sealed trait KeyConditionExpr[-From, +To] extends Renderable { self => def render: AliasMapRender[String] } object KeyConditionExpr { - // models primary key expressions - // email.primaryKey === "x" - // Student.email.primaryKey === "x" && Student.subject.sortKey === "y" private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey[From, To], value: AttributeValue) extends KeyConditionExpr[From, To] { self => diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index abe499304..b7d5152e0 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -29,19 +29,6 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { _ <- batchWriteFromStream(ZStream(avi, adam)) { student => put("student", student) }.runDrain - /* - - must be a scalar - - mandatory Partition Key and Optional sort key - - String, Number or Binary - - defined once, used many times - problem with `Student.email.set("x"), Student.subject.set("y")` is that we can not create a function for this to reuse - Student.email.partitionKey === "x" // qualifies as a both a PK and a WhereKeyExpression - Student.email.partitionKey === "x" && Student.subject.sortKey === "y" // qualifies as a both a PK and a WhereKeyExpression - Student.email.partitionKey === "x" && Student.subject.sortKey > 1 // qualifies as only a WhereKeyExpression - */ -// found <- get("table", Student.email.set("x"), Student.subject.set("y")).execute -// _ = println(s"XXXXXXXX found=$found") - _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) From 6df09c228a7feee4b56bbd504e967ef4b57363e7 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 09:16:57 +0100 Subject: [PATCH 45/69] scalafmt --- dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala | 2 +- dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index f2ba8f8d9..1e4eb4264 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -465,7 +465,7 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } - def get[From: Schema, To]( + def get[From: Schema, To]( tableName: String, partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To], projections: ProjectionExpression[_, _]* diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index 7602bac85..26a4907ca 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -2,10 +2,9 @@ package zio.dynamodb.proofs import scala.annotation.implicitNotFound -@implicitNotFound("DynamoDB does not support primary key type ${A}") +@implicitNotFound("DynamoDB does not support primary key type ${A} - allowed types are: String, Number, Binary") sealed trait IsPrimaryKey[A] -// Allowed types for partition and sort keys are: String, Number, Binary object IsPrimaryKey { implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} // TODO: Avi - support other numeric types From 2e46f465e780c71e9dfdf9b6ae0a1949b61fa941 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 10:14:11 +0100 Subject: [PATCH 46/69] git file names are not case sensitive AAAGGHH! --- .../dynamodb/{keyConditionExpr.scala => KeyConditionExpr2.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dynamodb/src/main/scala/zio/dynamodb/{keyConditionExpr.scala => KeyConditionExpr2.scala} (100%) diff --git a/dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr2.scala similarity index 100% rename from dynamodb/src/main/scala/zio/dynamodb/keyConditionExpr.scala rename to dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr2.scala From b8b7e252e95e3e3909e9534cfec20bb5d3b9c6b3 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 10:17:28 +0100 Subject: [PATCH 47/69] git file names are not case sensitive AAAGGHH! pt2 --- dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala | 1 + .../dynamodb/{KeyConditionExpr2.scala => KeyConditionExpr.scala} | 0 2 files changed, 1 insertion(+) rename dynamodb/src/main/scala/zio/dynamodb/{KeyConditionExpr2.scala => KeyConditionExpr.scala} (100%) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 1e4eb4264..44ca306e4 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -29,6 +29,7 @@ import zio.schema.Schema import zio.stream.Stream import zio.{ Chunk, NonEmptyChunk, Schedule, ZIO, Zippable => _, _ } import zio.dynamodb.proofs.IsPrimaryKey +import zio.dynamodb.KeyConditionExpr sealed trait DynamoDBQuery[-In, +Out] { self => diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr2.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala similarity index 100% rename from dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr2.scala rename to dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala From eba3ae071991df65b68ef221ac5baf7997c79f54 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 11:17:25 +0100 Subject: [PATCH 48/69] delete unnecessary imports --- dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala | 1 - dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala | 2 -- 2 files changed, 3 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 44ca306e4..1e4eb4264 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -29,7 +29,6 @@ import zio.schema.Schema import zio.stream.Stream import zio.{ Chunk, NonEmptyChunk, Schedule, ZIO, Zippable => _, _ } import zio.dynamodb.proofs.IsPrimaryKey -import zio.dynamodb.KeyConditionExpr sealed trait DynamoDBQuery[-In, +Out] { self => diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index 0a6db9bc9..046e75133 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -1,7 +1,5 @@ package zio.dynamodb -import zio.dynamodb.PrimaryKey - /** * Models: * 1) partition key equality expressions From c76be3e005e288a94c5b359c68806009c7429235 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 11:26:58 +0100 Subject: [PATCH 49/69] delete unnecessary imports --- dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala index 4879ae664..4c5ce5696 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala @@ -1,7 +1,6 @@ package zio.dynamodb import zio.Chunk -import zio.dynamodb.KeyConditionExpr import zio.dynamodb.ProjectionExpression.$ import zio.dynamodb.ProjectionExpression._ import zio.test.Assertion._ From 5d58963e5b436098ef672875db86a14f681986d9 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 11:34:19 +0100 Subject: [PATCH 50/69] explicit type annotations for implicits --- .../main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index 26a4907ca..33dc7260e 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -6,14 +6,14 @@ import scala.annotation.implicitNotFound sealed trait IsPrimaryKey[A] object IsPrimaryKey { - implicit val intIsPrimaryKey = new IsPrimaryKey[Int] {} + implicit val intIsPrimaryKey: IsPrimaryKey[Int] = new IsPrimaryKey[Int] {} // TODO: Avi - support other numeric types - implicit val stringIsPrimaryKey = new IsPrimaryKey[String] {} + implicit val stringIsPrimaryKey: IsPrimaryKey[String] = new IsPrimaryKey[String] {} // binary data - implicit val binaryIsPrimaryKey = new IsPrimaryKey[Iterable[Byte]] {} - implicit val binaryIsPrimaryKey2 = new IsPrimaryKey[List[Byte]] {} - implicit val binaryIsPrimaryKey3 = new IsPrimaryKey[Vector[Byte]] {} + implicit val binaryIsPrimaryKey: IsPrimaryKey[Iterable[Byte]] = new IsPrimaryKey[Iterable[Byte]] {} + implicit val binaryIsPrimaryKey2: IsPrimaryKey[List[Byte]] = new IsPrimaryKey[List[Byte]] {} + implicit val binaryIsPrimaryKey3: IsPrimaryKey[Vector[Byte]] = new IsPrimaryKey[Vector[Byte]] {} // TODO: Avi - other collection types } From 30a51cb8e6159c1a6de4e78b5b2867c8be9e2526 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 11:43:02 +0100 Subject: [PATCH 51/69] scalafmt --- .../src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index 33dc7260e..64c481a5a 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -12,8 +12,8 @@ object IsPrimaryKey { implicit val stringIsPrimaryKey: IsPrimaryKey[String] = new IsPrimaryKey[String] {} // binary data - implicit val binaryIsPrimaryKey: IsPrimaryKey[Iterable[Byte]] = new IsPrimaryKey[Iterable[Byte]] {} - implicit val binaryIsPrimaryKey2: IsPrimaryKey[List[Byte]] = new IsPrimaryKey[List[Byte]] {} - implicit val binaryIsPrimaryKey3: IsPrimaryKey[Vector[Byte]] = new IsPrimaryKey[Vector[Byte]] {} + implicit val binaryIsPrimaryKey: IsPrimaryKey[Iterable[Byte]] = new IsPrimaryKey[Iterable[Byte]] {} + implicit val binaryIsPrimaryKey2: IsPrimaryKey[List[Byte]] = new IsPrimaryKey[List[Byte]] {} + implicit val binaryIsPrimaryKey3: IsPrimaryKey[Vector[Byte]] = new IsPrimaryKey[Vector[Byte]] {} // TODO: Avi - other collection types } From f0e3c42828ad6c80f38f6f757648601a8714c104 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 11:46:53 +0100 Subject: [PATCH 52/69] renames --- .../main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala | 4 ++-- dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala index 85e3770ec..35f9aa9e7 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBExecutorImpl.scala @@ -773,7 +773,7 @@ case object DynamoDBExecutorImpl { def awsQueryRequest(queryAll: QueryAll): QueryRequest = { val (aliasMap, (maybeFilterExpr, maybeKeyExpr, projections)) = (for { filter <- AliasMapRender.collectAll(queryAll.filterExpression.map(_.render)) - keyExpr <- AliasMapRender.collectAll(queryAll.keyConditionExpression.map(_.render)) + keyExpr <- AliasMapRender.collectAll(queryAll.keyConditionExpr.map(_.render)) projections <- AliasMapRender.forEach(queryAll.projections) } yield (filter, keyExpr, projections)).execute @@ -797,7 +797,7 @@ case object DynamoDBExecutorImpl { private def awsQueryRequest(querySome: QuerySome): QueryRequest = { val (aliasMap, (maybeFilterExpr, maybeKeyExpr, projections)) = (for { filter <- AliasMapRender.collectAll(querySome.filterExpression.map(_.render)) - keyExpr <- AliasMapRender.collectAll(querySome.keyConditionExpression2.map(_.render)) + keyExpr <- AliasMapRender.collectAll(querySome.keyConditionExpr.map(_.render)) projections <- AliasMapRender.forEach(querySome.projections) } yield (filter, keyExpr, projections)).execute diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 1e4eb4264..6e24ea826 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -335,9 +335,9 @@ sealed trait DynamoDBQuery[-In, +Out] { self => case Absolve(query) => Absolve(query.whereKey(keyConditionExpression)) case s: QuerySome => - s.copy(keyConditionExpression2 = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + s.copy(keyConditionExpr = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] case s: QueryAll => - s.copy(keyConditionExpression = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] + s.copy(keyConditionExpr = Some(keyConditionExpression)).asInstanceOf[DynamoDBQuery[In, Out]] case _ => self } @@ -836,7 +836,7 @@ object DynamoDBQuery { exclusiveStartKey: LastEvaluatedKey = None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, - keyConditionExpression2: Option[KeyConditionExpr[_, _]] = None, + keyConditionExpr: Option[KeyConditionExpr[_, _]] = None, projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes @@ -869,7 +869,7 @@ object DynamoDBQuery { exclusiveStartKey: LastEvaluatedKey = None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, - keyConditionExpression: Option[KeyConditionExpr[_, _]] = None, + keyConditionExpr: Option[KeyConditionExpr[_, _]] = None, projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes From d81880c2364da726bb36de8f6c22246f810763d3 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 12:10:44 +0100 Subject: [PATCH 53/69] make type param contravariant, more numbers --- .../scala/zio/dynamodb/proofs/IsPrimaryKey.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index 64c481a5a..a04d0cc7a 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -3,17 +3,17 @@ package zio.dynamodb.proofs import scala.annotation.implicitNotFound @implicitNotFound("DynamoDB does not support primary key type ${A} - allowed types are: String, Number, Binary") -sealed trait IsPrimaryKey[A] +sealed trait IsPrimaryKey[-A] object IsPrimaryKey { - implicit val intIsPrimaryKey: IsPrimaryKey[Int] = new IsPrimaryKey[Int] {} - // TODO: Avi - support other numeric types - implicit val stringIsPrimaryKey: IsPrimaryKey[String] = new IsPrimaryKey[String] {} - // binary data + implicit val shortIsPrimaryKey: IsPrimaryKey[Short] = new IsPrimaryKey[Short] {} + implicit val intIsPrimaryKey: IsPrimaryKey[Int] = new IsPrimaryKey[Int] {} + implicit val longIsPrimaryKey: IsPrimaryKey[Long] = new IsPrimaryKey[Long] {} + implicit val floatIsPrimaryKey: IsPrimaryKey[Float] = new IsPrimaryKey[Float] {} + implicit val doubleIsPrimaryKey: IsPrimaryKey[Double] = new IsPrimaryKey[Double] {} + implicit val bigDecimalIsPrimaryKey: IsPrimaryKey[BigDecimal] = new IsPrimaryKey[BigDecimal] {} + implicit val binaryIsPrimaryKey: IsPrimaryKey[Iterable[Byte]] = new IsPrimaryKey[Iterable[Byte]] {} - implicit val binaryIsPrimaryKey2: IsPrimaryKey[List[Byte]] = new IsPrimaryKey[List[Byte]] {} - implicit val binaryIsPrimaryKey3: IsPrimaryKey[Vector[Byte]] = new IsPrimaryKey[Vector[Byte]] {} - // TODO: Avi - other collection types } From 60160c92455997bca956bb8faaa83348058208c4 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 22 Jul 2023 12:32:23 +0100 Subject: [PATCH 54/69] renames --- .../scala/zio/dynamodb/KeyConditionExpr.scala | 8 ++++---- .../scala/zio/dynamodb/AliasMapRenderSpec.scala | 17 ++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index 046e75133..f4f8e1b9a 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -31,7 +31,7 @@ object KeyConditionExpr { private[dynamodb] final case class SortKeyEquals[-From, +To](sortKey: SortKey[From, To], value: AttributeValue) { self => - def render2: AliasMapRender[String] = + def miniRender: AliasMapRender[String] = AliasMapRender .getOrInsert(value) .map(v => s"${sortKey.keyName} = $v") @@ -48,7 +48,7 @@ object KeyConditionExpr { override def render: AliasMapRender[String] = for { pkStr <- pk.render - skStr <- sk.render2 + skStr <- sk.miniRender } yield s"$pkStr AND $skStr" } @@ -61,13 +61,13 @@ object KeyConditionExpr { def render: AliasMapRender[String] = for { pkStr <- pk.render - skStr <- sk.render2 + skStr <- sk.miniRender } yield s"$pkStr AND $skStr" } sealed trait ExtendedSortKeyExpr[-From, +To] { self => - def render2: AliasMapRender[String] = + def miniRender: AliasMapRender[String] = self match { case ExtendedSortKeyExpr.GreaterThan(sk, value) => AliasMapRender diff --git a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala index 4c5ce5696..732bfff2f 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala @@ -319,7 +319,6 @@ object AliasMapRenderSpec extends ZIOSpecDefault { } ) ), - // TODO: Avi - create new tests suite just for KeyConditionExpr ??? suite("KeyConditionExpr")( suite("Sort key expressions")( test("Equals") { @@ -328,7 +327,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.SortKeyEquals($("num").sortKey, one).render2.execute + KeyConditionExpr.SortKeyEquals($("num").sortKey, one).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 1))) && assert(expression)(equalTo("num = :v0")) @@ -339,7 +338,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.ExtendedSortKeyExpr.LessThan($("num").sortKey, one).render2.execute + KeyConditionExpr.ExtendedSortKeyExpr.LessThan($("num").sortKey, one).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 1))) && assert(expression)(equalTo("num < :v0")) @@ -350,7 +349,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.ExtendedSortKeyExpr.NotEqual($("num").sortKey, one).render2.execute + KeyConditionExpr.ExtendedSortKeyExpr.NotEqual($("num").sortKey, one).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 1))) && assert(expression)(equalTo("num <> :v0")) @@ -361,7 +360,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.ExtendedSortKeyExpr.GreaterThan($("num").sortKey, one).render2.execute + KeyConditionExpr.ExtendedSortKeyExpr.GreaterThan($("num").sortKey, one).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 1))) && assert(expression)(equalTo("num > :v0")) @@ -372,7 +371,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.ExtendedSortKeyExpr.LessThanOrEqual($("num").sortKey, one).render2.execute + KeyConditionExpr.ExtendedSortKeyExpr.LessThanOrEqual($("num").sortKey, one).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 1))) && assert(expression)(equalTo("num <= :v0")) @@ -383,7 +382,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.ExtendedSortKeyExpr.GreaterThanOrEqual($("num").sortKey, one).render2.execute + KeyConditionExpr.ExtendedSortKeyExpr.GreaterThanOrEqual($("num").sortKey, one).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 1))) && assert(expression)(equalTo("num >= :v0")) @@ -395,7 +394,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.ExtendedSortKeyExpr.Between($("num").sortKey, one, two).render2.execute + KeyConditionExpr.ExtendedSortKeyExpr.Between($("num").sortKey, one, two).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 2))) && assert(expression)(equalTo("num BETWEEN :v0 AND :v1")) @@ -406,7 +405,7 @@ object AliasMapRenderSpec extends ZIOSpecDefault { ) val (aliasMap, expression) = - KeyConditionExpr.ExtendedSortKeyExpr.BeginsWith($("num").sortKey, name).render2.execute + KeyConditionExpr.ExtendedSortKeyExpr.BeginsWith($("num").sortKey, name).miniRender.execute assert(aliasMap)(equalTo(AliasMap(map, 1))) && assert(expression)(equalTo("begins_with(num, :v0)")) From 32e61e815c518f0b69e8694ada2b1ecf661f3e6b Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 07:28:00 +0100 Subject: [PATCH 55/69] Delete CompositePrimaryKeyExpr type param, add update and delete --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 23 ++++++++++++++++++ .../scala/zio/dynamodb/DynamoDBQuery.scala | 24 ++++++++++++++++++- .../scala/zio/dynamodb/KeyConditionExpr.scala | 20 +++++++++++----- .../examples/KeyConditionExprExample.scala | 4 ++-- .../StudentZioDynamoDbExampleWithOptics.scala | 4 ++-- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 10 ++++---- .../zio/dynamodb/examples/model/Student.scala | 2 ++ 7 files changed, 71 insertions(+), 16 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 037be50cc..71bdf663a 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -177,6 +177,29 @@ object LiveSpec extends ZIOSpecDefault { val (id, num, ttl) = ProjectionExpression.accessors[ExpressionAttrNames] } + final case class ExpressionAttrNamesPkKeyword(ttl: String, num: Int, name: String) + object ExpressionAttrNamesPkKeyword { + implicit val schema: Schema.CaseClass3[String, Int, String, ExpressionAttrNamesPkKeyword] = + DeriveSchema.gen[ExpressionAttrNamesPkKeyword] + val (ttl, num, name) = ProjectionExpression.accessors[ExpressionAttrNamesPkKeyword] + } + + val x: KeyConditionExpr.CompositePrimaryKeyExpr[ExpressionAttrNamesPkKeyword] = + ExpressionAttrNamesPkKeyword.ttl.partitionKey === "id" && ExpressionAttrNamesPkKeyword.num.sortKey === 1 + + // val debugSuite = suite("debug")( + // test("delete should handle keyword") { + // withDefaultTable { tableName => + // val query = DynamoDBQuery + // .delete(tableName, ExpressionAttrNamesPkKeyword.ttl.partitionKey === "id" && ExpressionAttrNamesPkKeyword.num.sortKey === 1) + // .where(ExpressionAttrNames.ttl.notExists) + // query.execute.exit.map { result => + // assert(result)(succeeds(isNone)) + // } + // } + // } + // ) + val mainSuite: Spec[TestEnvironment, Any] = suite("live test")( suite("keywords in expression attribute names")( diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 6e24ea826..38cda4e64 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -476,7 +476,7 @@ object DynamoDBQuery { def get[From: Schema, To]( tableName: String, - compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From, To], + compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From], projections: ProjectionExpression[_, _]* )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { val _ = ev @@ -508,11 +508,33 @@ object DynamoDBQuery { def update[A: Schema](tableName: String, key: PrimaryKey)(action: Action[A]): DynamoDBQuery[A, Option[A]] = updateItem(tableName, key)(action).map(_.flatMap(item => fromItem(item).toOption)) + def update[From: Schema, To](tableName: String, partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To])( + action: Action[From] + ): DynamoDBQuery[From, Option[From]] = + updateItem(tableName, partitionKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) + + def update[From: Schema, To](tableName: String, compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From])( + action: Action[From] + ): DynamoDBQuery[From, Option[From]] = + updateItem(tableName, compositeKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) + def deleteItem(tableName: String, key: PrimaryKey): Write[Any, Option[Item]] = DeleteItem(TableName(tableName), key) def delete[A: Schema](tableName: String, key: PrimaryKey): DynamoDBQuery[Any, Option[A]] = deleteItem(tableName, key).map(_.flatMap(item => fromItem(item).toOption)) + def delete[From: Schema, To]( + tableName: String, + partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To] + ): DynamoDBQuery[Any, Option[From]] = + deleteItem(tableName, partitionKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) + + def delete[From: Schema, To]( + tableName: String, + compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From] + ): DynamoDBQuery[Any, Option[From]] = + deleteItem(tableName, compositeKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) + /** * when executed will return a Tuple of {{{(Chunk[Item], LastEvaluatedKey)}}} */ diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index f4f8e1b9a..0ba972319 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -18,8 +18,8 @@ object KeyConditionExpr { private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey[From, To], value: AttributeValue) extends KeyConditionExpr[From, To] { self => - def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1, To2] = - CompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) + def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1] = + CompositePrimaryKeyExpr[From1](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) def &&[From1 <: From, To2](other: ExtendedSortKeyExpr[From1, To2]): ExtendedCompositePrimaryKeyExpr[From1, To2] = ExtendedCompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) @@ -37,10 +37,18 @@ object KeyConditionExpr { .map(v => s"${sortKey.keyName} = $v") } - private[dynamodb] final case class CompositePrimaryKeyExpr[-From, +To]( - pk: PartitionKeyEquals[From, To], - sk: SortKeyEquals[From, To] - ) extends KeyConditionExpr[From, To] { + private[dynamodb] final case class CompositePrimaryKeyExpr2[-From]( + pk: PartitionKeyEquals[From, Any], + sk: SortKeyEquals[From, Any] + ) extends KeyConditionExpr[From, Any] { + + override def render: AliasMapRender[String] = ??? + } + + private[dynamodb] final case class CompositePrimaryKeyExpr[-From]( + pk: PartitionKeyEquals[From, Any], + sk: SortKeyEquals[From, Any] + ) extends KeyConditionExpr[From, Any] { self => def asAttrMap: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index c92b0176c..a18f46b1a 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -12,7 +12,7 @@ object KeyConditionExprExample extends App { import zio.dynamodb.KeyConditionExpr.SortKeyEquals import zio.dynamodb.ProjectionExpression.$ - val x6: CompositePrimaryKeyExpr[Any, String] = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey === "y" + val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey === "y" val x7 = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey > 1 val x8: ExtendedCompositePrimaryKeyExpr[Any, Int] = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey.between(1, 2) @@ -35,7 +35,7 @@ object KeyConditionExprExample extends App { // val pkX: PartitionKeyExpr[Student, String] = Student.age.primaryKey === "x" // as expected does not compile val sk1: SortKeyEquals[Student, String] = Student.subject.sortKey === "y" val sk2: ExtendedSortKeyExpr[Student, String] = Student.subject.sortKey > "y" - val pkAndSk = Student.email.partitionKey === "x" && Student.subject.sortKey === "y" + val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.partitionKey === "x" && Student.subject.sortKey === "y" //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed val pkAndSkExtended1 = Student.email.partitionKey === "x" && Student.subject.sortKey > "y" diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala index a276ba149..2c9c0ad39 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala @@ -49,13 +49,13 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { ) && email === "avi@gmail.com" && payment === Payment.CreditCard ) .execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { enrollmentDate.set(Some(enrolDate2)) + payment.set(Payment.PayPal) + address .set( Some(Address("line1", "postcode1")) ) }.execute - _ <- delete("student", primaryKey("adam@gmail.com", "english")) + _ <- delete("student", primaryKey2("adam@gmail.com", "english")) .where( enrollmentDate === Some( enrolDate diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index b7d5152e0..ec745ee2c 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -58,27 +58,27 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { ) === "CreditCard" /* && elephantCe */ ) .execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { altPayment.set(Payment.PayPal) + addresses.prependList(List(Address("line0", "postcode0"))) + studentNumber .add(1000) + groups.addSet(Set("group3")) // + elephantAction }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { altPayment.set(Payment.PayPal) + addresses.appendList(List(Address("line3", "postcode3"))) + groups .deleteFromSet(Set("group1")) }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { enrollmentDate.setIfNotExists(Some(enrolDate2)) + payment.set(altPayment) + address .set( Some(Address("line1", "postcode1")) ) // + elephantAction }.execute - _ <- update[Student]("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { addresses.remove(1) }.execute _ <- - delete("student", primaryKey("adam@gmail.com", "english")) + delete("student", primaryKey2("adam@gmail.com", "english")) .where( (enrollmentDate === Some(enrolDate) && payment <> Payment.PayPal && studentNumber .between(1, 3) && groups.contains("group1") && collegeName.contains( diff --git a/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala b/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala index 8bbb418e1..d17dedd7d 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala @@ -78,6 +78,8 @@ object Student { def primaryKey(email: String, subject: String): PrimaryKey = PrimaryKey("email" -> email, "subject" -> subject) + def primaryKey2(email: String, subject: String) = Student.email.partitionKey === email && Student.subject.sortKey === subject + val enrolDate = Instant.parse("2021-03-20T01:39:33Z") val enrolDate2 = Instant.parse("2022-03-20T01:39:33Z") From 0358d12a09aa1d7b0a12760aaa5e9599937e3137 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 07:43:28 +0100 Subject: [PATCH 56/69] fix signatures, scalafmt --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 27 ++++++++++--------- .../scala/zio/dynamodb/DynamoDBQuery.scala | 4 +-- .../scala/zio/dynamodb/KeyConditionExpr.scala | 2 +- .../zio/dynamodb/examples/model/Student.scala | 3 ++- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 71bdf663a..18487e17a 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -187,18 +187,21 @@ object LiveSpec extends ZIOSpecDefault { val x: KeyConditionExpr.CompositePrimaryKeyExpr[ExpressionAttrNamesPkKeyword] = ExpressionAttrNamesPkKeyword.ttl.partitionKey === "id" && ExpressionAttrNamesPkKeyword.num.sortKey === 1 - // val debugSuite = suite("debug")( - // test("delete should handle keyword") { - // withDefaultTable { tableName => - // val query = DynamoDBQuery - // .delete(tableName, ExpressionAttrNamesPkKeyword.ttl.partitionKey === "id" && ExpressionAttrNamesPkKeyword.num.sortKey === 1) - // .where(ExpressionAttrNames.ttl.notExists) - // query.execute.exit.map { result => - // assert(result)(succeeds(isNone)) - // } - // } - // } - // ) + val debugSuite = suite("debug")( + test("delete should handle keyword") { + withDefaultTable { tableName => + val query = DynamoDBQuery + .delete[ExpressionAttrNames]( + tableName, + PrimaryKey("id" -> "id", "num" -> 1) + ) + .where(ExpressionAttrNames.ttl.notExists) + query.execute.exit.map { result => + assert(result)(succeeds(isNone)) + } + } + } + ) val mainSuite: Spec[TestEnvironment, Any] = suite("live test")( diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 38cda4e64..24e413c7d 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -513,7 +513,7 @@ object DynamoDBQuery { ): DynamoDBQuery[From, Option[From]] = updateItem(tableName, partitionKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) - def update[From: Schema, To](tableName: String, compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From])( + def update[From: Schema](tableName: String, compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From])( action: Action[From] ): DynamoDBQuery[From, Option[From]] = updateItem(tableName, compositeKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) @@ -529,7 +529,7 @@ object DynamoDBQuery { ): DynamoDBQuery[Any, Option[From]] = deleteItem(tableName, partitionKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) - def delete[From: Schema, To]( + def delete[From: Schema]( tableName: String, compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From] ): DynamoDBQuery[Any, Option[From]] = diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index 0ba972319..d3558935d 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -18,7 +18,7 @@ object KeyConditionExpr { private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey[From, To], value: AttributeValue) extends KeyConditionExpr[From, To] { self => - def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1] = + def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1] = CompositePrimaryKeyExpr[From1](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) def &&[From1 <: From, To2](other: ExtendedSortKeyExpr[From1, To2]): ExtendedCompositePrimaryKeyExpr[From1, To2] = ExtendedCompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) diff --git a/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala b/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala index d17dedd7d..43c512eb2 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala @@ -78,7 +78,8 @@ object Student { def primaryKey(email: String, subject: String): PrimaryKey = PrimaryKey("email" -> email, "subject" -> subject) - def primaryKey2(email: String, subject: String) = Student.email.partitionKey === email && Student.subject.sortKey === subject + def primaryKey2(email: String, subject: String) = + Student.email.partitionKey === email && Student.subject.sortKey === subject val enrolDate = Instant.parse("2021-03-20T01:39:33Z") val enrolDate2 = Instant.parse("2022-03-20T01:39:33Z") From ee030b6e66ae4359350f074988e042e09b26eb52 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 08:38:00 +0100 Subject: [PATCH 57/69] play with new delete --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 18487e17a..017e122d2 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -177,23 +177,26 @@ object LiveSpec extends ZIOSpecDefault { val (id, num, ttl) = ProjectionExpression.accessors[ExpressionAttrNames] } - final case class ExpressionAttrNamesPkKeyword(ttl: String, num: Int, name: String) + final case class ExpressionAttrNamesPkKeyword(ttl: String, num: Int, name: Option[String]) object ExpressionAttrNamesPkKeyword { - implicit val schema: Schema.CaseClass3[String, Int, String, ExpressionAttrNamesPkKeyword] = + implicit val schema: Schema.CaseClass3[String, Int, Option[String], ExpressionAttrNamesPkKeyword] = DeriveSchema.gen[ExpressionAttrNamesPkKeyword] - val (ttl, num, name) = ProjectionExpression.accessors[ExpressionAttrNamesPkKeyword] + val (ttl, num, name) = ProjectionExpression.accessors[ExpressionAttrNamesPkKeyword] } - val x: KeyConditionExpr.CompositePrimaryKeyExpr[ExpressionAttrNamesPkKeyword] = + val compositeKey1: KeyConditionExpr.CompositePrimaryKeyExpr[ExpressionAttrNamesPkKeyword] = ExpressionAttrNamesPkKeyword.ttl.partitionKey === "id" && ExpressionAttrNamesPkKeyword.num.sortKey === 1 + val compositeKey2: KeyConditionExpr.CompositePrimaryKeyExpr[ExpressionAttrNames] = + ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 + val debugSuite = suite("debug")( test("delete should handle keyword") { withDefaultTable { tableName => val query = DynamoDBQuery - .delete[ExpressionAttrNames]( + .delete( tableName, - PrimaryKey("id" -> "id", "num" -> 1) + compositeKey2 ) .where(ExpressionAttrNames.ttl.notExists) query.execute.exit.map { result => @@ -202,6 +205,9 @@ object LiveSpec extends ZIOSpecDefault { } } ) + .provideSomeLayerShared[TestEnvironment]( + testLayer.orDie + ) @@ nondeterministic val mainSuite: Spec[TestEnvironment, Any] = suite("live test")( From b5aaed8390d19441fbb59437079ae5cb8ecf7cff Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 08:54:04 +0100 Subject: [PATCH 58/69] delete dead code --- .../src/main/scala/zio/dynamodb/KeyConditionExpr.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index d3558935d..d3cbae9f8 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -37,14 +37,6 @@ object KeyConditionExpr { .map(v => s"${sortKey.keyName} = $v") } - private[dynamodb] final case class CompositePrimaryKeyExpr2[-From]( - pk: PartitionKeyEquals[From, Any], - sk: SortKeyEquals[From, Any] - ) extends KeyConditionExpr[From, Any] { - - override def render: AliasMapRender[String] = ??? - } - private[dynamodb] final case class CompositePrimaryKeyExpr[-From]( pk: PartitionKeyEquals[From, Any], sk: SortKeyEquals[From, Any] From 92abe43cd366246d51253e1d70a08223516468f9 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 09:31:58 +0100 Subject: [PATCH 59/69] some it test coverage for new API --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 017e122d2..39476ceef 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -177,26 +177,13 @@ object LiveSpec extends ZIOSpecDefault { val (id, num, ttl) = ProjectionExpression.accessors[ExpressionAttrNames] } - final case class ExpressionAttrNamesPkKeyword(ttl: String, num: Int, name: Option[String]) - object ExpressionAttrNamesPkKeyword { - implicit val schema: Schema.CaseClass3[String, Int, Option[String], ExpressionAttrNamesPkKeyword] = - DeriveSchema.gen[ExpressionAttrNamesPkKeyword] - val (ttl, num, name) = ProjectionExpression.accessors[ExpressionAttrNamesPkKeyword] - } - - val compositeKey1: KeyConditionExpr.CompositePrimaryKeyExpr[ExpressionAttrNamesPkKeyword] = - ExpressionAttrNamesPkKeyword.ttl.partitionKey === "id" && ExpressionAttrNamesPkKeyword.num.sortKey === 1 - - val compositeKey2: KeyConditionExpr.CompositePrimaryKeyExpr[ExpressionAttrNames] = - ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 - val debugSuite = suite("debug")( test("delete should handle keyword") { withDefaultTable { tableName => val query = DynamoDBQuery .delete( tableName, - compositeKey2 + ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 ) .where(ExpressionAttrNames.ttl.notExists) query.execute.exit.map { result => @@ -227,7 +214,6 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .queryAll[ExpressionAttrNames](tableName) - .whereKey(ExpressionAttrNames.id.partitionKey === "id") .filter(ExpressionAttrNames.ttl.notExists) query.execute.flatMap(_.runDrain).exit.map { result => assert(result)(succeeds(isUnit)) @@ -266,7 +252,10 @@ object LiveSpec extends ZIOSpecDefault { test("delete should handle keyword") { withDefaultTable { tableName => val query = DynamoDBQuery - .delete[ExpressionAttrNames](tableName, PrimaryKey("id" -> "id", "num" -> 1)) + .delete( + tableName, + ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 + ) .where(ExpressionAttrNames.ttl.notExists) query.execute.exit.map { result => assert(result)(succeeds(isNone)) @@ -286,7 +275,10 @@ object LiveSpec extends ZIOSpecDefault { test("update should handle keyword") { withDefaultTable { tableName => val query = DynamoDBQuery - .update[ExpressionAttrNames](tableName, PrimaryKey("id" -> "1", "num" -> 1))( + .update( + tableName, + ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 + )( ExpressionAttrNames.ttl.set(Some(42L)) ) .where(ExpressionAttrNames.ttl.notExists) From 84bfb4b91ab418232b154d32879ba41d6c7e7076 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 10:00:41 +0100 Subject: [PATCH 60/69] reduce scope, fix it test --- dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 24e413c7d..9328ab190 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -505,7 +505,7 @@ object DynamoDBQuery { UpdateExpression(action) ) - def update[A: Schema](tableName: String, key: PrimaryKey)(action: Action[A]): DynamoDBQuery[A, Option[A]] = + private[dynamodb] def update[A: Schema](tableName: String, key: PrimaryKey)(action: Action[A]): DynamoDBQuery[A, Option[A]] = updateItem(tableName, key)(action).map(_.flatMap(item => fromItem(item).toOption)) def update[From: Schema, To](tableName: String, partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To])( @@ -520,7 +520,7 @@ object DynamoDBQuery { def deleteItem(tableName: String, key: PrimaryKey): Write[Any, Option[Item]] = DeleteItem(TableName(tableName), key) - def delete[A: Schema](tableName: String, key: PrimaryKey): DynamoDBQuery[Any, Option[A]] = + private[dynamodb] def delete[A: Schema](tableName: String, key: PrimaryKey): DynamoDBQuery[Any, Option[A]] = deleteItem(tableName, key).map(_.flatMap(item => fromItem(item).toOption)) def delete[From: Schema, To]( From 4069d6e6fb430c610c943908494e6c16a77a5d90 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 10:00:57 +0100 Subject: [PATCH 61/69] reduce scope, fix it test --- dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 39476ceef..c5119b15c 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -214,6 +214,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val query = DynamoDBQuery .queryAll[ExpressionAttrNames](tableName) + .whereKey(ExpressionAttrNames.id.partitionKey === "id") .filter(ExpressionAttrNames.ttl.notExists) query.execute.flatMap(_.runDrain).exit.map { result => assert(result)(succeeds(isUnit)) @@ -1257,9 +1258,10 @@ object LiveSpec extends ZIOSpecDefault { }, test("delete item handles keyword") { withDefaultTable { tableName => - val d = delete[ExpressionAttrNames]( + val d = delete( tableName = tableName, - key = pk(avi3Item) + compositeKeyExpr = + ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 ).where(ExpressionAttrNames.ttl.notExists) d.transaction.execute.exit.map { result => assert(result.isSuccess)(isTrue) @@ -1280,9 +1282,9 @@ object LiveSpec extends ZIOSpecDefault { }, test("transact update item should handle keyword") { withDefaultTable { tableName => - val u = update[ExpressionAttrNames]( + val u = update( tableName = tableName, - key = pk(avi3Item) + compositeKeyExpr = ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 )(ExpressionAttrNames.ttl.set(None)).where(ExpressionAttrNames.ttl.notExists) u.transaction.execute.exit.map { result => assert(result.isSuccess)(isTrue) From fba3c02b02a036d5fcd139a309086722b067b20f Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 23 Jul 2023 10:01:21 +0100 Subject: [PATCH 62/69] scalafmt --- dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala | 3 ++- dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index c5119b15c..3e2e37c48 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -1284,7 +1284,8 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val u = update( tableName = tableName, - compositeKeyExpr = ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 + compositeKeyExpr = + ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 )(ExpressionAttrNames.ttl.set(None)).where(ExpressionAttrNames.ttl.notExists) u.transaction.execute.exit.map { result => assert(result.isSuccess)(isTrue) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 9328ab190..9d48282f0 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -505,7 +505,9 @@ object DynamoDBQuery { UpdateExpression(action) ) - private[dynamodb] def update[A: Schema](tableName: String, key: PrimaryKey)(action: Action[A]): DynamoDBQuery[A, Option[A]] = + private[dynamodb] def update[A: Schema](tableName: String, key: PrimaryKey)( + action: Action[A] + ): DynamoDBQuery[A, Option[A]] = updateItem(tableName, key)(action).map(_.flatMap(item => fromItem(item).toOption)) def update[From: Schema, To](tableName: String, partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To])( From e4646b3a0c57641a6d9c7aca6d362a07fa4b9bdb Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 29 Jul 2023 07:44:46 +0100 Subject: [PATCH 63/69] port over key condition render fix from main --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 74 +++++++++++++++ .../scala/zio/dynamodb/KeyConditionExpr.scala | 72 ++++++++------- .../zio/dynamodb/AliasMapRenderSpec.scala | 90 ++++++++++++------- 3 files changed, 171 insertions(+), 65 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 3e2e37c48..d1a607f48 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -114,6 +114,12 @@ object LiveSpec extends ZIOSpecDefault { AttributeDefinition.attrDefnString(name) ) + private def sortKeyStringTableWithKeywords(tableName: String) = + createTable(tableName, KeySchema("and", "source"), BillingMode.PayPerRequest)( + AttributeDefinition.attrDefnString("and"), + AttributeDefinition.attrDefnString("source") + ) + private def managedTable(tableDefinition: String => CreateTable) = ZIO .acquireRelease( @@ -123,6 +129,17 @@ object LiveSpec extends ZIOSpecDefault { } yield TableName(tableName) )(tName => deleteTable(tName.value).execute.orDie) + private def withPkKeywordsTable( + f: String => ZIO[DynamoDBExecutor, Throwable, TestResult] + ) = + ZIO.scoped { + managedTable(sortKeyStringTableWithKeywords).flatMap { table => + for { + result <- f(table.value) + } yield result + } + } + // TODO: Avi - fix problem with inference of this function when splitting suites // "a type was inferred to be `Any`; this may indicate a programming error." private def withTemporaryTable[R]( @@ -177,6 +194,13 @@ object LiveSpec extends ZIOSpecDefault { val (id, num, ttl) = ProjectionExpression.accessors[ExpressionAttrNames] } + final case class ExpressionAttrNamesPkKeywords(and: String, source: String, ttl: Option[Long]) + object ExpressionAttrNamesPkKeywords { + implicit val schema: Schema.CaseClass3[String, String, Option[Long], ExpressionAttrNamesPkKeywords] = + DeriveSchema.gen[ExpressionAttrNamesPkKeywords] + val (and, source, ttl) = ProjectionExpression.accessors[ExpressionAttrNamesPkKeywords] + } + val debugSuite = suite("debug")( test("delete should handle keyword") { withDefaultTable { tableName => @@ -198,6 +222,56 @@ object LiveSpec extends ZIOSpecDefault { val mainSuite: Spec[TestEnvironment, Any] = suite("live test")( + suite("key words in Key Condition Expressions")( + test("queryAll should handle keywords in primary key names using high level API") { + withPkKeywordsTable { tableName => + val query = DynamoDBQuery + .queryAll[ExpressionAttrNamesPkKeywords](tableName) + .whereKey( + ExpressionAttrNamesPkKeywords.and.partitionKey === "and1" && ExpressionAttrNamesPkKeywords.source.sortKey === "source1" + ) + .filter(ExpressionAttrNamesPkKeywords.ttl.notExists) + query.execute.flatMap(_.runDrain).exit.map { result => + assert(result)(succeeds(isUnit)) + } + } + }, + test("queryAll should handle keywords in primary key name using low level API") { + withPkKeywordsTable { tableName => + val query = DynamoDBQuery + .queryAll[ExpressionAttrNamesPkKeywords](tableName) + .whereKey($("and").partitionKey === "and1" && $("source").sortKey === "source1") + .filter(ExpressionAttrNamesPkKeywords.ttl.notExists) + query.execute.flatMap(_.runDrain).exit.map { result => + assert(result)(succeeds(isUnit)) + } + } + }, + test("querySome should handle keywords in primary key name using high level API") { + withPkKeywordsTable { tableName => + val query = DynamoDBQuery + .querySome[ExpressionAttrNamesPkKeywords](tableName, 1) + .whereKey( + ExpressionAttrNamesPkKeywords.and.partitionKey === "and1" && ExpressionAttrNamesPkKeywords.source.sortKey === "source1" + ) + .filter(ExpressionAttrNamesPkKeywords.ttl.notExists) + for { + result <- query.execute + } yield assert(result._1)(hasSize(equalTo(0))) + } + }, + test("querySome should handle keywords in primary key name using low level API") { + withPkKeywordsTable { tableName => + val query = DynamoDBQuery + .querySome[ExpressionAttrNames](tableName, 1) + .whereKey($("and").partitionKey === "and1" && $("source").sortKey === "source1") + .filter(ExpressionAttrNames.ttl.notExists) + for { + result <- query.execute + } yield assert(result._1)(hasSize(equalTo(0))) + } + } + ), suite("keywords in expression attribute names")( suite("using high level api")( test("scanAll should handle keyword") { diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index d3cbae9f8..9e30f71ff 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -14,6 +14,9 @@ sealed trait KeyConditionExpr[-From, +To] extends Renderable { self => } object KeyConditionExpr { + def getOrInsert[From, To](primaryKeyName: String): AliasMapRender[String] = + // note primary keys must be scalar values, they can't be nested + AliasMapRender.getOrInsert(ProjectionExpression.MapElement[From, To](ProjectionExpression.Root, primaryKeyName)) private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey[From, To], value: AttributeValue) extends KeyConditionExpr[From, To] { self => @@ -26,15 +29,20 @@ object KeyConditionExpr { def asAttrMap: AttrMap = AttrMap(pk.keyName -> value) override def render: AliasMapRender[String] = - AliasMapRender.getOrInsert(value).map(v => s"${pk.keyName} = $v") + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(pk.keyName) + } yield s"${keyAlias} = $v" + } private[dynamodb] final case class SortKeyEquals[-From, +To](sortKey: SortKey[From, To], value: AttributeValue) { self => def miniRender: AliasMapRender[String] = - AliasMapRender - .getOrInsert(value) - .map(v => s"${sortKey.keyName} = $v") + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(sortKey.keyName) + } yield s"${keyAlias} = $v" } private[dynamodb] final case class CompositePrimaryKeyExpr[-From]( @@ -70,39 +78,41 @@ object KeyConditionExpr { def miniRender: AliasMapRender[String] = self match { case ExtendedSortKeyExpr.GreaterThan(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} > $v") + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(sk.keyName) + } yield s"${keyAlias} > $v" case ExtendedSortKeyExpr.LessThan(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} < $v") + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(sk.keyName) + } yield s"${keyAlias} < $v" case ExtendedSortKeyExpr.NotEqual(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} <> $v") + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(sk.keyName) + } yield s"${keyAlias} <> $v" case ExtendedSortKeyExpr.LessThanOrEqual(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} <= $v") + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(sk.keyName) + } yield s"${keyAlias} <= $v" case ExtendedSortKeyExpr.GreaterThanOrEqual(sk, value) => - AliasMapRender - .getOrInsert(value) - .map(v => s"${sk.keyName} >= $v") + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(sk.keyName) + } yield s"${keyAlias} >= $v" case ExtendedSortKeyExpr.Between(left, min, max) => - AliasMapRender - .getOrInsert(min) - .flatMap(min => - AliasMapRender.getOrInsert(max).map { max => - s"${left.keyName} BETWEEN $min AND $max" - } - ) + for { + min2 <- AliasMapRender.getOrInsert(min) + max2 <- AliasMapRender.getOrInsert(max) + keyAlias <- KeyConditionExpr.getOrInsert(left.keyName) + } yield s"${keyAlias} BETWEEN $min2 AND $max2" case ExtendedSortKeyExpr.BeginsWith(left, value) => - AliasMapRender - .getOrInsert(value) - .map { v => - s"begins_with(${left.keyName}, $v)" - } + for { + v <- AliasMapRender.getOrInsert(value) + keyAlias <- KeyConditionExpr.getOrInsert(left.keyName) + } yield s"begins_with(${keyAlias}, $v)" } } diff --git a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala index 732bfff2f..f49a7d28e 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/AliasMapRenderSpec.scala @@ -323,98 +323,116 @@ object AliasMapRenderSpec extends ZIOSpecDefault { suite("Sort key expressions")( test("Equals") { val map = Map( - avKey(one) -> ":v0" + avKey(one) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr.SortKeyEquals($("num").sortKey, one).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("num = :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("#n1 = :v0")) }, test("LessThan") { val map = Map( - avKey(one) -> ":v0" + avKey(one) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr.ExtendedSortKeyExpr.LessThan($("num").sortKey, one).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("num < :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("#n1 < :v0")) }, test("NotEqual") { val map = Map( - avKey(one) -> ":v0" + avKey(one) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr.ExtendedSortKeyExpr.NotEqual($("num").sortKey, one).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("num <> :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("#n1 <> :v0")) }, test("GreaterThan") { val map = Map( - avKey(one) -> ":v0" + avKey(one) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr.ExtendedSortKeyExpr.GreaterThan($("num").sortKey, one).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("num > :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("#n1 > :v0")) }, test("LessThanOrEqual") { val map = Map( - avKey(one) -> ":v0" + avKey(one) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr.ExtendedSortKeyExpr.LessThanOrEqual($("num").sortKey, one).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("num <= :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("#n1 <= :v0")) }, test("GreaterThanOrEqual") { val map = Map( - avKey(one) -> ":v0" + avKey(one) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr.ExtendedSortKeyExpr.GreaterThanOrEqual($("num").sortKey, one).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("num >= :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("#n1 >= :v0")) }, test("Between") { val map = Map( - avKey(one) -> ":v0", - avKey(two) -> ":v1" + avKey(one) -> ":v0", + avKey(two) -> ":v1", + pathSegment(Root, "num") -> "#n2", + fullPath($("num")) -> "#n2" ) val (aliasMap, expression) = KeyConditionExpr.ExtendedSortKeyExpr.Between($("num").sortKey, one, two).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 2))) && - assert(expression)(equalTo("num BETWEEN :v0 AND :v1")) + assert(aliasMap)(equalTo(AliasMap(map, 3))) && + assert(expression)(equalTo("#n2 BETWEEN :v0 AND :v1")) }, test("BeginsWith") { val map = Map( - avKey(name) -> ":v0" + avKey(name) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr.ExtendedSortKeyExpr.BeginsWith($("num").sortKey, name).miniRender.execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("begins_with(num, :v0)")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("begins_with(#n1, :v0)")) } ), suite("PartitionKeyExpression")( test("Equals") { val map = Map( - avKey(one) -> ":v0" + avKey(one) -> ":v0", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1" ) val (aliasMap, expression) = KeyConditionExpr @@ -422,27 +440,31 @@ object AliasMapRenderSpec extends ZIOSpecDefault { .render .execute - assert(aliasMap)(equalTo(AliasMap(map, 1))) && - assert(expression)(equalTo("num = :v0")) + assert(aliasMap)(equalTo(AliasMap(map, 2))) && + assert(expression)(equalTo("#n1 = :v0")) } ), test("And") { val map = Map( - avKey(two) -> ":v0", - avKey(one) -> ":v1", - avKey(three) -> ":v2" + avKey(two) -> ":v0", + avKey(one) -> ":v2", + avKey(three) -> ":v3", + pathSegment(Root, "num") -> "#n1", + fullPath($("num")) -> "#n1", + pathSegment(Root, "num2") -> "#n4", + fullPath($("num2")) -> "#n4" ) val (aliasMap, expression) = KeyConditionExpr .ExtendedCompositePrimaryKeyExpr( KeyConditionExpr.PartitionKeyEquals($("num").partitionKey, two), - KeyConditionExpr.ExtendedSortKeyExpr.Between($("num").sortKey, one, three) + KeyConditionExpr.ExtendedSortKeyExpr.Between($("num2").sortKey, one, three) ) .render .execute - assert(aliasMap)(equalTo(AliasMap(map, 3))) && - assert(expression)(equalTo("num = :v0 AND num BETWEEN :v1 AND :v2")) + assert(aliasMap)(equalTo(AliasMap(map, 5))) && + assert(expression)(equalTo("#n1 = :v0 AND #n4 BETWEEN :v2 AND :v3")) } ), suite("AttributeValueType")( From 4e8cfbc74e614e6e5c0ce06afd5f14bf32d8f4b5 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 5 Aug 2023 09:43:17 +0100 Subject: [PATCH 64/69] Added extra type param and PrimaryKeyExpr trait --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 23 +------ .../scala/zio/dynamodb/DynamoDBQuery.scala | 64 +++++++++---------- .../scala/zio/dynamodb/KeyConditionExpr.scala | 36 +++++++---- .../src/main/scala/zio/dynamodb/package.scala | 34 ++-------- .../proofs/CanSortKeyBeginsWith.scala | 2 +- .../zio/dynamodb/proofs/IsPrimaryKey.scala | 4 ++ .../zio/dynamodb/ZStreamPipeliningSpec.scala | 11 ++-- .../examples/BatchFromStreamExamples.scala | 5 +- .../examples/KeyConditionExprExample.scala | 31 +++++---- .../StudentZioDynamoDbExample.scala | 2 +- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 2 +- 11 files changed, 94 insertions(+), 120 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index d1a607f48..5873cb796 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -201,25 +201,6 @@ object LiveSpec extends ZIOSpecDefault { val (and, source, ttl) = ProjectionExpression.accessors[ExpressionAttrNamesPkKeywords] } - val debugSuite = suite("debug")( - test("delete should handle keyword") { - withDefaultTable { tableName => - val query = DynamoDBQuery - .delete( - tableName, - ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 - ) - .where(ExpressionAttrNames.ttl.notExists) - query.execute.exit.map { result => - assert(result)(succeeds(isNone)) - } - } - } - ) - .provideSomeLayerShared[TestEnvironment]( - testLayer.orDie - ) @@ nondeterministic - val mainSuite: Spec[TestEnvironment, Any] = suite("live test")( suite("key words in Key Condition Expressions")( @@ -1334,7 +1315,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val d = delete( tableName = tableName, - compositeKeyExpr = + primaryKeyExpr = ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 ).where(ExpressionAttrNames.ttl.notExists) d.transaction.execute.exit.map { result => @@ -1358,7 +1339,7 @@ object LiveSpec extends ZIOSpecDefault { withDefaultTable { tableName => val u = update( tableName = tableName, - compositeKeyExpr = + primaryKeyExpr = ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 )(ExpressionAttrNames.ttl.set(None)).where(ExpressionAttrNames.ttl.notExists) u.transaction.execute.exit.map { result => diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 9d48282f0..c4a520855 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -327,7 +327,7 @@ sealed trait DynamoDBQuery[-In, +Out] { self => * val newQuery = query.whereKey($("email").partitionKey === "avi@gmail.com" && $("subject").sortKey === "maths") * }}} */ - def whereKey[From, To](keyConditionExpression: KeyConditionExpr[From, To]): DynamoDBQuery[In, Out] = + def whereKey[From, To1, To2](keyConditionExpression: KeyConditionExpr[From, To1, To2]): DynamoDBQuery[In, Out] = self match { case Zip(left, right, zippable) => Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) @@ -465,23 +465,23 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } - def get[From: Schema, To]( + def get[From: Schema, To1, To2]( tableName: String, - partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To], + partitionKeyExpr: PrimaryKeyExpr[From, To1, To2], projections: ProjectionExpression[_, _]* - )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { - val _ = ev + )(implicit ev: IsPrimaryKey[To1], ev2: IsPrimaryKey[To2]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { + val (_, _) = (ev, ev2) get(tableName, partitionKeyExpr.asAttrMap, projections: _*) } - def get[From: Schema, To]( - tableName: String, - compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From], - projections: ProjectionExpression[_, _]* - )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { - val _ = ev - get(tableName, compositeKeyExpr.asAttrMap, projections: _*) - } + // def get[From: Schema, To]( + // tableName: String, + // compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From], + // projections: ProjectionExpression[_, _]* + // )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { + // val _ = ev + // get(tableName, compositeKeyExpr.asAttrMap, projections: _*) + // } private[dynamodb] def fromItem[A: Schema](item: Item): Either[DynamoDBError, A] = { val av = ToAttributeValue.attrMapToAttributeValue.toAttributeValue(item) @@ -510,32 +510,32 @@ object DynamoDBQuery { ): DynamoDBQuery[A, Option[A]] = updateItem(tableName, key)(action).map(_.flatMap(item => fromItem(item).toOption)) - def update[From: Schema, To](tableName: String, partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To])( + def update[From: Schema, To1, To2](tableName: String, primaryKeyExpr: PrimaryKeyExpr[From, To1, To2])( action: Action[From] ): DynamoDBQuery[From, Option[From]] = - updateItem(tableName, partitionKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) + updateItem(tableName, primaryKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) - def update[From: Schema](tableName: String, compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From])( - action: Action[From] - ): DynamoDBQuery[From, Option[From]] = - updateItem(tableName, compositeKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) + // def update[From: Schema](tableName: String, compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From])( + // action: Action[From] + // ): DynamoDBQuery[From, Option[From]] = + // updateItem(tableName, compositeKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) def deleteItem(tableName: String, key: PrimaryKey): Write[Any, Option[Item]] = DeleteItem(TableName(tableName), key) - private[dynamodb] def delete[A: Schema](tableName: String, key: PrimaryKey): DynamoDBQuery[Any, Option[A]] = - deleteItem(tableName, key).map(_.flatMap(item => fromItem(item).toOption)) + // private[dynamodb] def delete[A: Schema](tableName: String, key: PrimaryKey): DynamoDBQuery[Any, Option[A]] = + // deleteItem(tableName, key).map(_.flatMap(item => fromItem(item).toOption)) - def delete[From: Schema, To]( - tableName: String, - partitionKeyExpr: KeyConditionExpr.PartitionKeyEquals[From, To] + def delete[From: Schema, To1, To2]( + tableName: String, // TODO: rename as deleteFrom(tableName)(etc) + primaryKeyExpr: PrimaryKeyExpr[From, To1, To2] ): DynamoDBQuery[Any, Option[From]] = - deleteItem(tableName, partitionKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) + deleteItem(tableName, primaryKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) - def delete[From: Schema]( - tableName: String, - compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From] - ): DynamoDBQuery[Any, Option[From]] = - deleteItem(tableName, compositeKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) + // def delete[From: Schema]( + // tableName: String, + // compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From] + // ): DynamoDBQuery[Any, Option[From]] = + // deleteItem(tableName, compositeKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) /** * when executed will return a Tuple of {{{(Chunk[Item], LastEvaluatedKey)}}} @@ -860,7 +860,7 @@ object DynamoDBQuery { exclusiveStartKey: LastEvaluatedKey = None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, - keyConditionExpr: Option[KeyConditionExpr[_, _]] = None, + keyConditionExpr: Option[KeyConditionExpr[_, _, _]] = None, projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes @@ -893,7 +893,7 @@ object DynamoDBQuery { exclusiveStartKey: LastEvaluatedKey = None, // allows client to control start position - eg for client managed paging filterExpression: Option[FilterExpression[_]] = None, - keyConditionExpr: Option[KeyConditionExpr[_, _]] = None, + keyConditionExpr: Option[KeyConditionExpr[_, _, _]] = None, projections: List[ProjectionExpression[_, _]] = List.empty, // if empty all attributes will be returned capacity: ReturnConsumedCapacity = ReturnConsumedCapacity.None, select: Option[Select] = None, // if ProjectExpression supplied then only valid value is SpecificAttributes diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index 9e30f71ff..945ecdd56 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -9,22 +9,30 @@ package zio.dynamodb * Note 1), 2) and 3) are all valid key condition expressions * BUT only 1) and 2) are valid primary key expressions that can be used in GetItem, UpdateItem and DeleteItem DynamoDB queries */ -sealed trait KeyConditionExpr[-From, +To] extends Renderable { self => +sealed trait KeyConditionExpr[-From, +To1, +To2] extends Renderable { self => def render: AliasMapRender[String] } +sealed trait PrimaryKeyExpr[-From, +To1, +To2] extends KeyConditionExpr[From, To1, To2] { + def asAttrMap: AttrMap +} + object KeyConditionExpr { + type SortKeyNotUsed + def getOrInsert[From, To](primaryKeyName: String): AliasMapRender[String] = // note primary keys must be scalar values, they can't be nested AliasMapRender.getOrInsert(ProjectionExpression.MapElement[From, To](ProjectionExpression.Root, primaryKeyName)) private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey[From, To], value: AttributeValue) - extends KeyConditionExpr[From, To] { self => + extends PrimaryKeyExpr[From, To, SortKeyNotUsed] { self => - def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1] = - CompositePrimaryKeyExpr[From1](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) - def &&[From1 <: From, To2](other: ExtendedSortKeyExpr[From1, To2]): ExtendedCompositePrimaryKeyExpr[From1, To2] = - ExtendedCompositePrimaryKeyExpr[From1, To2](self.asInstanceOf[PartitionKeyEquals[From1, To2]], other) + def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1, To, To2] = + CompositePrimaryKeyExpr[From1, To, To2](self.asInstanceOf[PartitionKeyEquals[From1, To]], other) + def &&[From1 <: From, To2]( + other: ExtendedSortKeyExpr[From1, To2] + ): ExtendedCompositePrimaryKeyExpr[From1, To, To2] = + ExtendedCompositePrimaryKeyExpr[From1, To, To2](self.asInstanceOf[PartitionKeyEquals[From1, To]], other) def asAttrMap: AttrMap = AttrMap(pk.keyName -> value) @@ -45,10 +53,10 @@ object KeyConditionExpr { } yield s"${keyAlias} = $v" } - private[dynamodb] final case class CompositePrimaryKeyExpr[-From]( - pk: PartitionKeyEquals[From, Any], - sk: SortKeyEquals[From, Any] - ) extends KeyConditionExpr[From, Any] { + private[dynamodb] final case class CompositePrimaryKeyExpr[-From, +To1, +To2]( + pk: PartitionKeyEquals[From, To1], + sk: SortKeyEquals[From, To2] + ) extends PrimaryKeyExpr[From, To1, To2] { self => def asAttrMap: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) @@ -60,10 +68,10 @@ object KeyConditionExpr { } yield s"$pkStr AND $skStr" } - private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, +To]( - pk: PartitionKeyEquals[From, To], - sk: ExtendedSortKeyExpr[From, To] - ) extends KeyConditionExpr[From, To] { + private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, +To1, +To2]( + pk: PartitionKeyEquals[From, To1], + sk: ExtendedSortKeyExpr[From, To2] + ) extends KeyConditionExpr[From, To1, To2] { self => def render: AliasMapRender[String] = diff --git a/dynamodb/src/main/scala/zio/dynamodb/package.scala b/dynamodb/src/main/scala/zio/dynamodb/package.scala index bf69d1a2b..f264aea5e 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/package.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/package.scala @@ -36,7 +36,9 @@ package object dynamodb { def batchWriteFromStream[R, A, In, B]( stream: ZStream[R, Throwable, A], mPar: Int = 10 - )(f: A => DynamoDBQuery[In, B]): ZStream[DynamoDBExecutor with R, Throwable, B] = + )( + f: A => DynamoDBQuery[In, B] + ): ZStream[DynamoDBExecutor with R, Throwable, B] = // TODO: Avi - can we constraint query to put or write? stream .aggregateAsync(ZSink.collectAllN[A](25)) .mapZIOPar(mPar) { chunk => @@ -101,38 +103,12 @@ package object dynamodb { * @tparam B implicit Schema[B] where B is the type of the element in the returned stream * @return stream of Either[DynamoDBError.DecodingError, (A, Option[B])] */ - def batchReadFromStream[R, A, B: Schema]( - tableName: String, - stream: ZStream[R, Throwable, A], - mPar: Int = 10 - )( - pk: A => PrimaryKey - ): ZStream[R with DynamoDBExecutor, Throwable, Either[DynamoDBError.DecodingError, (A, Option[B])]] = - stream - .aggregateAsync(ZSink.collectAllN[A](100)) - .mapZIOPar(mPar) { chunk => - val batchGetItem: DynamoDBQuery[B, Chunk[Either[DynamoDBError.DecodingError, (A, Option[B])]]] = DynamoDBQuery - .forEach(chunk) { a => - DynamoDBQuery.get(tableName, pk(a)).map { - case Right(b) => Right((a, Some(b))) - case Left(DynamoDBError.ValueNotFound(_)) => Right((a, None)) - case Left(e @ DynamoDBError.DecodingError(_)) => Left(e) - } - } - .map(Chunk.fromIterable) - for { - r <- ZIO.environment[DynamoDBExecutor] - list <- batchGetItem.execute.provideEnvironment(r) - } yield list - } - .flattenChunks - - def batchReadFromStream2[R, A, From: Schema, To: IsPrimaryKey]( + def batchReadFromStream2[R, A, From: Schema, To1: IsPrimaryKey, To2: IsPrimaryKey]( tableName: String, stream: ZStream[R, Throwable, A], mPar: Int = 10 )( - pk: A => KeyConditionExpr.PartitionKeyEquals[From, To] + pk: A => PrimaryKeyExpr[From, To1, To2] ): ZStream[R with DynamoDBExecutor, Throwable, Either[DynamoDBError.DecodingError, (A, Option[From])]] = stream .aggregateAsync(ZSink.collectAllN[A](100)) diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala index 8c818206c..26f95d12c 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/CanSortKeyBeginsWith.scala @@ -12,9 +12,9 @@ trait CanSortKeyBeginsWith0 extends CanSortKeyBeginsWith1 { new CanSortKeyBeginsWith[X, ProjectionExpression.Unknown] {} } trait CanSortKeyBeginsWith1 { + // begins_with with only applies to keys of type string or bytes implicit def bytes[A <: Iterable[Byte]]: CanSortKeyBeginsWith[A, A] = new CanSortKeyBeginsWith[A, A] {} - implicit def string: CanSortKeyBeginsWith[String, String] = new CanSortKeyBeginsWith[String, String] {} } object CanSortKeyBeginsWith extends CanSortKeyBeginsWith0 { diff --git a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala index a04d0cc7a..473c49260 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/proofs/IsPrimaryKey.scala @@ -1,6 +1,7 @@ package zio.dynamodb.proofs import scala.annotation.implicitNotFound +import zio.dynamodb.KeyConditionExpr @implicitNotFound("DynamoDB does not support primary key type ${A} - allowed types are: String, Number, Binary") sealed trait IsPrimaryKey[-A] @@ -16,4 +17,7 @@ object IsPrimaryKey { implicit val bigDecimalIsPrimaryKey: IsPrimaryKey[BigDecimal] = new IsPrimaryKey[BigDecimal] {} implicit val binaryIsPrimaryKey: IsPrimaryKey[Iterable[Byte]] = new IsPrimaryKey[Iterable[Byte]] {} + + implicit val sortKeyNotUsed: IsPrimaryKey[KeyConditionExpr.SortKeyNotUsed] = + new IsPrimaryKey[KeyConditionExpr.SortKeyNotUsed] {} } diff --git a/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala index f1ed1c08f..73aa308d6 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala @@ -11,7 +11,8 @@ object ZStreamPipeliningSpec extends ZIOSpecDefault { final case class Person(id: Int, name: String) object Person { - implicit val schema: Schema[Person] = DeriveSchema.gen[Person] + implicit val schema: Schema.CaseClass2[Int, String, Person] = DeriveSchema.gen[Person] + val (id, name) = ProjectionExpression.accessors[Person] } private val people = (1 to 200).map(i => Person(i, s"name$i")).toList @@ -25,8 +26,8 @@ object ZStreamPipeliningSpec extends ZIOSpecDefault { _ <- batchWriteFromStream(personStream) { person => put("person", person) }.runDrain - xs <- batchReadFromStream[Any, Person, Person]("person", personStream)(person => - PrimaryKey("id" -> person.id) + xs <- batchReadFromStream2("person", personStream)(person => + Person.id.partitionKey === person.id ).right.runCollect actualPeople = xs.toList.map { case (_, p) => p }.collect { case Some(b) => b } } yield assert(actualPeople)(equalTo(people)) @@ -41,8 +42,8 @@ object ZStreamPipeliningSpec extends ZIOSpecDefault { PrimaryKey("id" -> 1) -> Item("id" -> 1, "name" -> "Avi"), PrimaryKey("id" -> 2) -> Item("id" -> 2, "boom!" -> "de-serialisation-error-expected") ) - actualPeople <- batchReadFromStream[Any, Person, Person]("person", personStream.take(3))(person => - PrimaryKey("id" -> person.id) + actualPeople <- batchReadFromStream2("person", personStream.take(3))(person => + Person.id.partitionKey === person.id ).runCollect } yield assertTrue( actualPeople == Chunk( diff --git a/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala b/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala index 98fa8ac7e..453fd2de2 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala @@ -11,7 +11,8 @@ object BatchFromStreamExamples extends ZIOAppDefault { final case class Person(id: Int, name: String) object Person { - implicit val schema: Schema[Person] = DeriveSchema.gen[Person] + implicit val schema: Schema.CaseClass2[Int, String, Person] = DeriveSchema.gen[Person] + val (id, name) = ProjectionExpression.accessors[Person] } private val personIdStream: UStream[Int] = @@ -37,7 +38,7 @@ object BatchFromStreamExamples extends ZIOAppDefault { .runDrain // same again but use Schema derived codecs to convert an Item to a Person - _ <- batchReadFromStream[Any, Int, Person]("person", personIdStream)(id => PrimaryKey("id" -> id)) + _ <- batchReadFromStream2("person", personIdStream)(id => Person.id.partitionKey === id) .mapZIOPar(4)(person => printLine(s"person=$person")) .runDrain } yield ()).provide(DynamoDBExecutor.test) diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index a18f46b1a..206120e34 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -12,11 +12,12 @@ object KeyConditionExprExample extends App { import zio.dynamodb.KeyConditionExpr.SortKeyEquals import zio.dynamodb.ProjectionExpression.$ - val x6: CompositePrimaryKeyExpr[Any] = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey === "y" - val x7 = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey > 1 - val x8: ExtendedCompositePrimaryKeyExpr[Any, Int] = + val x6: CompositePrimaryKeyExpr[Any, ProjectionExpression.Unknown, String] = + $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey === "y" + val x7 = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey > 1 + val x8: ExtendedCompositePrimaryKeyExpr[Any, ProjectionExpression.Unknown, Int] = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey.between(1, 2) - val x9 = + val x9 = $("foo.bar").partitionKey === 1 && $("foo.baz").sortKey.beginsWith(1L) final case class Elephant(email: String, subject: String, age: Int) @@ -31,23 +32,25 @@ object KeyConditionExprExample extends App { val (email, subject, age, binary, binary2) = ProjectionExpression.accessors[Student] } - val pk: PartitionKeyEquals[Student, String] = Student.email.partitionKey === "x" + val pk: PartitionKeyEquals[Student, String] = Student.email.partitionKey === "x" // val pkX: PartitionKeyExpr[Student, String] = Student.age.primaryKey === "x" // as expected does not compile - val sk1: SortKeyEquals[Student, String] = Student.subject.sortKey === "y" - val sk2: ExtendedSortKeyExpr[Student, String] = Student.subject.sortKey > "y" - val pkAndSk: CompositePrimaryKeyExpr[Student] = Student.email.partitionKey === "x" && Student.subject.sortKey === "y" + val sk1: SortKeyEquals[Student, String] = Student.subject.sortKey === "y" + val sk2: ExtendedSortKeyExpr[Student, String] = Student.subject.sortKey > "y" + val pkAndSk: CompositePrimaryKeyExpr[Student, String, String] = + Student.email.partitionKey === "x" && Student.subject.sortKey === "y" + //val three = Student.email.primaryKey === "x" && Student.subject.sortKey === "y" && Student.subject.sortKey // 3 terms not allowed - val pkAndSkExtended1 = + val pkAndSkExtended1 = Student.email.partitionKey === "x" && Student.subject.sortKey > "y" - val pkAndSkExtended2 = + val pkAndSkExtended2 = Student.email.partitionKey === "x" && Student.subject.sortKey < "y" - val pkAndSkExtended3 = + val pkAndSkExtended3 = Student.email.partitionKey === "x" && Student.subject.sortKey.between("1", "2") - val pkAndSkExtended4 = + val pkAndSkExtended4 = Student.email.partitionKey === "x" && Student.subject.sortKey.beginsWith("1") - val pkAndSkExtended5 = + val pkAndSkExtended5 = Student.email.partitionKey === "x" && Student.binary.sortKey.beginsWith(List(1.toByte)) - val pkAndSkExtended6 = + val pkAndSkExtended6 = Student.email.partitionKey === "x" && Student.binary2.sortKey.beginsWith(List(1.toByte)) // TODO: Avi - fix ToAttrubuteValue interop with Array[Byte] diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala index 188508934..21e26593e 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala @@ -22,7 +22,7 @@ object StudentZioDynamoDbExample extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey(s.email, s.subject)) + _ <- batchReadFromStream2("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- DynamoDBQuery.deleteTable("student").execute diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index ec745ee2c..e72461376 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -30,7 +30,7 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) + _ <- batchReadFromStream2("student", ZStream(avi, adam))(student => primaryKey2(student.email, student.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- scanAll[Student]("student") From e5f942b7d76e99b58908e30e8d8a75f02c62aa96 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 5 Aug 2023 09:43:46 +0100 Subject: [PATCH 65/69] missed commit --- .../dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala index 2c9c0ad39..ea99da8bd 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala @@ -26,7 +26,7 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey(s.email, s.subject)) + _ <- batchReadFromStream2("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- scanAll[Student]("student").filter { From 6687a2854d5aad4383b903779f98752c07650f3e Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 5 Aug 2023 10:39:40 +0100 Subject: [PATCH 66/69] Improve type param names --- .../scala/zio/dynamodb/KeyConditionExpr.scala | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index 945ecdd56..8443d6187 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -9,11 +9,11 @@ package zio.dynamodb * Note 1), 2) and 3) are all valid key condition expressions * BUT only 1) and 2) are valid primary key expressions that can be used in GetItem, UpdateItem and DeleteItem DynamoDB queries */ -sealed trait KeyConditionExpr[-From, +To1, +To2] extends Renderable { self => +sealed trait KeyConditionExpr[-From, +Pk, +Sk] extends Renderable { self => def render: AliasMapRender[String] } -sealed trait PrimaryKeyExpr[-From, +To1, +To2] extends KeyConditionExpr[From, To1, To2] { +sealed trait PrimaryKeyExpr[-From, +Pk, +Sk] extends KeyConditionExpr[From, Pk, Sk] { def asAttrMap: AttrMap } @@ -24,15 +24,15 @@ object KeyConditionExpr { // note primary keys must be scalar values, they can't be nested AliasMapRender.getOrInsert(ProjectionExpression.MapElement[From, To](ProjectionExpression.Root, primaryKeyName)) - private[dynamodb] final case class PartitionKeyEquals[-From, +To](pk: PartitionKey[From, To], value: AttributeValue) - extends PrimaryKeyExpr[From, To, SortKeyNotUsed] { self => + private[dynamodb] final case class PartitionKeyEquals[-From, +Pk](pk: PartitionKey[From, Pk], value: AttributeValue) + extends PrimaryKeyExpr[From, Pk, SortKeyNotUsed] { self => - def &&[From1 <: From, To2](other: SortKeyEquals[From1, To2]): CompositePrimaryKeyExpr[From1, To, To2] = - CompositePrimaryKeyExpr[From1, To, To2](self.asInstanceOf[PartitionKeyEquals[From1, To]], other) - def &&[From1 <: From, To2]( - other: ExtendedSortKeyExpr[From1, To2] - ): ExtendedCompositePrimaryKeyExpr[From1, To, To2] = - ExtendedCompositePrimaryKeyExpr[From1, To, To2](self.asInstanceOf[PartitionKeyEquals[From1, To]], other) + def &&[From1 <: From, Sk](other: SortKeyEquals[From1, Sk]): CompositePrimaryKeyExpr[From1, Pk, Sk] = + CompositePrimaryKeyExpr[From1, Pk, Sk](self.asInstanceOf[PartitionKeyEquals[From1, Pk]], other) + def &&[From1 <: From, Sk]( + other: ExtendedSortKeyExpr[From1, Sk] + ): ExtendedCompositePrimaryKeyExpr[From1, Pk, Sk] = + ExtendedCompositePrimaryKeyExpr[From1, Pk, Sk](self.asInstanceOf[PartitionKeyEquals[From1, Pk]], other) def asAttrMap: AttrMap = AttrMap(pk.keyName -> value) @@ -44,7 +44,7 @@ object KeyConditionExpr { } - private[dynamodb] final case class SortKeyEquals[-From, +To](sortKey: SortKey[From, To], value: AttributeValue) { + private[dynamodb] final case class SortKeyEquals[-From, +Sk](sortKey: SortKey[From, Sk], value: AttributeValue) { self => def miniRender: AliasMapRender[String] = for { @@ -53,10 +53,10 @@ object KeyConditionExpr { } yield s"${keyAlias} = $v" } - private[dynamodb] final case class CompositePrimaryKeyExpr[-From, +To1, +To2]( - pk: PartitionKeyEquals[From, To1], - sk: SortKeyEquals[From, To2] - ) extends PrimaryKeyExpr[From, To1, To2] { + private[dynamodb] final case class CompositePrimaryKeyExpr[-From, +Pk, +Sk]( + pk: PartitionKeyEquals[From, Pk], + sk: SortKeyEquals[From, Sk] + ) extends PrimaryKeyExpr[From, Pk, Sk] { self => def asAttrMap: AttrMap = PrimaryKey(pk.pk.keyName -> pk.value, sk.sortKey.keyName -> sk.value) @@ -68,10 +68,10 @@ object KeyConditionExpr { } yield s"$pkStr AND $skStr" } - private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, +To1, +To2]( - pk: PartitionKeyEquals[From, To1], - sk: ExtendedSortKeyExpr[From, To2] - ) extends KeyConditionExpr[From, To1, To2] { + private[dynamodb] final case class ExtendedCompositePrimaryKeyExpr[-From, +Pk, +Sk]( + pk: PartitionKeyEquals[From, Pk], + sk: ExtendedSortKeyExpr[From, Sk] + ) extends KeyConditionExpr[From, Pk, Sk] { self => def render: AliasMapRender[String] = @@ -82,7 +82,7 @@ object KeyConditionExpr { } - sealed trait ExtendedSortKeyExpr[-From, +To] { self => + sealed trait ExtendedSortKeyExpr[-From, +Sk] { self => def miniRender: AliasMapRender[String] = self match { case ExtendedSortKeyExpr.GreaterThan(sk, value) => From b157f77107f2bcf9a2988eabf8be83294bcaff02 Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sat, 5 Aug 2023 10:56:30 +0100 Subject: [PATCH 67/69] cleanup --- .../scala/zio/dynamodb/DynamoDBQuery.scala | 35 ++++--------------- .../src/main/scala/zio/dynamodb/package.scala | 4 +-- .../zio/dynamodb/ZStreamPipeliningSpec.scala | 7 ++-- .../examples/BatchFromStreamExamples.scala | 2 +- .../StudentZioDynamoDbExample.scala | 2 +- .../StudentZioDynamoDbExampleWithOptics.scala | 2 +- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 2 +- 7 files changed, 15 insertions(+), 39 deletions(-) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index c4a520855..7e238d81a 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -465,24 +465,15 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } - def get[From: Schema, To1, To2]( + def get[From: Schema, Pk, Sk]( tableName: String, - partitionKeyExpr: PrimaryKeyExpr[From, To1, To2], + partitionKeyExpr: PrimaryKeyExpr[From, Pk, Sk], projections: ProjectionExpression[_, _]* - )(implicit ev: IsPrimaryKey[To1], ev2: IsPrimaryKey[To2]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { + )(implicit ev: IsPrimaryKey[Pk], ev2: IsPrimaryKey[Sk]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { val (_, _) = (ev, ev2) get(tableName, partitionKeyExpr.asAttrMap, projections: _*) } - // def get[From: Schema, To]( - // tableName: String, - // compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From], - // projections: ProjectionExpression[_, _]* - // )(implicit ev: IsPrimaryKey[To]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { - // val _ = ev - // get(tableName, compositeKeyExpr.asAttrMap, projections: _*) - // } - private[dynamodb] def fromItem[A: Schema](item: Item): Either[DynamoDBError, A] = { val av = ToAttributeValue.attrMapToAttributeValue.toAttributeValue(item) av.decode(Schema[A]) @@ -510,33 +501,19 @@ object DynamoDBQuery { ): DynamoDBQuery[A, Option[A]] = updateItem(tableName, key)(action).map(_.flatMap(item => fromItem(item).toOption)) - def update[From: Schema, To1, To2](tableName: String, primaryKeyExpr: PrimaryKeyExpr[From, To1, To2])( + def update[From: Schema, Pk, Sk](tableName: String, primaryKeyExpr: PrimaryKeyExpr[From, Pk, Sk])( action: Action[From] ): DynamoDBQuery[From, Option[From]] = updateItem(tableName, primaryKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) - // def update[From: Schema](tableName: String, compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From])( - // action: Action[From] - // ): DynamoDBQuery[From, Option[From]] = - // updateItem(tableName, compositeKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) - def deleteItem(tableName: String, key: PrimaryKey): Write[Any, Option[Item]] = DeleteItem(TableName(tableName), key) - // private[dynamodb] def delete[A: Schema](tableName: String, key: PrimaryKey): DynamoDBQuery[Any, Option[A]] = - // deleteItem(tableName, key).map(_.flatMap(item => fromItem(item).toOption)) - - def delete[From: Schema, To1, To2]( + def delete[From: Schema, Pk, Sk]( tableName: String, // TODO: rename as deleteFrom(tableName)(etc) - primaryKeyExpr: PrimaryKeyExpr[From, To1, To2] + primaryKeyExpr: PrimaryKeyExpr[From, Pk, Sk] ): DynamoDBQuery[Any, Option[From]] = deleteItem(tableName, primaryKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) - // def delete[From: Schema]( - // tableName: String, - // compositeKeyExpr: KeyConditionExpr.CompositePrimaryKeyExpr[From] - // ): DynamoDBQuery[Any, Option[From]] = - // deleteItem(tableName, compositeKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) - /** * when executed will return a Tuple of {{{(Chunk[Item], LastEvaluatedKey)}}} */ diff --git a/dynamodb/src/main/scala/zio/dynamodb/package.scala b/dynamodb/src/main/scala/zio/dynamodb/package.scala index f264aea5e..565c5245b 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/package.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/package.scala @@ -103,12 +103,12 @@ package object dynamodb { * @tparam B implicit Schema[B] where B is the type of the element in the returned stream * @return stream of Either[DynamoDBError.DecodingError, (A, Option[B])] */ - def batchReadFromStream2[R, A, From: Schema, To1: IsPrimaryKey, To2: IsPrimaryKey]( + def batchReadFromStream[R, A, From: Schema, Pk: IsPrimaryKey, Sk: IsPrimaryKey]( tableName: String, stream: ZStream[R, Throwable, A], mPar: Int = 10 )( - pk: A => PrimaryKeyExpr[From, To1, To2] + pk: A => PrimaryKeyExpr[From, Pk, Sk] ): ZStream[R with DynamoDBExecutor, Throwable, Either[DynamoDBError.DecodingError, (A, Option[From])]] = stream .aggregateAsync(ZSink.collectAllN[A](100)) diff --git a/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala index 73aa308d6..ffa1c2703 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/ZStreamPipeliningSpec.scala @@ -26,9 +26,8 @@ object ZStreamPipeliningSpec extends ZIOSpecDefault { _ <- batchWriteFromStream(personStream) { person => put("person", person) }.runDrain - xs <- batchReadFromStream2("person", personStream)(person => - Person.id.partitionKey === person.id - ).right.runCollect + xs <- + batchReadFromStream("person", personStream)(person => Person.id.partitionKey === person.id).right.runCollect actualPeople = xs.toList.map { case (_, p) => p }.collect { case Some(b) => b } } yield assert(actualPeople)(equalTo(people)) }, @@ -42,7 +41,7 @@ object ZStreamPipeliningSpec extends ZIOSpecDefault { PrimaryKey("id" -> 1) -> Item("id" -> 1, "name" -> "Avi"), PrimaryKey("id" -> 2) -> Item("id" -> 2, "boom!" -> "de-serialisation-error-expected") ) - actualPeople <- batchReadFromStream2("person", personStream.take(3))(person => + actualPeople <- batchReadFromStream("person", personStream.take(3))(person => Person.id.partitionKey === person.id ).runCollect } yield assertTrue( diff --git a/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala b/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala index 453fd2de2..be703b209 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/BatchFromStreamExamples.scala @@ -38,7 +38,7 @@ object BatchFromStreamExamples extends ZIOAppDefault { .runDrain // same again but use Schema derived codecs to convert an Item to a Person - _ <- batchReadFromStream2("person", personIdStream)(id => Person.id.partitionKey === id) + _ <- batchReadFromStream("person", personIdStream)(id => Person.id.partitionKey === id) .mapZIOPar(4)(person => printLine(s"person=$person")) .runDrain } yield ()).provide(DynamoDBExecutor.test) diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala index 21e26593e..e6206aeae 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala @@ -22,7 +22,7 @@ object StudentZioDynamoDbExample extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream2("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) + _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- DynamoDBQuery.deleteTable("student").execute diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala index ea99da8bd..b45d8de22 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala @@ -26,7 +26,7 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream2("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) + _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- scanAll[Student]("student").filter { diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index e72461376..d4cd7d1d7 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -30,7 +30,7 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream2("student", ZStream(avi, adam))(student => primaryKey2(student.email, student.subject)) + _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey2(student.email, student.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- scanAll[Student]("student") From ff8be2189b181de4b83fd53bbb5aa7789e4cca5e Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 6 Aug 2023 10:13:50 +0100 Subject: [PATCH 68/69] make get with AttrMap PK private, some cleanup --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 20 ++++++++----- .../scala/zio/dynamodb/DynamoDBQuery.scala | 27 ++++++++--------- .../scala/zio/dynamodb/KeyConditionExpr.scala | 12 ++++---- .../src/main/scala/zio/dynamodb/package.scala | 2 +- .../scala/zio/dynamodb/GetAndPutSpec.scala | 30 ++++++++++++------- .../scala/zio/dynamodb/codec/models.scala | 5 ++++ .../examples/KeyConditionExprExample.scala | 1 - .../StudentZioDynamoDbExample.scala | 2 +- .../StudentZioDynamoDbExampleWithOptics.scala | 6 ++-- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 12 ++++---- .../zio/dynamodb/examples/model/Student.scala | 7 ++--- 11 files changed, 70 insertions(+), 54 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index 5873cb796..c7368e367 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -61,7 +61,12 @@ object LiveSpec extends ZIOSpecDefault { private val stringSortKeyItem = Item(id -> adam, name -> adam) - private final case class Person(id: String, firstName: String, num: Int) + final case class Person(id: String, firstName: String, num: Int) + object Person { + implicit lazy val schema: Schema.CaseClass3[String, String, Int, Person] = DeriveSchema.gen[Person] + + val (id, firstName, num) = ProjectionExpression.accessors[Person] + } private implicit lazy val person: Schema[Person] = DeriveSchema.gen[Person] private val aviPerson = Person(first, avi, 1) @@ -555,7 +560,7 @@ object LiveSpec extends ZIOSpecDefault { }, test("get into case class") { withDefaultTable { tableName => - get[Person](tableName, secondPrimaryKey).execute.map(person => + get(tableName, Person.id.partitionKey === second && Person.num.sortKey === 2).execute.map(person => assert(person)(equalTo(Right(Person("second", "adam", 2)))) ) } @@ -1349,14 +1354,15 @@ object LiveSpec extends ZIOSpecDefault { }, test("update item") { withDefaultTable { tableName => + val key = Person.id.partitionKey === first && Person.num.sortKey === 7 val updateItem = UpdateItem( - key = pk(avi3Item), + key = key.asAttrMap, tableName = TableName(tableName), updateExpression = UpdateExpression($(name).set(notAdam)) ) for { _ <- updateItem.transaction.execute - written <- get[Person](tableName, pk(avi3Item)).execute + written <- get(tableName, key).execute } yield assert(written)(isRight(equalTo(Person(first, notAdam, 7)))) } }, @@ -1383,9 +1389,9 @@ object LiveSpec extends ZIOSpecDefault { for { _ <- (putItem zip conditionCheck zip updateItem zip deleteItem).transaction.execute - put <- get[Person](tableName, Item(id -> first, number -> 10)).execute - deleted <- get[Person](tableName, Item(id -> first, number -> 4)).execute - updated <- get[Person](tableName, Item(id -> first, number -> 7)).execute + put <- get(tableName, Person.id.partitionKey === first && Person.num.sortKey === 10).execute + deleted <- get(tableName, Person.id.partitionKey === first && Person.num.sortKey === 4).execute + updated <- get(tableName, Person.id.partitionKey === first && Person.num.sortKey === 7).execute } yield assert(put)(isRight(equalTo(Person(first, avi3, 10)))) && assert(deleted)(isLeft) && assert(updated)(isRight(equalTo(Person(first, notAdam, 7)))) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index 7e238d81a..e86ccd068 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -327,7 +327,7 @@ sealed trait DynamoDBQuery[-In, +Out] { self => * val newQuery = query.whereKey($("email").partitionKey === "avi@gmail.com" && $("subject").sortKey === "maths") * }}} */ - def whereKey[From, To1, To2](keyConditionExpression: KeyConditionExpr[From, To1, To2]): DynamoDBQuery[In, Out] = + def whereKey[From, Pk, Sk](keyConditionExpression: KeyConditionExpr[From, Pk, Sk]): DynamoDBQuery[In, Out] = self match { case Zip(left, right, zippable) => Zip(left.whereKey(keyConditionExpression), right.whereKey(keyConditionExpression), zippable) @@ -453,8 +453,16 @@ object DynamoDBQuery { ): DynamoDBQuery[Any, Option[Item]] = GetItem(TableName(tableName), key, projections.toList) - // TODO: Avi - make private? - def get[A: Schema]( + def get[From: Schema, Pk, Sk]( + tableName: String, + partitionKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk], + projections: ProjectionExpression[_, _]* + )(implicit ev: IsPrimaryKey[Pk], ev2: IsPrimaryKey[Sk]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { + val (_, _) = (ev, ev2) + get(tableName, partitionKeyExpr.asAttrMap, projections: _*) + } + + private def get[A: Schema]( tableName: String, key: PrimaryKey, projections: ProjectionExpression[_, _]* @@ -465,15 +473,6 @@ object DynamoDBQuery { case None => Left(ValueNotFound(s"value with key $key not found")) } - def get[From: Schema, Pk, Sk]( - tableName: String, - partitionKeyExpr: PrimaryKeyExpr[From, Pk, Sk], - projections: ProjectionExpression[_, _]* - )(implicit ev: IsPrimaryKey[Pk], ev2: IsPrimaryKey[Sk]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { - val (_, _) = (ev, ev2) - get(tableName, partitionKeyExpr.asAttrMap, projections: _*) - } - private[dynamodb] def fromItem[A: Schema](item: Item): Either[DynamoDBError, A] = { val av = ToAttributeValue.attrMapToAttributeValue.toAttributeValue(item) av.decode(Schema[A]) @@ -501,7 +500,7 @@ object DynamoDBQuery { ): DynamoDBQuery[A, Option[A]] = updateItem(tableName, key)(action).map(_.flatMap(item => fromItem(item).toOption)) - def update[From: Schema, Pk, Sk](tableName: String, primaryKeyExpr: PrimaryKeyExpr[From, Pk, Sk])( + def update[From: Schema, Pk, Sk](tableName: String, primaryKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk])( action: Action[From] ): DynamoDBQuery[From, Option[From]] = updateItem(tableName, primaryKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) @@ -510,7 +509,7 @@ object DynamoDBQuery { def delete[From: Schema, Pk, Sk]( tableName: String, // TODO: rename as deleteFrom(tableName)(etc) - primaryKeyExpr: PrimaryKeyExpr[From, Pk, Sk] + primaryKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk] ): DynamoDBQuery[Any, Option[From]] = deleteItem(tableName, primaryKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) diff --git a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala index 8443d6187..5f5a34a93 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/KeyConditionExpr.scala @@ -1,25 +1,25 @@ package zio.dynamodb /** - * Models: + * This sum type models: * 1) partition key equality expressions * 2) composite primary key expressions where sort key expression is equality * 3) extended composite primary key expressions where sort key is not equality eg >, <, >=, <=, between, begins_with * - * Note 1), 2) and 3) are all valid key condition expressions + * Note 1), 2) and 3) are all valid key condition expressions used in Query DynamoDB queries * BUT only 1) and 2) are valid primary key expressions that can be used in GetItem, UpdateItem and DeleteItem DynamoDB queries */ sealed trait KeyConditionExpr[-From, +Pk, +Sk] extends Renderable { self => def render: AliasMapRender[String] } -sealed trait PrimaryKeyExpr[-From, +Pk, +Sk] extends KeyConditionExpr[From, Pk, Sk] { - def asAttrMap: AttrMap -} - object KeyConditionExpr { type SortKeyNotUsed + sealed trait PrimaryKeyExpr[-From, +Pk, +Sk] extends KeyConditionExpr[From, Pk, Sk] { + def asAttrMap: AttrMap + } + def getOrInsert[From, To](primaryKeyName: String): AliasMapRender[String] = // note primary keys must be scalar values, they can't be nested AliasMapRender.getOrInsert(ProjectionExpression.MapElement[From, To](ProjectionExpression.Root, primaryKeyName)) diff --git a/dynamodb/src/main/scala/zio/dynamodb/package.scala b/dynamodb/src/main/scala/zio/dynamodb/package.scala index 565c5245b..0b4721727 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/package.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/package.scala @@ -108,7 +108,7 @@ package object dynamodb { stream: ZStream[R, Throwable, A], mPar: Int = 10 )( - pk: A => PrimaryKeyExpr[From, Pk, Sk] + pk: A => KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk] ): ZStream[R with DynamoDBExecutor, Throwable, Either[DynamoDBError.DecodingError, (A, Option[From])]] = stream .aggregateAsync(ZSink.collectAllN[A](100)) diff --git a/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala index 2ae50087c..26cbade01 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala @@ -9,9 +9,14 @@ import zio.test.{ assertTrue, ZIOSpecDefault } object GetAndPutSpec extends ZIOSpecDefault { final case class SimpleCaseClass2(id: Int, name: String) + object SimpleCaseClass2 { + implicit val schema: Schema.CaseClass2[Int, String, SimpleCaseClass2] = DeriveSchema.gen[SimpleCaseClass2] + val (id, name) = ProjectionExpression.accessors[SimpleCaseClass2] + } + final case class SimpleCaseClass2OptionalField(id: Int, maybeName: Option[String]) - implicit lazy val simpleCaseClass2: Schema[SimpleCaseClass2] = DeriveSchema.gen[SimpleCaseClass2] - implicit lazy val simpleCaseClass2OptionalField: Schema[SimpleCaseClass2OptionalField] = + implicit val simpleCaseClass2: Schema[SimpleCaseClass2] = DeriveSchema.gen[SimpleCaseClass2] + implicit val simpleCaseClass2OptionalField: Schema[SimpleCaseClass2OptionalField] = DeriveSchema.gen[SimpleCaseClass2OptionalField] private val primaryKey1 = PrimaryKey("id" -> 1) @@ -24,18 +29,18 @@ object GetAndPutSpec extends ZIOSpecDefault { test("that exists") { for { _ <- TestDynamoDBExecutor.addItems("table1", primaryKey1 -> Item("id" -> 1, "name" -> "Avi")) - found <- get[SimpleCaseClass2]("table1", primaryKey1).execute + found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Right(SimpleCaseClass2(1, "Avi"))) }, test("that does not exists") { for { - found <- get[SimpleCaseClass2]("table1", primaryKey1).execute + found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Left(ValueNotFound("value with key AttrMap(Map(id -> Number(1))) not found"))) }, test("with missing attributes results in an error") { for { _ <- TestDynamoDBExecutor.addItems("table1", primaryKey1 -> Item("id" -> 1)) - found <- get[SimpleCaseClass2]("table1", primaryKey1).execute + found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Left(DecodingError("field 'name' not found in Map(Map(String(id) -> Number(1)))"))) }, test("batched") { @@ -45,7 +50,10 @@ object GetAndPutSpec extends ZIOSpecDefault { primaryKey1 -> Item("id" -> 1, "name" -> "Avi"), primaryKey2 -> Item("id" -> 2, "name" -> "Tarlochan") ) - r <- (get[SimpleCaseClass2]("table1", primaryKey1) zip get[SimpleCaseClass2]("table1", primaryKey2)).execute + r <- (get("table1", SimpleCaseClass2.id.partitionKey === 1) zip get( + "table1", + SimpleCaseClass2.id.partitionKey === 2 + )).execute } yield assertTrue(r._1 == Right(SimpleCaseClass2(1, "Avi"))) && assertTrue( r._2 == Right(SimpleCaseClass2(2, "Tarlochan")) ) @@ -56,15 +64,15 @@ object GetAndPutSpec extends ZIOSpecDefault { test("""SimpleCaseClass2(1, "Avi")""") { for { _ <- put[SimpleCaseClass2]("table1", SimpleCaseClass2(1, "Avi")).execute - found <- get[SimpleCaseClass2]("table1", primaryKey1).execute + found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Right(SimpleCaseClass2(1, "Avi"))) }, test("""top level enum PreBilled(1, "foobar")""") { for { _ <- TestDynamoDBExecutor.addTable("table1", "id") _ <- put[Invoice]("table1", PreBilled(1, "foobar")).execute - found <- get[Invoice]("table1", primaryKey1).execute - } yield assertTrue(found == Right(PreBilled(1, "foobar"))) + found <- get("table1", PreBilled.id.partitionKey === 1).execute.absolve + } yield assertTrue(found == PreBilled(1, "foobar")) }, test("""batched SimpleCaseClass2(1, "Avi") and SimpleCaseClass2(2, "Tarlochan")""") { for { @@ -72,8 +80,8 @@ object GetAndPutSpec extends ZIOSpecDefault { "table1", SimpleCaseClass2(2, "Tarlochan") )).execute - found1 <- get[SimpleCaseClass2]("table1", primaryKey1).execute - found2 <- get[SimpleCaseClass2]("table1", primaryKey2).execute + found1 <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute + found2 <- get("table1", SimpleCaseClass2.id.partitionKey === 2).execute } yield assertTrue(found1 == Right(SimpleCaseClass2(1, "Avi"))) && assertTrue( found2 == Right(SimpleCaseClass2(2, "Tarlochan")) ) diff --git a/dynamodb/src/test/scala/zio/dynamodb/codec/models.scala b/dynamodb/src/test/scala/zio/dynamodb/codec/models.scala index 585a72ce7..b7f0f8c75 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/codec/models.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/codec/models.scala @@ -5,6 +5,7 @@ import zio.schema.annotation.{ caseName, discriminatorName, fieldName } import zio.schema.{ DeriveSchema, Schema } import java.time.Instant +import zio.dynamodb.ProjectionExpression // ADT example sealed trait Status @@ -95,5 +96,9 @@ sealed trait Invoice { object Invoice { final case class Billed(id: Int, i: Int) extends Invoice final case class PreBilled(id: Int, s: String) extends Invoice + object PreBilled { + implicit val schema: Schema.CaseClass2[Int, String, PreBilled] = DeriveSchema.gen[PreBilled] + val (id, s) = ProjectionExpression.accessors[PreBilled] + } implicit val schema: Schema[Invoice] = DeriveSchema.gen[Invoice] } diff --git a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala index 206120e34..e207d487a 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/KeyConditionExprExample.scala @@ -52,7 +52,6 @@ object KeyConditionExprExample extends App { Student.email.partitionKey === "x" && Student.binary.sortKey.beginsWith(List(1.toByte)) val pkAndSkExtended6 = Student.email.partitionKey === "x" && Student.binary2.sortKey.beginsWith(List(1.toByte)) - // TODO: Avi - fix ToAttrubuteValue interop with Array[Byte] val (aliasMap, s) = pkAndSkExtended1.render.execute println(s"aliasMap=$aliasMap, s=$s") diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala index e6206aeae..188508934 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExample.scala @@ -22,7 +22,7 @@ object StudentZioDynamoDbExample extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) + _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey(s.email, s.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- DynamoDBQuery.deleteTable("student").execute diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala index b45d8de22..b5832ec6c 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala @@ -26,7 +26,7 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey2(s.email, s.subject)) + _ <- batchReadFromStream("student", ZStream(avi, adam))(s => primaryKey(s.email, s.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- scanAll[Student]("student").filter { @@ -49,13 +49,13 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { ) && email === "avi@gmail.com" && payment === Payment.CreditCard ) .execute - _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey("avi@gmail.com", "maths")) { enrollmentDate.set(Some(enrolDate2)) + payment.set(Payment.PayPal) + address .set( Some(Address("line1", "postcode1")) ) }.execute - _ <- delete("student", primaryKey2("adam@gmail.com", "english")) + _ <- delete("student", primaryKey("adam@gmail.com", "english")) .where( enrollmentDate === Some( enrolDate diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index d4cd7d1d7..3db8c0082 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -30,7 +30,7 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { put("student", student) }.runDrain _ <- put("student", avi.copy(payment = Payment.CreditCard)).execute - _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey2(student.email, student.subject)) + _ <- batchReadFromStream("student", ZStream(avi, adam))(student => primaryKey(student.email, student.subject)) .tap(errorOrStudent => Console.printLine(s"student=$errorOrStudent")) .runDrain _ <- scanAll[Student]("student") @@ -58,27 +58,27 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { ) === "CreditCard" /* && elephantCe */ ) .execute - _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey("avi@gmail.com", "maths")) { altPayment.set(Payment.PayPal) + addresses.prependList(List(Address("line0", "postcode0"))) + studentNumber .add(1000) + groups.addSet(Set("group3")) // + elephantAction }.execute - _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey("avi@gmail.com", "maths")) { altPayment.set(Payment.PayPal) + addresses.appendList(List(Address("line3", "postcode3"))) + groups .deleteFromSet(Set("group1")) }.execute - _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey("avi@gmail.com", "maths")) { enrollmentDate.setIfNotExists(Some(enrolDate2)) + payment.set(altPayment) + address .set( Some(Address("line1", "postcode1")) ) // + elephantAction }.execute - _ <- update("student", primaryKey2("avi@gmail.com", "maths")) { + _ <- update("student", primaryKey("avi@gmail.com", "maths")) { addresses.remove(1) }.execute _ <- - delete("student", primaryKey2("adam@gmail.com", "english")) + delete("student", primaryKey("adam@gmail.com", "english")) .where( (enrollmentDate === Some(enrolDate) && payment <> Payment.PayPal && studentNumber .between(1, 3) && groups.contains("group1") && collegeName.contains( diff --git a/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala b/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala index 43c512eb2..99cc7c8fe 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/model/Student.scala @@ -1,11 +1,12 @@ package zio.dynamodb.examples.model import zio.dynamodb.Annotations.enumOfCaseObjects -import zio.dynamodb.{ PrimaryKey, ProjectionExpression } +import zio.dynamodb.ProjectionExpression import zio.schema.DeriveSchema import java.time.Instant import zio.schema.Schema +import zio.dynamodb.KeyConditionExpr @enumOfCaseObjects sealed trait Payment @@ -76,9 +77,7 @@ object Student { ) = ProjectionExpression.accessors[Student] - def primaryKey(email: String, subject: String): PrimaryKey = PrimaryKey("email" -> email, "subject" -> subject) - - def primaryKey2(email: String, subject: String) = + def primaryKey(email: String, subject: String): KeyConditionExpr.PrimaryKeyExpr[Student, String, String] = Student.email.partitionKey === email && Student.subject.sortKey === subject val enrolDate = Instant.parse("2021-03-20T01:39:33Z") From 41a4eec264530a3e280f62ceb317f9c7389e675a Mon Sep 17 00:00:00 2001 From: Avinder Bahra Date: Sun, 6 Aug 2023 10:46:57 +0100 Subject: [PATCH 69/69] curry PK parameter in hi level API for get, update, delete queries --- .../src/it/scala/zio/dynamodb/LiveSpec.scala | 22 ++++++++----------- .../scala/zio/dynamodb/DynamoDBQuery.scala | 8 ++++--- .../src/main/scala/zio/dynamodb/package.scala | 2 +- .../scala/zio/dynamodb/GetAndPutSpec.scala | 17 +++++++------- .../scala/zio/dynamodb/examples/Main.scala | 2 +- .../examples/SimpleDecodedExample.scala | 2 +- ...ypeSafeRoundTripSerialisationExample.scala | 2 +- .../StudentZioDynamoDbExampleWithOptics.scala | 4 ++-- ...StudentZioDynamoDbTypeSafeAPIExample.scala | 10 ++++----- 9 files changed, 33 insertions(+), 36 deletions(-) diff --git a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala index c7368e367..4c710629a 100644 --- a/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala +++ b/dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala @@ -313,8 +313,7 @@ object LiveSpec extends ZIOSpecDefault { test("delete should handle keyword") { withDefaultTable { tableName => val query = DynamoDBQuery - .delete( - tableName, + .delete(tableName)( ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 ) .where(ExpressionAttrNames.ttl.notExists) @@ -336,8 +335,7 @@ object LiveSpec extends ZIOSpecDefault { test("update should handle keyword") { withDefaultTable { tableName => val query = DynamoDBQuery - .update( - tableName, + .update(tableName)( ExpressionAttrNames.id.partitionKey === "id" && ExpressionAttrNames.num.sortKey === 1 )( ExpressionAttrNames.ttl.set(Some(42L)) @@ -560,7 +558,7 @@ object LiveSpec extends ZIOSpecDefault { }, test("get into case class") { withDefaultTable { tableName => - get(tableName, Person.id.partitionKey === second && Person.num.sortKey === 2).execute.map(person => + get(tableName)(Person.id.partitionKey === second && Person.num.sortKey === 2).execute.map(person => assert(person)(equalTo(Right(Person("second", "adam", 2)))) ) } @@ -1318,8 +1316,7 @@ object LiveSpec extends ZIOSpecDefault { }, test("delete item handles keyword") { withDefaultTable { tableName => - val d = delete( - tableName = tableName, + val d = delete(tableName = tableName)( primaryKeyExpr = ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 ).where(ExpressionAttrNames.ttl.notExists) @@ -1342,8 +1339,7 @@ object LiveSpec extends ZIOSpecDefault { }, test("transact update item should handle keyword") { withDefaultTable { tableName => - val u = update( - tableName = tableName, + val u = update(tableName = tableName)( primaryKeyExpr = ExpressionAttrNames.id.partitionKey === "first" && ExpressionAttrNames.num.sortKey === 7 )(ExpressionAttrNames.ttl.set(None)).where(ExpressionAttrNames.ttl.notExists) @@ -1362,7 +1358,7 @@ object LiveSpec extends ZIOSpecDefault { ) for { _ <- updateItem.transaction.execute - written <- get(tableName, key).execute + written <- get(tableName)(key).execute } yield assert(written)(isRight(equalTo(Person(first, notAdam, 7)))) } }, @@ -1389,9 +1385,9 @@ object LiveSpec extends ZIOSpecDefault { for { _ <- (putItem zip conditionCheck zip updateItem zip deleteItem).transaction.execute - put <- get(tableName, Person.id.partitionKey === first && Person.num.sortKey === 10).execute - deleted <- get(tableName, Person.id.partitionKey === first && Person.num.sortKey === 4).execute - updated <- get(tableName, Person.id.partitionKey === first && Person.num.sortKey === 7).execute + put <- get(tableName)(Person.id.partitionKey === first && Person.num.sortKey === 10).execute + deleted <- get(tableName)(Person.id.partitionKey === first && Person.num.sortKey === 4).execute + updated <- get(tableName)(Person.id.partitionKey === first && Person.num.sortKey === 7).execute } yield assert(put)(isRight(equalTo(Person(first, avi3, 10)))) && assert(deleted)(isLeft) && assert(updated)(isRight(equalTo(Person(first, notAdam, 7)))) diff --git a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala index e86ccd068..5e81e283a 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/DynamoDBQuery.scala @@ -455,8 +455,9 @@ object DynamoDBQuery { def get[From: Schema, Pk, Sk]( tableName: String, - partitionKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk], projections: ProjectionExpression[_, _]* + )( + partitionKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk] )(implicit ev: IsPrimaryKey[Pk], ev2: IsPrimaryKey[Sk]): DynamoDBQuery[From, Either[DynamoDBError, From]] = { val (_, _) = (ev, ev2) get(tableName, partitionKeyExpr.asAttrMap, projections: _*) @@ -500,7 +501,7 @@ object DynamoDBQuery { ): DynamoDBQuery[A, Option[A]] = updateItem(tableName, key)(action).map(_.flatMap(item => fromItem(item).toOption)) - def update[From: Schema, Pk, Sk](tableName: String, primaryKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk])( + def update[From: Schema, Pk, Sk](tableName: String)(primaryKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk])( action: Action[From] ): DynamoDBQuery[From, Option[From]] = updateItem(tableName, primaryKeyExpr.asAttrMap)(action).map(_.flatMap(item => fromItem(item).toOption)) @@ -508,7 +509,8 @@ object DynamoDBQuery { def deleteItem(tableName: String, key: PrimaryKey): Write[Any, Option[Item]] = DeleteItem(TableName(tableName), key) def delete[From: Schema, Pk, Sk]( - tableName: String, // TODO: rename as deleteFrom(tableName)(etc) + tableName: String + )( primaryKeyExpr: KeyConditionExpr.PrimaryKeyExpr[From, Pk, Sk] ): DynamoDBQuery[Any, Option[From]] = deleteItem(tableName, primaryKeyExpr.asAttrMap).map(_.flatMap(item => fromItem(item).toOption)) diff --git a/dynamodb/src/main/scala/zio/dynamodb/package.scala b/dynamodb/src/main/scala/zio/dynamodb/package.scala index 0b4721727..3b163e2bb 100644 --- a/dynamodb/src/main/scala/zio/dynamodb/package.scala +++ b/dynamodb/src/main/scala/zio/dynamodb/package.scala @@ -116,7 +116,7 @@ package object dynamodb { val batchGetItem: DynamoDBQuery[From, Chunk[Either[DynamoDBError.DecodingError, (A, Option[From])]]] = DynamoDBQuery .forEach(chunk) { a => - DynamoDBQuery.get(tableName, pk(a)).map { + DynamoDBQuery.get(tableName)(pk(a)).map { case Right(b) => Right((a, Some(b))) case Left(DynamoDBError.ValueNotFound(_)) => Right((a, None)) case Left(e @ DynamoDBError.DecodingError(_)) => Left(e) diff --git a/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala b/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala index 26cbade01..124130f50 100644 --- a/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala +++ b/dynamodb/src/test/scala/zio/dynamodb/GetAndPutSpec.scala @@ -29,18 +29,18 @@ object GetAndPutSpec extends ZIOSpecDefault { test("that exists") { for { _ <- TestDynamoDBExecutor.addItems("table1", primaryKey1 -> Item("id" -> 1, "name" -> "Avi")) - found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute + found <- get("table1")(SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Right(SimpleCaseClass2(1, "Avi"))) }, test("that does not exists") { for { - found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute + found <- get("table1")(SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Left(ValueNotFound("value with key AttrMap(Map(id -> Number(1))) not found"))) }, test("with missing attributes results in an error") { for { _ <- TestDynamoDBExecutor.addItems("table1", primaryKey1 -> Item("id" -> 1)) - found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute + found <- get("table1")(SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Left(DecodingError("field 'name' not found in Map(Map(String(id) -> Number(1)))"))) }, test("batched") { @@ -50,8 +50,7 @@ object GetAndPutSpec extends ZIOSpecDefault { primaryKey1 -> Item("id" -> 1, "name" -> "Avi"), primaryKey2 -> Item("id" -> 2, "name" -> "Tarlochan") ) - r <- (get("table1", SimpleCaseClass2.id.partitionKey === 1) zip get( - "table1", + r <- (get("table1")(SimpleCaseClass2.id.partitionKey === 1) zip get("table1")( SimpleCaseClass2.id.partitionKey === 2 )).execute } yield assertTrue(r._1 == Right(SimpleCaseClass2(1, "Avi"))) && assertTrue( @@ -64,14 +63,14 @@ object GetAndPutSpec extends ZIOSpecDefault { test("""SimpleCaseClass2(1, "Avi")""") { for { _ <- put[SimpleCaseClass2]("table1", SimpleCaseClass2(1, "Avi")).execute - found <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute + found <- get("table1")(SimpleCaseClass2.id.partitionKey === 1).execute } yield assertTrue(found == Right(SimpleCaseClass2(1, "Avi"))) }, test("""top level enum PreBilled(1, "foobar")""") { for { _ <- TestDynamoDBExecutor.addTable("table1", "id") _ <- put[Invoice]("table1", PreBilled(1, "foobar")).execute - found <- get("table1", PreBilled.id.partitionKey === 1).execute.absolve + found <- get("table1")(PreBilled.id.partitionKey === 1).execute.absolve } yield assertTrue(found == PreBilled(1, "foobar")) }, test("""batched SimpleCaseClass2(1, "Avi") and SimpleCaseClass2(2, "Tarlochan")""") { @@ -80,8 +79,8 @@ object GetAndPutSpec extends ZIOSpecDefault { "table1", SimpleCaseClass2(2, "Tarlochan") )).execute - found1 <- get("table1", SimpleCaseClass2.id.partitionKey === 1).execute - found2 <- get("table1", SimpleCaseClass2.id.partitionKey === 2).execute + found1 <- get("table1")(SimpleCaseClass2.id.partitionKey === 1).execute + found2 <- get("table1")(SimpleCaseClass2.id.partitionKey === 2).execute } yield assertTrue(found1 == Right(SimpleCaseClass2(1, "Avi"))) && assertTrue( found2 == Right(SimpleCaseClass2(2, "Tarlochan")) ) diff --git a/examples/src/main/scala/zio/dynamodb/examples/Main.scala b/examples/src/main/scala/zio/dynamodb/examples/Main.scala index cca9d9db5..d3c1f7c2e 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/Main.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/Main.scala @@ -20,7 +20,7 @@ object Main extends ZIOAppDefault { private val program = for { _ <- put("personTable", examplePerson).execute - person <- get("personTable", Person.id.partitionKey === 1).execute + person <- get("personTable")(Person.id.partitionKey === 1).execute _ <- zio.Console.printLine(s"hello $person") } yield () diff --git a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala index 5f8264538..95830f595 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/SimpleDecodedExample.scala @@ -24,7 +24,7 @@ object SimpleDecodedExample extends ZIOAppDefault { private val program = for { _ <- put("table1", NestedCaseClass2(id = 1, SimpleCaseClass3(2, "Avi", flag = true))).execute // Save case class to DB - caseClass <- get("table1", NestedCaseClass2.id.partitionKey === 2).execute // read case class from DB + caseClass <- get("table1")(NestedCaseClass2.id.partitionKey === 2).execute // read case class from DB _ <- printLine(s"get: found $caseClass") either <- scanSome[NestedCaseClass2]("table1", 10).execute _ <- printLine(s"scanSome: found $either") diff --git a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala index 29d8576b9..16765a069 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/TypeSafeRoundTripSerialisationExample.scala @@ -104,7 +104,7 @@ object TypeSafeRoundTripSerialisationExample extends ZIOAppDefault { def genericFindById[A <: Invoice]( pkExpr: KeyConditionExpr.PartitionKeyEquals[A, String] )(implicit ev: Schema[A]): ZIO[DynamoDBExecutor, Throwable, Either[DynamoDBError, Invoice]] = - DynamoDBQuery.get("table1", pkExpr).execute + DynamoDBQuery.get("table1")(pkExpr).execute def genericSave[A <: Invoice]( invoice: A diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala index b5832ec6c..514e3fa3b 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbExampleWithOptics.scala @@ -49,13 +49,13 @@ object StudentZioDynamoDbExampleWithOptics extends ZIOAppDefault { ) && email === "avi@gmail.com" && payment === Payment.CreditCard ) .execute - _ <- update("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student")(primaryKey("avi@gmail.com", "maths")) { enrollmentDate.set(Some(enrolDate2)) + payment.set(Payment.PayPal) + address .set( Some(Address("line1", "postcode1")) ) }.execute - _ <- delete("student", primaryKey("adam@gmail.com", "english")) + _ <- delete("student")(primaryKey("adam@gmail.com", "english")) .where( enrollmentDate === Some( enrolDate diff --git a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala index 3db8c0082..3e093f03a 100644 --- a/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala +++ b/examples/src/main/scala/zio/dynamodb/examples/dynamodblocal/StudentZioDynamoDbTypeSafeAPIExample.scala @@ -58,27 +58,27 @@ object StudentZioDynamoDbTypeSafeAPIExample extends ZIOAppDefault { ) === "CreditCard" /* && elephantCe */ ) .execute - _ <- update("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student")(primaryKey("avi@gmail.com", "maths")) { altPayment.set(Payment.PayPal) + addresses.prependList(List(Address("line0", "postcode0"))) + studentNumber .add(1000) + groups.addSet(Set("group3")) // + elephantAction }.execute - _ <- update("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student")(primaryKey("avi@gmail.com", "maths")) { altPayment.set(Payment.PayPal) + addresses.appendList(List(Address("line3", "postcode3"))) + groups .deleteFromSet(Set("group1")) }.execute - _ <- update("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student")(primaryKey("avi@gmail.com", "maths")) { enrollmentDate.setIfNotExists(Some(enrolDate2)) + payment.set(altPayment) + address .set( Some(Address("line1", "postcode1")) ) // + elephantAction }.execute - _ <- update("student", primaryKey("avi@gmail.com", "maths")) { + _ <- update("student")(primaryKey("avi@gmail.com", "maths")) { addresses.remove(1) }.execute _ <- - delete("student", primaryKey("adam@gmail.com", "english")) + delete("student")(primaryKey("adam@gmail.com", "english")) .where( (enrollmentDate === Some(enrolDate) && payment <> Payment.PayPal && studentNumber .between(1, 3) && groups.contains("group1") && collegeName.contains(