{"version":3,"file":"joinDataFrames.mjs","sources":["../../../../src/transformations/transformers/joinDataFrames.ts"],"sourcesContent":["import { getTimeField, sortDataFrame } from '../../dataframe/processDataFrame';\nimport { DataFrame, Field, FieldType, TIME_SERIES_VALUE_FIELD_NAME } from '../../types/dataFrame';\nimport { FieldMatcher } from '../../types/transformations';\nimport { fieldMatchers } from '../matchers';\nimport { FieldMatcherID } from '../matchers/ids';\n\nimport { JoinMode } from './joinByField';\n\nexport function pickBestJoinField(data: DataFrame[]): FieldMatcher {\n  const { timeField } = getTimeField(data[0]);\n  if (timeField) {\n    return fieldMatchers.get(FieldMatcherID.firstTimeField).get({});\n  }\n  let common: string[] = [];\n  for (const f of data[0].fields) {\n    if (f.type === FieldType.number) {\n      common.push(f.name);\n    }\n  }\n\n  for (let i = 1; i < data.length; i++) {\n    const names: string[] = [];\n    for (const f of data[0].fields) {\n      if (f.type === FieldType.number) {\n        names.push(f.name);\n      }\n    }\n    common = common.filter((v) => !names.includes(v));\n  }\n\n  return fieldMatchers.get(FieldMatcherID.byName).get(common[0]);\n}\n\n/**\n * @internal\n */\nexport interface JoinOptions {\n  /**\n   * The input fields\n   */\n  frames: DataFrame[];\n\n  /**\n   * The field to join -- frames that do not have this field will be dropped\n   */\n  joinBy?: FieldMatcher;\n\n  /**\n   * Optionally filter the non-join fields\n   */\n  keep?: FieldMatcher;\n\n  /**\n   * @internal -- used when we need to keep a reference to the original frame/field index\n   */\n  keepOriginIndices?: boolean;\n\n  /**\n   * @internal -- keep any pre-cached state.displayName\n   */\n  keepDisplayNames?: boolean;\n\n  /**\n   * @internal -- Optionally specify how to treat null values\n   */\n  nullMode?: (field: Field) => JoinNullMode;\n\n  /**\n   * @internal -- Optionally specify a join mode (outer or inner)\n   */\n  mode?: JoinMode;\n}\n\nfunction getJoinMatcher(options: JoinOptions): FieldMatcher {\n  return options.joinBy ?? pickBestJoinField(options.frames);\n}\n\n/**\n * @internal\n */\nexport function maybeSortFrame(frame: DataFrame, fieldIdx: number) {\n  if (fieldIdx >= 0) {\n    let sortByField = frame.fields[fieldIdx];\n\n    if (sortByField.type !== FieldType.string && !isLikelyAscendingVector(sortByField.values)) {\n      frame = sortDataFrame(frame, fieldIdx);\n    }\n  }\n\n  return frame;\n}\n\n/**\n * This will return a single frame joined by the first matching field.  When a join field is not specified,\n * the default will use the first time field\n */\nexport function joinDataFrames(options: JoinOptions): DataFrame | undefined {\n  if (!options.frames?.length) {\n    return;\n  }\n\n  const nullMode =\n    options.nullMode ??\n    ((field: Field) => {\n      let spanNulls = field.config.custom?.spanNulls;\n      return spanNulls === true ? NULL_REMOVE : spanNulls === -1 ? NULL_RETAIN : NULL_EXPAND;\n    });\n\n  if (options.frames.length === 1) {\n    let frame = options.frames[0];\n    let frameCopy = frame;\n\n    const joinFieldMatcher = getJoinMatcher(options);\n    let joinIndex = frameCopy.fields.findIndex((f) => joinFieldMatcher(f, frameCopy, options.frames));\n\n    if (options.keepOriginIndices) {\n      frameCopy = {\n        ...frame,\n        fields: frame.fields.map((f, fieldIndex) => {\n          const copy = { ...f };\n          const origin = {\n            frameIndex: 0,\n            fieldIndex,\n          };\n          if (copy.state) {\n            copy.state.origin = origin;\n          } else {\n            copy.state = { origin };\n          }\n          return copy;\n        }),\n      };\n\n      // Make sure the join field is first\n      if (joinIndex > 0) {\n        const joinField = frameCopy.fields[joinIndex];\n        const fields = frameCopy.fields.filter((f, idx) => idx !== joinIndex);\n        fields.unshift(joinField);\n        frameCopy.fields = fields;\n        joinIndex = 0;\n      }\n    }\n\n    if (joinIndex >= 0) {\n      frameCopy = maybeSortFrame(frameCopy, joinIndex);\n    }\n\n    if (options.keep) {\n      let fields = frameCopy.fields.filter(\n        (f, fieldIdx) => fieldIdx === joinIndex || options.keep!(f, frameCopy, options.frames)\n      );\n\n      // mutate already copied frame\n      if (frame !== frameCopy) {\n        frameCopy.fields = fields;\n      } else {\n        frameCopy = {\n          ...frame,\n          fields,\n        };\n      }\n    }\n\n    return frameCopy;\n  }\n\n  const nullModes: JoinNullMode[][] = [];\n  const allData: AlignedData[] = [];\n  const originalFields: Field[] = [];\n  const joinFieldMatcher = getJoinMatcher(options);\n\n  for (let frameIndex = 0; frameIndex < options.frames.length; frameIndex++) {\n    const frame = options.frames[frameIndex];\n\n    if (!frame || !frame.fields?.length) {\n      continue; // skip the frame\n    }\n\n    const nullModesFrame: JoinNullMode[] = [NULL_REMOVE];\n    let join: Field | undefined = undefined;\n    let fields: Field[] = [];\n\n    for (let fieldIndex = 0; fieldIndex < frame.fields.length; fieldIndex++) {\n      const field = frame.fields[fieldIndex];\n      field.state = field.state || {};\n\n      if (!join && joinFieldMatcher(field, frame, options.frames)) {\n        join = field;\n      } else {\n        if (options.keep && !options.keep(field, frame, options.frames)) {\n          continue; // skip field\n        }\n\n        // Support the standard graph span nulls field config\n        nullModesFrame.push(nullMode(field));\n\n        let labels = field.labels ?? {};\n        let name = field.name;\n        if (frame.name) {\n          if (field.name === TIME_SERIES_VALUE_FIELD_NAME) {\n            name = frame.name;\n          } else if (labels.name == null) {\n            // add the name label from frame\n            labels = { ...labels, name: frame.name };\n          }\n        }\n\n        fields.push({\n          ...field,\n          name,\n          labels,\n        });\n      }\n\n      if (options.keepOriginIndices) {\n        field.state.origin = {\n          frameIndex,\n          fieldIndex,\n        };\n      }\n    }\n\n    if (!join) {\n      continue; // skip the frame\n    }\n\n    if (originalFields.length === 0) {\n      originalFields.push(join); // first join field\n    }\n\n    nullModes.push(nullModesFrame);\n    const a: AlignedData = [join.values]; //\n\n    for (const field of fields) {\n      a.push(field.values);\n      originalFields.push(field);\n      if (!options.keepDisplayNames) {\n        // clear field displayName state\n        delete field.state?.displayName;\n      }\n    }\n    allData.push(a);\n  }\n\n  let joined: Array<Array<number | string | null | undefined>> = [];\n\n  if (options.mode === JoinMode.outerTabular) {\n    joined = joinTabular(allData, true);\n  } else if (options.mode === JoinMode.inner) {\n    joined = joinTabular(allData);\n  } else {\n    joined = join(allData, nullModes, options.mode);\n  }\n\n  return {\n    // ...options.data[0], // keep name, meta?\n    length: joined[0] ? joined[0].length : 0,\n    fields: originalFields.map((f, index) => ({\n      ...f,\n      values: joined[index],\n    })),\n  };\n}\n\n/**\n * SQL-style join of tables, using the first column in each\n */\nfunction joinTabular(tables: AlignedData[], outer = false) {\n  // console.time('joinTabular');\n\n  let ltable = tables[0];\n  let lfield = ltable[0];\n\n  // iterate tables, merging right table with left, with the result becoming the new left\n  // rinse and repeat for each tables in the array\n  for (let ti = 1; ti < tables.length; ti++) {\n    let rtable = tables[ti];\n    let rfield = rtable[0];\n\n    /**\n     * Build an inverted index of the right table's join column like { \"foo\": [1,2,3], \"bar\": [7,12], ... }\n     * where the keys are unique values and the arrays are indices where these values were found\n     */\n    // console.time('index right');\n    let index: Record<string | number, number[]> = {};\n\n    for (let i = 0; i < rfield.length; i++) {\n      let val = rfield[i];\n\n      let idxs = index[val];\n\n      if (idxs == null) {\n        idxs = index[val] = [];\n      }\n\n      idxs.push(i);\n    }\n    // console.timeEnd('index right');\n\n    /**\n     * Loop over the left table's join column and match each non-null value to the right index,\n     * copying the matched ridxs array into new matched list, like [33, [45,79,233]], where first\n     * value is left idx and second value is right idxs\n     *\n     * Also keep track of unmatched or null left values for outer join, since we'll need to include these\n     */\n    let matchedKeys = new Set();\n    let unmatchedLeft = [];\n    let unmatchedRight = [];\n\n    // console.time('match left');\n    let matched: Array<[lidx: number, ridxs: number[]]> = [];\n\n    // count of total number of output rows, so we can\n    // pre-allocate the final array size during materialization\n    let count = 0;\n\n    for (let i = 0; i < lfield.length; i++) {\n      let v = lfield[i];\n\n      if (v != null) {\n        let idxs = index[v];\n\n        if (idxs != null) {\n          matched.push([i, idxs]);\n          count += idxs.length;\n          outer && matchedKeys.add(v);\n        } else if (outer) {\n          unmatchedLeft.push(i);\n        }\n      } else if (outer) {\n        unmatchedLeft.push(i);\n      }\n    }\n    count += unmatchedLeft.length;\n    // console.timeEnd('match left');\n\n    /**\n     * For outer joins, also loop over the right index to record unmatched values\n     */\n    // console.time('unmatched right');\n    if (outer) {\n      for (let k in index) {\n        if (!matchedKeys.has(k)) {\n          unmatchedRight.push(...index[k]);\n        }\n      }\n      count += unmatchedRight.length;\n    }\n    // console.timeEnd('unmatched right');\n\n    /**\n     * Now we can use matched, unmatchedLeft, unmatchedRight, ltable, and rtable to assemble the final table\n     * Instead of using 3-deep nested loops, we eliminate the loops over the known column structure\n     * For this we compile a new function using the schemas from both tables, and filling that struct by looping\n     * over the matched lookup array, then appending the unmatched left rows (and null-filling the right values),\n     * then appending the unmatched right rows (and null-filling the left values).\n     *\n     * The assembled function looks something like this when joining 2-col left + 2-col right:\n     *\n     * function anonymous(matched, unmatchedLeft, unmatchedRight, ltable, rtable) {\n     *   const joined = [Array(99991),Array(99991),Array(99991)];\n     *\n     *   let rowIdx = 0;\n     *\n     *   for (let i = 0; i < matched.length; i++) {\n     *     let [lidx, ridxs] = matched[i];\n     *\n     *     for (let j = 0; j < ridxs.length; j++, rowIdx++) {\n     *       let ridx = ridxs[j];\n     *       joined[0][rowIdx] = ltable[0][lidx];\n     *       joined[1][rowIdx] = ltable[1][lidx];\n     *       joined[2][rowIdx] = rtable[1][ridx];\n     *     }\n     *   }\n     *\n     *   for (let i = 0; i < unmatchedLeft.length; i++, rowIdx++) {\n     *     let lidx = unmatchedLeft[i];\n     *     joined[0][rowIdx] = ltable[0][lidx];\n     *     joined[1][rowIdx] = ltable[1][lidx];\n     *     joined[2][rowIdx] = null;\n     *   }\n     *\n     *   for (let i = 0; i < unmatchedRight.length; i++, rowIdx++) {\n     *     let ridx = unmatchedRight[i];\n     *     joined[0][rowIdx] = rtable[0][ridx];\n     *     joined[1][rowIdx] = null;\n     *     joined[2][rowIdx] = rtable[1][ridx];\n     *   }\n     *\n     *   return joined;\n     * }\n     */\n    // console.time('materialize');\n    let outFieldsTpl = Array.from({ length: ltable.length + rtable.length - 1 }, () => `Array(${count})`).join(',');\n    let copyLeftRowTpl = ltable.map((c, i) => `joined[${i}][rowIdx] = ltable[${i}][lidx]`).join(';');\n    // (skips join field in right table)\n    let copyRightRowTpl = rtable\n      .slice(1)\n      .map((c, i) => `joined[${ltable.length + i}][rowIdx] = rtable[${i + 1}][ridx]`)\n      .join(';');\n\n    // for outer joins, when we null-fill the left row values, we still populate the first (join) column\n    // with the right row's join column value, rather than omitting it as we do for matched left/right where\n    // that value is already filled by the left row\n    let nullLeftRowTpl = ltable\n      .map((c, i) => `joined[${i}][rowIdx] = ${i === 0 ? `rtable[${i}][ridx]` : `null`}`)\n      .join(';');\n    // (skips join field in right table)\n    let nullRightRowTpl = rtable.slice(1).map((c, i) => `joined[${ltable.length + i}][rowIdx] = null`);\n\n    let materialize = new Function(\n      'matched',\n      'unmatchedLeft',\n      'unmatchedRight',\n      'ltable',\n      'rtable',\n      `\n      const joined = [${outFieldsTpl}];\n\n      let rowIdx = 0;\n\n      for (let i = 0; i < matched.length; i++) {\n        let [lidx, ridxs] = matched[i];\n\n        for (let j = 0; j < ridxs.length; j++, rowIdx++) {\n          let ridx = ridxs[j];\n          ${copyLeftRowTpl};\n          ${copyRightRowTpl};\n        }\n      }\n\n      for (let i = 0; i < unmatchedLeft.length; i++, rowIdx++) {\n        let lidx = unmatchedLeft[i];\n        ${copyLeftRowTpl};\n        ${nullRightRowTpl};\n      }\n\n      for (let i = 0; i < unmatchedRight.length; i++, rowIdx++) {\n        let ridx = unmatchedRight[i];\n        ${nullLeftRowTpl};\n        ${copyRightRowTpl};\n      }\n\n      return joined;\n      `\n    );\n\n    let joined = materialize(matched, unmatchedLeft, unmatchedRight, ltable, rtable);\n    // console.timeEnd('materialize');\n\n    ltable = joined;\n    lfield = ltable[0];\n  }\n\n  // console.timeEnd('joinTabular');\n\n  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n  return ltable as Array<Array<string | number | null | undefined>>;\n}\n\n//--------------------------------------------------------------------------------\n// Below here is copied from uplot (MIT License)\n// https://github.com/leeoniya/uPlot/blob/master/src/utils.js#L325\n// This avoids needing to import uplot into the data package\n//--------------------------------------------------------------------------------\n\n// Copied from uplot\nexport type TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Uint8ClampedArray\n  | Float32Array\n  | Float64Array;\n\nexport type AlignedData =\n  | TypedArray[]\n  | [xValues: number[] | TypedArray, ...yValues: Array<Array<number | null | undefined> | TypedArray>];\n\n// nullModes\nexport const NULL_REMOVE = 0; // nulls are converted to undefined (e.g. for spanGaps: true)\nexport const NULL_RETAIN = 1; // nulls are retained, with alignment artifacts set to undefined (default)\nexport const NULL_EXPAND = 2; // nulls are expanded to include any adjacent alignment artifacts\n\ntype JoinNullMode = number; // NULL_IGNORE | NULL_RETAIN | NULL_EXPAND;\n\n// sets undefined values to nulls when adjacent to existing nulls (minesweeper)\nfunction nullExpand(yVals: Array<number | null>, nullIdxs: number[], alignedLen: number) {\n  for (let i = 0, xi, lastNullIdx = -1; i < nullIdxs.length; i++) {\n    let nullIdx = nullIdxs[i];\n\n    if (nullIdx > lastNullIdx) {\n      xi = nullIdx - 1;\n      while (xi >= 0 && yVals[xi] == null) {\n        yVals[xi--] = null;\n      }\n\n      xi = nullIdx + 1;\n      while (xi < alignedLen && yVals[xi] == null) {\n        yVals[(lastNullIdx = xi++)] = null;\n      }\n    }\n  }\n}\n\n// nullModes is a tables-matched array indicating how to treat nulls in each series\nexport function join(tables: AlignedData[], nullModes?: number[][], mode: JoinMode = JoinMode.outer) {\n  let xVals: Set<number> = new Set();\n\n  for (let ti = 0; ti < tables.length; ti++) {\n    let t = tables[ti];\n    let xs = t[0];\n    let len = xs.length;\n\n    for (let i = 0; i < len; i++) {\n      xVals.add(xs[i]);\n    }\n  }\n\n  let data = [Array.from(xVals).sort((a, b) => a - b)];\n\n  let alignedLen = data[0].length;\n\n  let xIdxs = new Map();\n\n  for (let i = 0; i < alignedLen; i++) {\n    xIdxs.set(data[0][i], i);\n  }\n\n  for (let ti = 0; ti < tables.length; ti++) {\n    let t = tables[ti];\n    let xs = t[0];\n\n    for (let si = 1; si < t.length; si++) {\n      let ys = t[si];\n\n      let yVals = Array(alignedLen).fill(undefined);\n\n      let nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN;\n\n      let nullIdxs = [];\n\n      for (let i = 0; i < ys.length; i++) {\n        let yVal = ys[i];\n        let alignedIdx = xIdxs.get(xs[i]);\n\n        if (yVal === null) {\n          if (nullMode !== NULL_REMOVE) {\n            yVals[alignedIdx] = yVal;\n\n            if (nullMode === NULL_EXPAND) {\n              nullIdxs.push(alignedIdx);\n            }\n          }\n        } else {\n          yVals[alignedIdx] = yVal;\n        }\n      }\n\n      nullExpand(yVals, nullIdxs, alignedLen);\n\n      data.push(yVals);\n    }\n  }\n\n  return data;\n}\n\n// Test a few samples to see if the values are ascending\n// Only exported for tests\nexport function isLikelyAscendingVector(data: unknown[], samples = 50) {\n  const len = data.length;\n\n  // empty or single value\n  if (len <= 1) {\n    return true;\n  }\n\n  // skip leading & trailing nullish\n  let firstIdx = 0;\n  let lastIdx = len - 1;\n\n  while (firstIdx <= lastIdx && data[firstIdx] == null) {\n    firstIdx++;\n  }\n\n  while (lastIdx >= firstIdx && data[lastIdx] == null) {\n    lastIdx--;\n  }\n\n  // all nullish or one value surrounded by nullish\n  if (lastIdx <= firstIdx) {\n    return true;\n  }\n\n  const stride = Math.max(1, Math.floor((lastIdx - firstIdx + 1) / samples));\n\n  for (let prevVal = data[firstIdx], i = firstIdx + stride; i <= lastIdx; i += stride) {\n    const v = data[i];\n\n    if (v != null && prevVal != null) {\n      if (v <= prevVal) {\n        return false;\n      }\n\n      prevVal = v;\n    }\n  }\n\n  return true;\n}\n"],"names":["_a","joinFieldMatcher","join"],"mappings":"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"}