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

com.spotify.scio.spanner.SpannerIO.scala Maven / Gradle / Ivy

/*
 * Copyright 2019 Spotify AB.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.spotify.scio.spanner

import com.google.cloud.spanner._
import com.spotify.scio.ScioContext
import com.spotify.scio.coders.CoderMaterializer
import com.spotify.scio.io.{EmptyTap, EmptyTapOf, ScioIO, Tap}
import com.spotify.scio.values.SCollection
import org.apache.beam.sdk.io.gcp.spanner.SpannerIO.FailureMode
import org.apache.beam.sdk.io.gcp.spanner.{SpannerConfig, SpannerIO => BSpannerIO}

import scala.jdk.CollectionConverters._
import com.spotify.scio.io.TapT

sealed trait SpannerIO[T] extends ScioIO[T] {
  final override val tapT: TapT.Aux[T, Nothing] = EmptyTapOf[T]
  val config: SpannerConfig

  override def testId: String =
    s"${getClass.getSimpleName}" +
      s"(${config.getProjectId}, ${config.getInstanceId}, ${config.getDatabaseId})"
}

object SpannerRead {
  object ReadParam {
    val DefaultWithTransaction: Boolean = false
    val DefaultWithBatching: Boolean = true
  }

  sealed trait ReadMethod
  final case class FromTable(tableName: String, columns: Seq[String]) extends ReadMethod
  final case class FromQuery(query: String) extends ReadMethod

  final case class ReadParam private (
    readMethod: ReadMethod,
    withTransaction: Boolean = ReadParam.DefaultWithTransaction,
    withBatching: Boolean = ReadParam.DefaultWithBatching
  )
}

final case class SpannerRead(config: SpannerConfig) extends SpannerIO[Struct] {
  import SpannerRead._

  override type ReadP = ReadParam
  override type WriteP = Nothing

  override protected def read(sc: ScioContext, params: ReadP): SCollection[Struct] = {
    val coder = CoderMaterializer.beam(sc, coders.spannerStructCoder)
    var transform = BSpannerIO
      .read()
      .withSpannerConfig(config)
      .withBatching(params.withBatching)

    transform = params.readMethod match {
      case x: FromTable => transform.withTable(x.tableName).withColumns(x.columns.asJava)
      case y: FromQuery => transform.withQuery(y.query)
    }

    if (params.withTransaction) {
      val txn = BSpannerIO.createTransaction().withSpannerConfig(config)
      transform = transform.withTransaction(sc.applyInternal(txn))
    }

    sc.applyTransform(transform).setCoder(coder)
  }

  override protected def write(data: SCollection[Struct], params: WriteP): Tap[Nothing] =
    throw new UnsupportedOperationException("SpannerRead is read-only")

  override def tap(params: ReadP): Tap[Nothing] = EmptyTap
}

object SpannerWrite {
  object WriteParam {
    val DefaultFailureMode: FailureMode = FailureMode.FAIL_FAST
    val DefaultBatchSizeBytes: Long = 1024L * 1024L
  }

  final case class WriteParam private (
    failureMode: FailureMode = WriteParam.DefaultFailureMode,
    batchSizeBytes: Long = WriteParam.DefaultBatchSizeBytes
  )
}

final case class SpannerWrite(config: SpannerConfig) extends SpannerIO[Mutation] {
  override type ReadP = Nothing
  override type WriteP = SpannerWrite.WriteParam

  override protected def write(data: SCollection[Mutation], params: WriteP): Tap[Nothing] = {
    val transform = BSpannerIO
      .write()
      .withSpannerConfig(config)
      .withBatchSizeBytes(params.batchSizeBytes)
      .withFailureMode(params.failureMode)

    data.applyInternal(transform)
    EmptyTap
  }

  override protected def read(sc: ScioContext, params: ReadP): SCollection[Mutation] = sc.wrap {
    throw new UnsupportedOperationException("SpannerWrite is write-only")
  }

  override def tap(params: ReadP): Tap[Nothing] = EmptyTap
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy