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

a8.sync.RowSync.scala Maven / Gradle / Ivy

The newest version!
package a8.sync


import a8.sync.ResolvedTable.ResolvedField
import a8.sync.impl.{NormalizedKey, NormalizedRow, NormalizedTuple, NormalizedValue}
import Imports.*
import a8.shared.jdbcf.{Dialect, SqlString}
import SqlString.*
import a8.common.logging.Level
import a8.sync.ResolvedTable.ColumnMapper.StringNormalValue
import a8.sync.RowSync.ValidationMessage
import a8.sync.dsl.TruncateAction
import cats.data.Chain
import zio.Chunk

import scala.annotation.nowarn
import scala.collection.mutable

//  sealed trait SyncAction
//  object SyncAction {
//    case object Insert extends SyncAction
//    case object Update extends SyncAction
//    case object Delete extends SyncAction
//    case object Noop extends SyncAction
//  }

object RowSync {

  type ValidationMessage = (Level,String)

  object impl {

    def autoTruncate(field: ResolvedField, value: NormalizedValue): NormalizedValue = {
      value match {
        case StringNormalValue(s) =>
          StringNormalValue(s.take(field.jdbcColumn.columnSize))
        case _ =>
          value
      }
    }

    def syncToSql(sync: RowSync): SqlString = {

      implicit def dialect: Dialect = sync.table.dialect

      import sync.table
      import sync.table.keyFieldsByIndex
      import sync.table.resolvedFields

      def tupleUp(fields: Chunk[ResolvedField], values: NormalizedTuple, fieldSeparator: SqlString)(fn: (ResolvedField, NormalizedValue) => SqlString): SqlString =
        fields
          .iterator
          .zip(values.iterator)
          .filter {
            case (_, NormalizedValue.Omit) => false
            case _ => true
          }
          .map { t =>
            fn(t._1, autoTruncate(t._1, t._2))
          }
          .mkSqlString(fieldSeparator)

      def whereClause: SqlString =
        tupleUp(keyFieldsByIndex, sync.key.values, q" and ") { (f, v) =>
          q"""${f} = ${v}"""
        }

      sync match {
        case v: RowSync.Insert =>
          val insertColumnNames =
            tupleUp(resolvedFields, v.truncatedNewRow.values, q", ") { (f, v) =>
              f
            }
          val insertValues =
            tupleUp(resolvedFields, v.truncatedNewRow.values, q", ") { (f, v) =>
              v
            }
          q"insert into ${table.qualifiedTargetTable} (${insertColumnNames}) values (${insertValues})"
        case v: RowSync.Update =>
          val setClause =
            tupleUp(resolvedFields, v.truncatedNewRow.values, q", ") { (f, v) =>
              q"""${f} = ${v}"""
            }
          q"update ${table.qualifiedTargetTable} set ${setClause} where ${whereClause}"
        case v: RowSync.Delete =>
          q"delete from ${table.qualifiedTargetTable} where ${whereClause}"
      }
    }

    def validateRow(untruncatedRow: NormalizedRow, defaultTruncationAction: TruncateAction, table: ResolvedTable): (Chain[ValidationMessage],NormalizedRow) = {
      val validationMessages = mutable.Buffer[ValidationMessage]()
      val truncatedValues =
        table
          .resolvedFields
          .iterator
          .zip(untruncatedRow.values.iterator)
          .map {
            case (resolvedField, untruncatedValue) =>
              val truncatedValue = autoTruncate(resolvedField, untruncatedValue)
              if ( truncatedValue != untruncatedValue ) {
                val truncateAction = resolvedField.field.truncateAction.getOrElse(defaultTruncationAction)
                val logLevelOpt = resolvedField.field.truncateAction.getOrElse(defaultTruncationAction).logLevel
                (logLevelOpt, untruncatedValue) match {
                  case (Some(logLevel), StringNormalValue(s)) =>
                    validationMessages.append(
                      logLevel -> s"${table.qualifiedTargetTable.toString}.${resolvedField.jdbcColumn.columnName.value} truncated from length ${s.length} to ${resolvedField.jdbcColumn.columnSize} original value is -- '${s}'"
                    ): @nowarn
                  case (Some(logLevel), v) =>
                    sys.error(s"don't know how to handle truncated value that is not a StringNormalValue ${v}")
                  case _ =>
                }
              }
              truncatedValue
          }
          .toChunk
      validationMessages.iterator.toChain -> untruncatedRow.copy(values = truncatedValues)
    }

  }

  case class Insert(truncatedNewRow: NormalizedRow, validationMessages: Chain[ValidationMessage], untruncatedNewRow: NormalizedRow)(table0: ResolvedTable) extends RowSync {
    val table = table0
    val key = truncatedNewRow.key
  }

  case class Update(truncatedNewRow: NormalizedRow, oldRow: NormalizedRow, validationMessages: Chain[ValidationMessage], untruncatedNewRow: NormalizedRow)(table0: ResolvedTable) extends RowSync {
    val table = table0
    val key = truncatedNewRow.key

    lazy val isNoop: Boolean =
      truncatedNewRow
        .values
        .iterator
        .zip(oldRow.values.iterator)
        .zip(table0.resolvedFields.iterator)
        .forall {
          case ((NormalizedValue.Omit, _), _) =>
            true
          case ((newValue, oldValue), field) =>
            newValue == oldValue
        }

  }

  case class Delete(oldRow: NormalizedRow, validationMessages: Chain[ValidationMessage])(table0: ResolvedTable) extends RowSync {
    val table = table0
    val key = oldRow.key
  }

}

sealed trait RowSync {

  val table: ResolvedTable
  val key: NormalizedKey
  val validationMessages: Chain[ValidationMessage]

  def updateSql: Option[SqlString] =
    this match {
      case u: RowSync.Update if u.isNoop =>
        None
      case _ =>
        RowSync.impl.syncToSql(this).toSome
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy