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

org.apache.flink.table.planner.plan.utils.WindowEmitStrategy.scala Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.flink.table.planner.plan.utils

import org.apache.flink.annotation.Experimental
import org.apache.flink.configuration.ConfigOption
import org.apache.flink.configuration.ConfigOptions.key
import org.apache.flink.table.api.{TableConfig, TableException}
import org.apache.flink.table.planner.plan.logical.{LogicalWindow, SessionGroupWindow}
import org.apache.flink.table.planner.plan.utils.AggregateUtil.isRowtimeAttribute
import org.apache.flink.table.planner.{JBoolean, JLong}
import org.apache.flink.table.runtime.operators.window.TimeWindow
import org.apache.flink.table.runtime.operators.window.triggers._

import java.time.Duration

class WindowEmitStrategy(
    isEventTime: JBoolean,
    isSessionWindow: JBoolean,
    earlyFireDelay: Duration,
    earlyFireDelayEnabled: JBoolean,
    lateFireDelay: Duration,
    lateFireDelayEnabled: JBoolean,
    allowLateness: JLong) {

  checkValidation()

  def getAllowLateness: JLong = allowLateness

  private def checkValidation(): Unit = {
    if (isSessionWindow && (earlyFireDelayEnabled || lateFireDelayEnabled)) {
      throw new TableException("Session window doesn't support EMIT strategy currently.")
    }
    if (isEventTime && lateFireDelayEnabled && allowLateness <= 0L) {
      throw new TableException("The 'AFTER WATERMARK' emit strategy requires set " +
        "'minIdleStateRetentionTime' in table config.")
    }
    if (earlyFireDelayEnabled && (earlyFireDelay == null || earlyFireDelay.toMillis < 0)) {
      throw new TableException("Early-fire delay should not be null or negative value when" +
        "enable early-fire emit strategy.")
    }
    if (lateFireDelayEnabled && (lateFireDelay == null || lateFireDelay.toMillis < 0)) {
      throw new TableException("Late-fire delay should not be null or negative value when" +
        "enable late-fire emit strategy.")
    }
  }

  def produceUpdates: JBoolean = {
    if (isEventTime) {
      earlyFireDelayEnabled || lateFireDelayEnabled
    } else {
      earlyFireDelayEnabled
    }
  }

  def getTrigger: Trigger[TimeWindow] = {
    val earlyTrigger = createTriggerFromFireDelay(earlyFireDelayEnabled, earlyFireDelay)
    val lateTrigger = createTriggerFromFireDelay(lateFireDelayEnabled, lateFireDelay)

    if (isEventTime) {
      val trigger = EventTimeTriggers.afterEndOfWindow[TimeWindow]()

      (earlyTrigger, lateTrigger) match {
        case (Some(early), Some(late)) => trigger.withEarlyFirings(early).withLateFirings(late)
        case (Some(early), None) => trigger.withEarlyFirings(early)
        case (None, Some(late)) => trigger.withLateFirings(late)
        case (None, None) => trigger
      }
    } else {
      val trigger = ProcessingTimeTriggers.afterEndOfWindow[TimeWindow]()

      // late trigger is ignored, as no late element in processing time
      earlyTrigger match {
        case Some(early) => trigger.withEarlyFirings(early)
        case None => trigger
      }
    }
  }

  override def toString: String = {
    val builder = new StringBuilder
    val earlyString = fireDelayToString(earlyFireDelayEnabled, earlyFireDelay)
    val lateString = fireDelayToString(lateFireDelayEnabled, lateFireDelay)
    if (earlyString != null) {
      builder.append("early ").append(earlyString)
    }
    if (lateString != null) {
      if (earlyString != null) {
        builder.append(", ")
      }
      builder.append("late ").append(lateString)
    }
    builder.toString
  }

  private def createTriggerFromFireDelay(
      enableDelayEmit: JBoolean,
      fireDelay: Duration): Option[Trigger[TimeWindow]] = {
    if (!enableDelayEmit) {
      None
    } else {
      if (fireDelay.toMillis > 0) {
        Some(ProcessingTimeTriggers.every(fireDelay))
      } else {
        Some(ElementTriggers.every())
      }
    }
  }

  private def fireDelayToString(enableDelayEmit: JBoolean, fireDelay: Duration): String = {
    if (!enableDelayEmit) {
      null
    } else {
      if (fireDelay.toMillis > 0) {
        s"delay ${fireDelay.toMillis} millisecond"
      } else {
        "no delay"
      }
    }
  }
}

object WindowEmitStrategy {
  def apply(tableConfig: TableConfig, window: LogicalWindow): WindowEmitStrategy = {
    val isEventTime = isRowtimeAttribute(window.timeAttribute)
    val isSessionWindow = window.isInstanceOf[SessionGroupWindow]

    val allowLateness = if (isSessionWindow) {
      // ignore allow lateness in session window because retraction is not supported
      0L
    } else if (tableConfig.getMinIdleStateRetentionTime < 0) {
      // min idle state retention time is not set, use 0L as default which means not allow lateness
      0L
    } else {
      // use min idle state retention time as allow lateness
      tableConfig.getMinIdleStateRetentionTime
    }
    val enableEarlyFireDelay = tableConfig.getConfiguration.getBoolean(
      TABLE_EXEC_EMIT_EARLY_FIRE_ENABLED)
    val earlyFireDelay: Duration = tableConfig.getConfiguration
      .getOptional(TABLE_EXEC_EMIT_EARLY_FIRE_DELAY)
      .orElse(null)
    val enableLateFireDelay = tableConfig.getConfiguration.getBoolean(
      TABLE_EXEC_EMIT_LATE_FIRE_ENABLED)
    val lateFireDelay: Duration = tableConfig.getConfiguration
      .getOptional(TABLE_EXEC_EMIT_LATE_FIRE_DELAY)
      .orElse(null)
    new WindowEmitStrategy(
      isEventTime,
      isSessionWindow,
      earlyFireDelay,
      enableEarlyFireDelay,
      lateFireDelay,
      enableLateFireDelay,
      allowLateness)
  }

  // It is a experimental config, will may be removed later.
  @Experimental
  val TABLE_EXEC_EMIT_EARLY_FIRE_ENABLED: ConfigOption[JBoolean] =
  key("table.exec.emit.early-fire.enabled")
      .defaultValue(Boolean.box(false))
      .withDescription("Specifies whether to enable early-fire emit." +
          "Early-fire is an emit strategy before watermark advanced to end of window.")

  // It is a experimental config, will may be removed later.
  @Experimental
  val TABLE_EXEC_EMIT_EARLY_FIRE_DELAY: ConfigOption[Duration] =
  key("table.exec.emit.early-fire.delay")
      .durationType()
      .noDefaultValue()
      .withDescription("The early firing delay in milli second, early fire is " +
          "the emit strategy before watermark advanced to end of window. " +
          "< 0 is illegal configuration. " +
          "0 means no delay (fire on every element). " +
          "> 0 means the fire interval. ")

  // It is a experimental config, will may be removed later.
  @Experimental
  val TABLE_EXEC_EMIT_LATE_FIRE_ENABLED: ConfigOption[JBoolean] =
  key("table.exec.emit.late-fire.enabled")
      .defaultValue(Boolean.box(false))
      .withDescription("Specifies whether to enable late-fire emit. " +
          "Late-fire is an emit strategy after watermark advanced to end of window.")

  // It is a experimental config, will may be removed later.
  @Experimental
  val TABLE_EXEC_EMIT_LATE_FIRE_DELAY: ConfigOption[Duration] =
  key("table.exec.emit.late-fire.delay")
      .durationType()
      .noDefaultValue()
      .withDescription("The late firing delay in milli second, late fire is " +
          "the emit strategy after watermark advanced to end of window. " +
          "< 0 is illegal configuration. " +
          "0 means no delay (fire on every element). " +
          "> 0 means the fire interval.")

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy