<commit_before>import Long from "long";
import { PromisePoolExecutor } from "promise-pool-executor";

import {
  BigQuery,
  GetTablesResponse,
  Table,
  TableField,
  TableMetadata
} from "@google-cloud/bigquery";
import { Credentials } from "df/api/commands/credentials";
import { IDbAdapter, IDbClient, IExecutionResult, OnCancel } from "df/api/dbadapters/index";
import { parseBigqueryEvalError } from "df/api/utils/error_parsing";
import { LimitedResultSet } from "df/api/utils/results";
import { coerceAsError } from "df/common/errors/errors";
import { retry } from "df/common/promises";
import { collectEvaluationQueries, QueryOrAction } from "df/core/adapters";
import { dataform } from "df/protos/ts";

const EXTRA_GOOGLE_SCOPES = ["https://www.googleapis.com/auth/drive"];

const BIGQUERY_DATE_RELATED_FIELDS = [
  "BigQueryDate",
  "BigQueryTime",
  "BigQueryTimestamp",
  "BigQueryDatetime"
];

const BIGQUERY_INTERNAL_ERROR_JOB_MAX_ATTEMPTS = 3;

export class BigQueryDbAdapter implements IDbAdapter {
  public static async create(credentials: Credentials, options?: { concurrencyLimit?: number }) {
    return new BigQueryDbAdapter(credentials, options);
  }

  private bigQueryCredentials: dataform.IBigQuery;
  private pool: PromisePoolExecutor;

  private readonly clients = new Map<string, BigQuery>();

  private constructor(credentials: Credentials, options?: { concurrencyLimit?: number }) {
    this.bigQueryCredentials = credentials as dataform.IBigQuery;
    // Bigquery allows 50 concurrent queries, and a rate limit of 100/user/second by default.
    // These limits should be safely low enough for most projects.
    this.pool = new PromisePoolExecutor({
      concurrencyLimit: options?.concurrencyLimit || 16,
      frequencyWindow: 1000,
      frequencyLimit: 30
    });
  }

  public async execute(
    statement: string,
    options: {
      params?: { [name: string]: any };
      onCancel?: OnCancel;
      interactive?: boolean;
      rowLimit?: number;
      byteLimit?: number;
      bigquery?: {
        labels?: { [label: string]: string };
        location?: string;
      };
    } = { interactive: false, rowLimit: 1000, byteLimit: 1024 * 1024 }
  ): Promise<IExecutionResult> {
    if (options?.interactive && options?.bigquery?.labels) {
      throw new Error("BigQuery job labels may not be set for interactive queries.");
    }
    return this.pool
      .addSingleTask({
        generator: () =>
          options?.interactive
            ? this.runQuery(
                statement,
                options?.params,
                options?.rowLimit,
                options?.byteLimit,
                options.bigquery?.location
              )
            : this.createQueryJob(
                statement,
                options?.params,
                options?.rowLimit,
                options?.byteLimit,
                options?.onCancel,<commit_msg>Adds error handling when query statement is empty (#1225)<commit_after>import Long from "long";
import { PromisePoolExecutor } from "promise-pool-executor";

import {
  BigQuery,
  GetTablesResponse,
  Table,
  TableField,
  TableMetadata
} from "@google-cloud/bigquery";
import { Credentials } from "df/api/commands/credentials";
import { IDbAdapter, IDbClient, IExecutionResult, OnCancel } from "df/api/dbadapters/index";
import { parseBigqueryEvalError } from "df/api/utils/error_parsing";
import { LimitedResultSet } from "df/api/utils/results";
import { coerceAsError } from "df/common/errors/errors";
import { retry } from "df/common/promises";
import { collectEvaluationQueries, QueryOrAction } from "df/core/adapters";
import { dataform } from "df/protos/ts";

const EXTRA_GOOGLE_SCOPES = ["https://www.googleapis.com/auth/drive"];

const BIGQUERY_DATE_RELATED_FIELDS = [
  "BigQueryDate",
  "BigQueryTime",
  "BigQueryTimestamp",
  "BigQueryDatetime"
];

const BIGQUERY_INTERNAL_ERROR_JOB_MAX_ATTEMPTS = 3;

export class BigQueryDbAdapter implements IDbAdapter {
  public static async create(credentials: Credentials, options?: { concurrencyLimit?: number }) {
    return new BigQueryDbAdapter(credentials, options);
  }

  private bigQueryCredentials: dataform.IBigQuery;
  private pool: PromisePoolExecutor;

  private readonly clients = new Map<string, BigQuery>();

  private constructor(credentials: Credentials, options?: { concurrencyLimit?: number }) {
    this.bigQueryCredentials = credentials as dataform.IBigQuery;
    // Bigquery allows 50 concurrent queries, and a rate limit of 100/user/second by default.
    // These limits should be safely low enough for most projects.
    this.pool = new PromisePoolExecutor({
      concurrencyLimit: options?.concurrencyLimit || 16,
      frequencyWindow: 1000,
      frequencyLimit: 30
    });
  }

  public async execute(
    statement: string,
    options: {
      params?: { [name: string]: any };
      onCancel?: OnCancel;
      interactive?: boolean;
      rowLimit?: number;
      byteLimit?: number;
      bigquery?: {
        labels?: { [label: string]: string };
        location?: string;
      };
    } = { interactive: false, rowLimit: 1000, byteLimit: 1024 * 1024 }
  ): Promise<IExecutionResult> {
    if (options?.interactive && options?.bigquery?.labels) {
      throw new Error("BigQuery job labels may not be set for interactive queries.");
    }

    if (!statement) {
      throw new Error("Query string cannot be empty");
    }
    return this.pool
      .addSingleTask({
        generator: () =>
          options?.interactive
            ? this.runQuery(
                statement,
                options?.params,
                options?.rowLimit,
                options?.byteLimit,
                options.bigquery?.location
              )
            : this.createQueryJob(
                statement,
                options?.params,
                options?.rowLimit,
                options?.byteLimit,
                options?.onCancel,
{'fraction_non_alphanumeric': 0.1066015493432132, 'fraction_numerical': 0.009430784776018861, 'mean_word_length': 3.015551048005409, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '89275', 'n_tokens_mistral': 1727, 'n_tokens_neox': 1621, 'n_words': 419}