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

zio.dynamodb.ProjectionExpression.scala Maven / Gradle / Ivy

The newest version!
package zio.dynamodb

import zio.Chunk
import zio.dynamodb.Annotations.{ maybeCaseName, maybeDiscriminator }
import zio.dynamodb.ConditionExpression.Operand.ProjectionExpressionOperand
import zio.dynamodb.ProjectionExpression.{ ListElement, MapElement, Root }
import zio.dynamodb.UpdateExpression.SetOperand.{ IfNotExists, ListAppend, ListPrepend, PathOperand }
import zio.dynamodb.proofs._
import zio.schema.{ AccessorBuilder, Schema }

import scala.annotation.tailrec
import proofs.Containable

// The maximum depth for a document path is 32
sealed trait ProjectionExpression[-From, +To] { self =>

  def >>>[To2](that: ProjectionExpression[To, To2]): ProjectionExpression[From, To2] =
    that match {
      case ProjectionExpression.Root                       =>
        self.asInstanceOf[ProjectionExpression[From, To2]]
      case ProjectionExpression.MapElement(parent, key)    =>
        ProjectionExpression
          .MapElement(self >>> parent, key)
      case ProjectionExpression.ListElement(parent, index) =>
        ProjectionExpression
          .ListElement(self >>> parent, index)
    }

  def elementAt[To2](
    index: Int
  )(implicit ev: To <:< Iterable[To2]): ProjectionExpression[From, To2] = {
    val _ = ev
    ProjectionExpression
      .ListElement(self, index)
      .asInstanceOf[ProjectionExpression[From, To2]]
  }

  /**
   * DDB keys must be strings
   */
  def valueAt[To2](key: String)(implicit ev: To <:< Map[String, To2]): ProjectionExpression[From, To2] = {
    val _ = ev
    ProjectionExpression
      .MapElement(self, key)
      .asInstanceOf[ProjectionExpression[From, To2]]
  }

  def unsafeTo[To2](implicit ev: To <:< ProjectionExpression.Unknown): ProjectionExpression[From, To2] = {
    val _ = ev
    self.asInstanceOf[ProjectionExpression[From, To2]]
  }

  def unsafeFrom[From2]: ProjectionExpression[From2, To] =
    self.asInstanceOf[ProjectionExpression[From2, To]]

  def apply(index: Int): ProjectionExpression[From, ProjectionExpression.Unknown] =
    ProjectionExpression.listElement(self, index)

  def apply(key: String): ProjectionExpression[From, ProjectionExpression.Unknown] =
    ProjectionExpression.mapElement(self, key)

  // unary ConditionExpressions

  // applies to all types
  def exists: ConditionExpression[From]    = ConditionExpression.AttributeExists(self)
  // applies to all types
  def notExists: ConditionExpression[From] = ConditionExpression.AttributeNotExists(self)
  // Applies to all types except Number and Boolean
  def size[To2 >: To](implicit ev: Sizable[To2]): ConditionExpression.Operand.Size[From, To2] = {
    val _ = ev
    ConditionExpression.Operand.Size(self, ev)
  }

  /**
   * Removes an element at the specified index from a list. Note that index is zero based
   */
  def remove[From2 <: From](
    index: Int
  )(implicit ev: ListRemoveable[To]): UpdateExpression.Action.RemoveAction[From2] = {
    val _ = ev
    UpdateExpression.Action.RemoveAction(ProjectionExpression.ListElement(self, index))
  }

  // apply to all types
  def isBinary: ConditionExpression[From]    = isType(AttributeValueType.Binary)
  def isNumber: ConditionExpression[From]    = isType(AttributeValueType.Number)
  def isString: ConditionExpression[From]    = isType(AttributeValueType.String)
  def isBool: ConditionExpression[From]      = isType(AttributeValueType.Bool)
  def isBinarySet: ConditionExpression[From] = isType(AttributeValueType.BinarySet)
  def isList: ConditionExpression[From]      = isType(AttributeValueType.List)
  def isMap: ConditionExpression[From]       = isType(AttributeValueType.Map)
  def isNumberSet: ConditionExpression[From] = isType(AttributeValueType.NumberSet)
  def isNull: ConditionExpression[From]      = isType(AttributeValueType.Null)
  def isStringSet: ConditionExpression[From] = isType(AttributeValueType.StringSet)

  /**
   * Only applies to a string attribute
   */
  def beginsWith(av: String)(implicit ev: Beginnable[String, To]): ConditionExpression[From] = {
    val _ = ev
    ConditionExpression.BeginsWith(self, AttributeValue.String(av))
  }

  // UpdateExpression conversions

  /**
   * Removes this PathExpression from an item
   */
  def remove[From2 <: From]: UpdateExpression.Action.RemoveAction[From2] =
    UpdateExpression.Action.RemoveAction[From2](self)

  override def toString: String = {
    @tailrec
    def loop(pe: ProjectionExpression[_, _], acc: List[String]): List[String] =
      pe match {
        case Root                                               =>
          acc // identity
        case ProjectionExpression.MapElement(Root, pathSegment) =>
          loop(Root, acc :+ pathSegment)
        case MapElement(parent, key)                            =>
          val pathSegment = key
          loop(parent, acc :+ "." + pathSegment)
        case ListElement(parent, index)                         =>
          loop(parent, acc :+ s"[$index]")
      }

    loop(self, List.empty).reverse.mkString
  }

  private def isType(attributeType: AttributeValueType): ConditionExpression[From] =
    ConditionExpression.AttributeType(self, attributeType)

}

trait ProjectionExpressionLowPriorityImplicits0 extends ProjectionExpressionLowPriorityImplicits1 {
  implicit class ProjectionExpressionSyntax0[From, To: ToAttributeValue](self: ProjectionExpression[From, To]) {

    def partitionKey(implicit ev: IsPrimaryKey[To]): PartitionKey[From, To] = {
      val _ = ev
      self match {
        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]): SortKey[From, To] = {
      val _ = ev
      self match {
        case ProjectionExpression.MapElement(_, key) => SortKey[From, To](key)
        case _                                       => throw new IllegalArgumentException("Not a partition key") // should not happen
      }
    }

    def set(a: To): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self,
        UpdateExpression.SetOperand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(a))
      )

    def set(pe: ProjectionExpression[From, To]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(self, PathOperand(pe))

    /**
     *  Set attribute if it does not exists
     */
    def setIfNotExists(a: To): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(self, IfNotExists(self, implicitly[ToAttributeValue[To]].toAttributeValue(a)))

    /**
     * Append `a` to this list attribute
     */
    def append[A](
      a: A
    )(implicit ev: To <:< Iterable[A], to: ToAttributeValue[A]): UpdateExpression.Action.SetAction[From, To] =
      appendList(List(a).asInstanceOf[To])

    /**
     * Add list `xs` to the end of this list attribute
     */
    def appendList[A](
      xs: To
    )(implicit ev: To <:< Iterable[A], to: ToAttributeValue[A]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self,
        ListAppend(self, AttributeValue.List(xs.toList.map(a => to.toAttributeValue(a))))
      )

    /**
     * Prepend `a` to this list attribute
     */
    def prepend[A](
      a: A
    )(implicit ev: To <:< Iterable[A], to: ToAttributeValue[A]): UpdateExpression.Action.SetAction[From, To] = {
      val _ = ev
      UpdateExpression.Action.SetAction(
        self,
        ListPrepend(self, AttributeValue.List(List(a).map(a => to.toAttributeValue(a))))
      )
    }

    /**
     * Add list `xs` to the beginning of this list attribute
     */
    def prependList[A](
      xs: To
    )(implicit ev: To <:< Iterable[A], to: ToAttributeValue[A]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self,
        ListPrepend(self, AttributeValue.List(xs.toList.map(a => to.toAttributeValue(a))))
      )

    def between(minValue: To, maxValue: To): ConditionExpression[From] =
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .between(
          implicitly[ToAttributeValue[To]].toAttributeValue(minValue),
          implicitly[ToAttributeValue[To]].toAttributeValue(maxValue)
        )

    /**
     * Remove all elements of parameter `set` from this set attribute
     */
    def deleteFromSet(set: To)(implicit ev: To <:< Set[_]): UpdateExpression.Action.DeleteAction[From] = {
      val _ = ev
      UpdateExpression.Action.DeleteAction(self, implicitly[ToAttributeValue[To]].toAttributeValue(set))
    }

    def inSet(values: Set[To]): ConditionExpression[From] =
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .in(values.map(implicitly[ToAttributeValue[To]].toAttributeValue))

    def in(value: To, values: To*): ConditionExpression[From] = {
      val set: Set[To] = values.toSet + value
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .in(set.map(implicitly[ToAttributeValue[To]].toAttributeValue))
    }

    /**
     * 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))
    }

    /**
     * adds this value as a number attribute if it does not exists, else adds the numeric value to the existing attribute
     */
    def add(a: To)(implicit ev: Addable[To, To]): UpdateExpression.Action.AddAction[From] = {
      val _ = ev
      UpdateExpression.Action.AddAction(self, implicitly[ToAttributeValue[To]].toAttributeValue(a))
    }

    /**
     * Adds this set as an attribute if it does not exists, else if it exists it adds the elements of the set
     * Note that `A` must be a scalar type and is further restricted to Number, String, or Binary
     */
    def addSet[A](
      set: Set[A]
    )(implicit ev: Addable[To, A], evSet: Set[A] <:< To): UpdateExpression.Action.AddAction[From] = {
      val (_, _) = (ev, evSet)
      UpdateExpression.Action.AddAction(
        self,
        implicitly[ToAttributeValue[To]].toAttributeValue(evSet(set))
      )
    }

    def ===(that: To): ConditionExpression[From]                             =
      ConditionExpression.Equals(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def ===(that: ProjectionExpression[From, To]): ConditionExpression[From] =
      ConditionExpression.Equals(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def <>(that: To): ConditionExpression[From]                             =
      ConditionExpression.NotEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def <>(that: ProjectionExpression[From, To]): ConditionExpression[From] =
      ConditionExpression.NotEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def <(that: To): ConditionExpression[From]                             =
      ConditionExpression.LessThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def <(that: ProjectionExpression[From, To]): ConditionExpression[From] =
      ConditionExpression.LessThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def <=(that: To): ConditionExpression[From]                             =
      ConditionExpression.LessThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def <=(that: ProjectionExpression[From, To]): ConditionExpression[From] =
      ConditionExpression.LessThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def >(that: To): ConditionExpression[From]                             =
      ConditionExpression.GreaterThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def >(that: ProjectionExpression[From, To]): ConditionExpression[From] =
      ConditionExpression.GreaterThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def >=(that: To): ConditionExpression[From]                             =
      ConditionExpression.GreaterThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def >=(that: ProjectionExpression[From, To]): ConditionExpression[From] =
      ConditionExpression.GreaterThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

  }
}

trait ProjectionExpressionLowPriorityImplicits1 {
  implicit class ProjectionExpressionSyntax1[From, To](self: ProjectionExpression[From, To]) {

    /**
     * Modify or Add an item Attribute
     */
    def set(a: To)(implicit to: ToAttributeValue[To]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self,
        UpdateExpression.SetOperand.ValueOperand(to.toAttributeValue(a))
      )

    def set[From1 <: From](that: ProjectionExpression[From1, To]): UpdateExpression.Action.SetAction[From1, To] =
      UpdateExpression.Action.SetAction(self, PathOperand(that))

    /**
     * Add item attribute if it does not exists
     */
    def setIfNotExists(a: To)(implicit to: ToAttributeValue[To]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self,
        IfNotExists(self, to.toAttributeValue(a))
      )

    /**
     * Add item attribute if it does not exists
     */
    def setIfNotExists(
      that: ProjectionExpression[From, To], // TODO: To should be Unknown?
      a: To
    )(implicit
      to: ToAttributeValue[To]
    ): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action
        .SetAction(
          self,
          IfNotExists(that, to.toAttributeValue(a))
        )

    def append(a: To)(implicit to: ToAttributeValue[To]): UpdateExpression.Action.SetAction[From, To] =
      appendList(List(a))

    /**
     * Add list `xs` to the end of this list attribute
     */
    def appendList(xs: Iterable[To])(implicit to: ToAttributeValue[To]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self,
        ListAppend(self, AttributeValue.List(xs.toList.map(a => to.toAttributeValue(a))))
      )

    /**
     * Prepend `a` to this list attribute
     */
    def prepend(a: To)(implicit to: ToAttributeValue[To]): UpdateExpression.Action.SetAction[From, To] =
      prependList(List(a))

    /**
     * Add list `xs` to the beginning of this list attribute
     */
    def prependList(xs: Iterable[To])(implicit to: ToAttributeValue[To]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self,
        ListPrepend(self, AttributeValue.List(xs.toList.map(a => to.toAttributeValue(a))))
      )

    def between(minValue: To, maxValue: To)(implicit to: ToAttributeValue[To]): ConditionExpression[From] =
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .between(to.toAttributeValue(minValue), to.toAttributeValue(maxValue))

    /**
     * Remove all elements of parameter "set" from this set
     */
    def deleteFromSet[To2](
      set: To2
    )(implicit ev: To2 <:< Set[_], to: ToAttributeValue[To2]): UpdateExpression.Action.DeleteAction[From] = {
      val _ = ev
      UpdateExpression.Action.DeleteAction(self, to.toAttributeValue(set))
    }

    def inSet[To2](values: Set[To2])(implicit to: ToAttributeValue[To2]): ConditionExpression[From] =
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .in(values.map(to.toAttributeValue))
        .asInstanceOf[ConditionExpression[From]]

    def in[To2](value: To2, values: To2*)(implicit to: ToAttributeValue[To2]): ConditionExpression[From] = {
      val set: Set[To2] = values.toSet + value
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .in(set.map(to.toAttributeValue))
    }

    /**
     * Applies to a String or Set
     */
    def contains[To2](av: To2)(implicit to: ToAttributeValue[To2]): ConditionExpression[From] =
      ConditionExpression.Contains(self, to.toAttributeValue(av))

    /**
     * adds a number attribute if it does not exists, else adds the numeric value to the existing attribute
     */
    def add[To2](a: To2)(implicit to: ToAttributeValue[To2]): UpdateExpression.Action.AddAction[From] =
      UpdateExpression.Action.AddAction(self, to.toAttributeValue(a))

    /**
     * adds a set attribute if it does not exists, else if it exists it adds the elements of the set
     */
    def addSet[To2: ToAttributeValue](
      set: To2
    )(implicit ev: To2 <:< Set[_]): UpdateExpression.Action.AddAction[From] = {
      val _ = ev
      UpdateExpression.Action.AddAction(
        self,
        implicitly[ToAttributeValue[To2]].toAttributeValue(set)
      )
    }

    def ===[To2: ToAttributeValue](that: To2): ConditionExpression[From] =
      ConditionExpression.Equals(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To2]].toAttributeValue(that))
      )

    def ===[To2](
      that: ProjectionExpression[From, To2]
    )(implicit refersTo: RefersTo[To, To2]): ConditionExpression[From] = {
      val _ = refersTo
      ConditionExpression.Equals(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )
    }

    // TODO: think about using != instead
    def <>[To2](
      that: ProjectionExpression[From, To2]
    )(implicit refersTo: RefersTo[To, To2]): ConditionExpression[From] = {
      val _ = refersTo
      ConditionExpression.NotEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )
    }
    def <[To2](
      that: ProjectionExpression[From, To2]
    )(implicit refersTo: RefersTo[To, To2]): ConditionExpression[From] = {
      val _ = refersTo
      ConditionExpression.LessThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )
    }
    def <=[To2](
      that: ProjectionExpression[From, To2]
    )(implicit refersTo: RefersTo[To, To2]): ConditionExpression[From] = {
      val _ = refersTo
      ConditionExpression.LessThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )
    }
    def >[To2](
      that: ProjectionExpression[From, To2]
    )(implicit refersTo: RefersTo[To, To2]): ConditionExpression[From] = {
      val _ = refersTo
      ConditionExpression.GreaterThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )
    }
    def >=[To2](
      that: ProjectionExpression[From, To2]
    )(implicit refersTo: RefersTo[To, To2]): ConditionExpression[From] = {
      val _ = refersTo
      ConditionExpression.GreaterThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )
    }
  }
}

object ProjectionExpression extends ProjectionExpressionLowPriorityImplicits0 {

  type Unknown

  def some[A]: ProjectionExpression[Option[A], A] =
    ProjectionExpression.root.asInstanceOf[ProjectionExpression[Option[A], A]]

  sealed trait OpticType
  object OpticType {
    case object Lens                                extends OpticType
    case class Prism(discriminator: Option[String]) extends OpticType
  }

  implicit class ProjectionExpressionSyntax[From](self: ProjectionExpression[From, Unknown]) {

    def partitionKey: PartitionKey[From, Unknown] =
      self match {
        case ProjectionExpression.MapElement(_, key) => PartitionKey[From, Unknown](key)
        case _                                       => throw new IllegalArgumentException("Not a partition key") // should not happen
      }
    def sortKey: SortKey[From, Unknown]           =
      self match {
        case ProjectionExpression.MapElement(_, key) => SortKey[From, Unknown](key)
        case _                                       => throw new IllegalArgumentException("Not a partition key") // should not happen
      }

    /**
     * Modify or Add an item Attribute
     */
    def set[To: ToAttributeValue](a: To): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self.unsafeTo[To],
        UpdateExpression.SetOperand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(a))
      )

    /**
     * Modify or Add an item Attribute
     */
    def set[From1 <: From, To](that: ProjectionExpression[From1, To]): UpdateExpression.Action.SetAction[From1, To] =
      UpdateExpression.Action.SetAction(self.unsafeTo, PathOperand(that))

    /**
     * Add item attribute if it does not exists
     */
    def setIfNotExists[To: ToAttributeValue](a: To): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self.unsafeTo,
        IfNotExists(self, implicitly[ToAttributeValue[To]].toAttributeValue(a))
      )

    /**
     * Add item attribute if it does not exists
     */
    def setIfNotExists[To: ToAttributeValue](
      that: ProjectionExpression[From, ProjectionExpression.Unknown],
      a: To
    ): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self.unsafeTo,
        IfNotExists(that, implicitly[ToAttributeValue[To]].toAttributeValue(a))
      )

    def append[A](a: A)(implicit to: ToAttributeValue[A]): UpdateExpression.Action.SetAction[From, A] =
      appendList(List(a))

    /**
     * Add list `xs` to the end of this list attribute
     */
    def appendList[To: ToAttributeValue](xs: Iterable[To]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self.unsafeTo,
        ListAppend(self, AttributeValue.List(xs.toList.map(a => implicitly[ToAttributeValue[To]].toAttributeValue(a))))
      )

    /**
     * Prepend `a` to this list attribute
     */
    def prepend[To: ToAttributeValue](a: To): UpdateExpression.Action.SetAction[From, To] =
      prependList(List(a))

    /**
     * Add list `xs` to the beginning of this list attribute
     */
    def prependList[To: ToAttributeValue](xs: Iterable[To]): UpdateExpression.Action.SetAction[From, To] =
      UpdateExpression.Action.SetAction(
        self.unsafeTo,
        ListPrepend(self, AttributeValue.List(xs.toList.map(a => implicitly[ToAttributeValue[To]].toAttributeValue(a))))
      )

    def between[To](minValue: To, maxValue: To)(implicit to: ToAttributeValue[To]): ConditionExpression[From] =
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .between(to.toAttributeValue(minValue), to.toAttributeValue(maxValue))

    /**
     * Remove all elements of parameter "set" from this set
     */
    def deleteFromSet[To](
      set: To
    )(implicit ev: To <:< Set[_], to: ToAttributeValue[To]): UpdateExpression.Action.DeleteAction[From] = {
      val _ = ev
      UpdateExpression.Action.DeleteAction(self, to.toAttributeValue(set))
    }

    def inSet[To](values: Set[To])(implicit to: ToAttributeValue[To]): ConditionExpression[From] =
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .in(values.map(to.toAttributeValue))

    def in[To](value: To, values: To*)(implicit to: ToAttributeValue[To]): ConditionExpression[From] = {
      val set: Set[To] = values.toSet + value
      ConditionExpression.Operand
        .ProjectionExpressionOperand(self)
        .in(set.map(to.toAttributeValue))
    }

    /**
     * Applies to a String or Set
     */
    def contains[To](av: To)(implicit to: ToAttributeValue[To]): ConditionExpression[From] =
      ConditionExpression.Contains(self, to.toAttributeValue(av))

    /**
     * adds a number attribute if it does not exists, else adds the numeric value to the existing attribute
     */
    def add[To](a: To)(implicit to: ToAttributeValue[To]): UpdateExpression.Action.AddAction[From] =
      UpdateExpression.Action.AddAction(self, to.toAttributeValue(a))

    /**
     * adds a set attribute if it does not exists, else if it exists it adds the elements of the set
     */
    def addSet[To: ToAttributeValue](set: To)(implicit ev: To <:< Set[_]): UpdateExpression.Action.AddAction[From] = {
      val _ = ev
      UpdateExpression.Action.AddAction(
        self,
        implicitly[ToAttributeValue[To]].toAttributeValue(set)
      )
    }

    def ===[To: ToAttributeValue](that: To): ConditionExpression[From] =
      ConditionExpression.Equals(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )

    def ===(that: ProjectionExpression[From, Any]): ConditionExpression[From] =
      ConditionExpression.Equals(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def <>[To: ToAttributeValue](that: To): ConditionExpression[From]        =
      ConditionExpression.NotEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def <>(that: ProjectionExpression[From, Any]): ConditionExpression[From] =
      ConditionExpression.NotEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def <[To: ToAttributeValue](that: To): ConditionExpression[From]        =
      ConditionExpression.LessThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def <(that: ProjectionExpression[From, Any]): ConditionExpression[From] =
      ConditionExpression.LessThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def <=[To: ToAttributeValue](that: To): ConditionExpression[From]        =
      ConditionExpression.LessThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def <=(that: ProjectionExpression[From, Any]): ConditionExpression[From] =
      ConditionExpression.LessThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def >[To: ToAttributeValue](that: To): ConditionExpression[From]        =
      ConditionExpression.GreaterThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def >(that: ProjectionExpression[From, Any]): ConditionExpression[From] =
      ConditionExpression.GreaterThan(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )

    def >=[To: ToAttributeValue](that: To): ConditionExpression[From]        =
      ConditionExpression.GreaterThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ValueOperand(implicitly[ToAttributeValue[To]].toAttributeValue(that))
      )
    def >=(that: ProjectionExpression[From, Any]): ConditionExpression[From] =
      ConditionExpression.GreaterThanOrEqual(
        ProjectionExpressionOperand(self),
        ConditionExpression.Operand.ProjectionExpressionOperand(that)
      )
  }

  val builder = new AccessorBuilder {
    override type Lens[F, From, To]   = ProjectionExpression[From, To]
    override type Prism[F, From, To]  = ProjectionExpression[From, To]
    override type Traversal[From, To] = Unit

    // respects @caseName annotation
    override def makeLens[F, S, A](product: Schema.Record[S], term: Schema.Field[S, A]): Lens[F, S, A] = {

      val label = maybeCaseName(term.annotations).getOrElse(term.name)
      ProjectionExpression.MapElement(Root, label)
    }

    def makePrism[F, S, A](sum: Schema.Enum[S], term: Schema.Case[S, A]): Prism[F, S, A] =
      maybeDiscriminator(sum.annotations) match {
        case Some(_) =>
          ProjectionExpression.Root.asInstanceOf[Prism[F, S, A]]
        case None    =>
          ProjectionExpression
            .MapElement(Root, maybeCaseName(term.annotations).getOrElse(term.id))
            .asInstanceOf[Prism[F, S, A]]
      }

    override def makeTraversal[S, A](collection: Schema.Collection[S, A], element: Schema[A]): Traversal[S, A] = ()
  }

  // where should we put this?
  def accessors[A](implicit s: Schema[A]): s.Accessors[builder.Lens, builder.Prism, builder.Traversal] =
    s.makeAccessors(builder)

  /*
  \.
  Matches the dot character.

  (?=(?:[^][^])*(?![^]*))
  Uses a positive lookahead to assert that the dot is followed by an even number of backticks.
  This ensures that the dot is outside a character sequence enclosed in backticks.
   */
  val regexDotOutsideBackticks = """\.(?=(?:[^`]*`[^`]*`)*(?![^`]*`))""".r

  private val regexMapElement     = """(^[a-zA-Z0-9_-]+|^`[^`]+`)""".r
  private val regexIndexedElement = """(^[a-zA-Z0-9_-]+|^`[^`]+`)(\[[0-9]+])+""".r
  private val regexGroupedIndexes = """(\[([0-9]+)])""".r

  // Note that you can only use a ProjectionExpression if the first character is a-z or A-Z and the second character
  // (if present) is a-z, A-Z, or 0-9. Also key words are not allowed
  // If this is not the case then you must use the Expression Attribute Names facility to create an alias.
  // Attribute names containing a dot "." must also use the Expression Attribute Names
  def apply(name: String) = ProjectionExpression.MapElement(Root, name)

  private[dynamodb] case object Root extends ProjectionExpression[Any, Any] // [Any, Nothing] ?

  private[dynamodb] final case class MapElement[From, To](parent: ProjectionExpression[From, _], key: String)
      extends ProjectionExpression[From, To]

  private[dynamodb] final case class ListElement[From, To](parent: ProjectionExpression[From, _], index: Int)
      extends ProjectionExpression[From, To]

  private[dynamodb] def root: ProjectionExpression[Any, Any]                           = Root
  private[dynamodb] def mapElement[A](parent: ProjectionExpression[A, _], key: String) =
    MapElement[A, ProjectionExpression.Unknown](parent, key)

  private[dynamodb] def listElement[A](parent: ProjectionExpression[A, _], index: Int) =
    ListElement[A, ProjectionExpression.Unknown](parent, index)

  /**
   * Unsafe version of `parse` that throws an IllegalStateException rather than returning an Either.
   * Note all path elements are substituted automatically using the `ExpressionAttributeNames` facility.
   * The underscore "_" and hypen "-" are allowed in the name without any escaping.
   * Any other special characters must be escaped with backticks as the first and last characters.
   *
   * Examples:
   * {{{
   * $("foo")            // simple map element
   * $("foo.bar[9].baz") // array element with nested access
   * $("`foo.bar`")      // simple map element with a dot in the name
   * $("foo_bar")        // simple map element
   * $("foo-bar")        // simple map element
   * }}}
   *
   * @see [[parse]]
   */
  def $(s: String): ProjectionExpression[Any, Unknown] =
    $$(s)

  def $$[From, To](s: String): ProjectionExpression[From, To] =
    parse(s) match {
      case Right(a)  => a.unsafeFrom[From].unsafeTo[To]
      case Left(msg) => throw new IllegalStateException(msg)
    }

  /**
   * Parses a string into an ProjectionExpression
   * Note all path elements are substituted automatically using the `ExpressionAttributeNames` facility.
   * The underscore "_" and hypen "-" are allowed in the name without any escaping.
   * Any other special characters must be escaped with backticks as the first and last characters.
   * eg
   * {{{
   * parse("foo.bar[9].baz"")
   * // Right(MapElement(ListElement(MapElement(Root(bar),baz),9),baz))
   * parse(fo$$o.ba$$r[9].ba$$z)
   * // Left("error with fo$$o,error with ba$$r[9],error with ba$$z")
   * }}}
   * @param s Projection expression as a string
   * @return either a `Right` of ProjectionExpression if successful, else a list of errors in a string
   */
  def parse(s: String): Either[String, ProjectionExpression[Unknown, Unknown]] = {

    // used to accumulate the Errors or if there are none the ProjectionExpression
    final case class Builder(pe: Option[Either[Chunk[String], ProjectionExpression[Unknown, Unknown]]] = None) { self =>

      def mapElement(name: String): Builder =
        Builder(self.pe match {
          case None                     =>
            Some(Right(ProjectionExpression.MapElement(Root, name)))
          case Some(Right(pe))          =>
            Some(Right(pe(name)))
          case someLeft @ Some(Left(_)) =>
            someLeft
        })

      def listElement(name: String, indexes: List[Int]): Builder = {
        @tailrec
        def multiDimPe(
          pe: ProjectionExpression[ProjectionExpression.Unknown, ProjectionExpression.Unknown],
          indexes: List[Int]
        ): ProjectionExpression[ProjectionExpression.Unknown, ProjectionExpression.Unknown] =
          if (indexes == Nil)
            pe
          else
            multiDimPe(pe(indexes.head), indexes.tail)

        Builder(self.pe match {
          case None                     =>
            Some(Right(multiDimPe(ProjectionExpression.MapElement(Root, name), indexes)))
          case Some(Right(pe))          =>
            Some(Right(multiDimPe(MapElement(pe, name), indexes)))
          case someLeft @ Some(Left(_)) =>
            someLeft
        })
      }

      def addError(s: String): Builder =
        Builder(self.pe match {
          case None | Some(Right(_)) =>
            Some(Left(Chunk(s"error with '$s'")))
          case Some(Left(chunk))     =>
            Some(Left(chunk :+ s"error with '$s'"))
        })

      def either
        : Either[Chunk[String], ProjectionExpression[ProjectionExpression.Unknown, ProjectionExpression.Unknown]] =
        self.pe.getOrElse(Left(Chunk("error - at least one element must be specified")))
    }

    if (s == null)
      Left("error - input string is 'null'")
    else if (s.startsWith(".") || s.endsWith("."))
      Left(s"error - input string '$s' is invalid")
    else {

      val elements: List[String] = regexDotOutsideBackticks.split(s).toList

      val builder = elements.foldLeft(Builder()) {
        case (accBuilder, s) =>
          s match {
            case regexIndexedElement(name, _) =>
              val indexesString = s.substring(s.indexOf('['))
              val indexes       = regexGroupedIndexes.findAllMatchIn(indexesString).map(_.group(2).toInt).toList
              accBuilder.listElement(name, indexes)
            case regexMapElement(name)        =>
              accBuilder.mapElement(name)
            case _                            =>
              accBuilder.addError(s)
          }
      }

      builder.either.left.map(_.mkString(","))
    }
  }

  def projectionsFromSchema[A: Schema]: Chunk[ProjectionExpression[_, _]] =
    implicitly[Schema[A]] match {
      case r: Schema.Record[A] =>
        r.fields.map { f =>
          ProjectionExpression.MapElement(Root, f.name)
        }
      case _                   => Chunk.empty
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy