Repository: apache/superset
File: superset/db_engine_specs/bigquery.py (Python)

Code before:
        ],
        "notes": (
            "Create a Service Account via GCP console with access to "
            "BigQuery datasets. For CSV/Excel uploads, also install pandas_gbq."
        ),
        "warnings": [
            "Google BigQuery Python SDK is not compatible with gevent. "
            "Use a worker type other than gevent when deploying with gunicorn.",
        ],
        "docs_url": "https://github.com/googleapis/python-bigquery-sqlalchemy",
    }

    # BigQuery doesn't maintain context when running multiple statements in the
    # same cursor, so we need to run all statements at once
    run_multiple_statements_as_one = True

    allows_hidden_cc_in_orderby = True

    supports_catalog = supports_dynamic_catalog = supports_cross_catalog_queries = True

    # when editing the database, mask this field in `encrypted_extra`
    # pylint: disable=invalid-name
    encrypted_extra_sensitive_fields = {"$.credentials_info.private_key"}
    encrypted_extra_sensitive_field_labels = {
        "$.credentials_info.private_key": "Service Account Private Key",
    }

    """
    https://www.python.org/dev/peps/pep-0249/#arraysize
    raw_connections bypass the sqlalchemy-bigquery query execution context and deal with
    raw dbapi connection directly.
    If this value is not set, the default value is set to 1, as described here,
    https://googlecloudplatform.github.io/google-cloud-python/latest/_modules/google/cloud/bigquery/dbapi/cursor.html#Cursor

    The default value of 5000 is derived from the sqlalchemy-bigquery.
    https://github.com/googleapis/python-bigquery-sqlalchemy/blob/4e17259088f89eac155adc19e0985278a29ecf9c/sqlalchemy_bigquery/base.py#L762
    """
    arraysize = 5000

    _date_trunc_functions = {
        "DATE": "DATE_TRUNC",
        "DATETIME": "DATETIME_TRUNC",
        "TIME": "TIME_TRUNC",
        "TIMESTAMP": "TIMESTAMP_TRUNC",
    }

    _time_grain_expressions = {
        None: "{col}",
        TimeGrain.SECOND: "CAST(TIMESTAMP_SECONDS("
        "UNIX_SECONDS(CAST({col} AS TIMESTAMP))"
        ") AS {type})",

Review comment (security):
I think it would be better to just make `encrypted_extra_sensitive_fields: set[str] | dict[str, str]` for now, and where we consume it we do:

```python
fields = (
    {field: field for field in encrypted_extra_sensitive_fields}
    if isinstance(encrypted_extra_sensitive_fields, set)
    else encrypted_extra_sensitive_fields
)
```

Then in 7.0 we make `encrypted_extra_sensitive_fields: dict[str, str]` only.

Otherwise we need to keep `encrypted_extra_sensitive_fields` and `encrypted_extra_sensitive_field_labels` in sync, ensuring they have the same keys.

We could also be fancy and try to autogenerate a label when `encrypted_extra_sensitive_fields` is a set, like:

```python
def generate_field_label(key: str) -> str:
    return " ".join(part.title() for part in key[2:].replace("_", " ").split("."))
```

But it might not be worth the trouble.

Code after:
        ],
        "notes": (
            "Create a Service Account via GCP console with access to "
            "BigQuery datasets. For CSV/Excel uploads, also install pandas_gbq."
        ),
        "warnings": [
            "Google BigQuery Python SDK is not compatible with gevent. "
            "Use a worker type other than gevent when deploying with gunicorn.",
        ],
        "docs_url": "https://github.com/googleapis/python-bigquery-sqlalchemy",
    }

    # BigQuery doesn't maintain context when running multiple statements in the
    # same cursor, so we need to run all statements at once
    run_multiple_statements_as_one = True

    allows_hidden_cc_in_orderby = True

    supports_catalog = supports_dynamic_catalog = supports_cross_catalog_queries = True

    # when editing the database, mask this field in `encrypted_extra`
    # pylint: disable=invalid-name
    encrypted_extra_sensitive_fields = {
        "$.credentials_info.private_key": "Service Account Private Key",
    }

    """
    https://www.python.org/dev/peps/pep-0249/#arraysize
    raw_connections bypass the sqlalchemy-bigquery query execution context and deal with
    raw dbapi connection directly.
    If this value is not set, the default value is set to 1, as described here,
    https://googlecloudplatform.github.io/google-cloud-python/latest/_modules/google/cloud/bigquery/dbapi/cursor.html#Cursor

    The default value of 5000 is derived from the sqlalchemy-bigquery.
    https://github.com/googleapis/python-bigquery-sqlalchemy/blob/4e17259088f89eac155adc19e0985278a29ecf9c/sqlalchemy_bigquery/base.py#L762
    """
    arraysize = 5000

    _date_trunc_functions = {
        "DATE": "DATE_TRUNC",
        "DATETIME": "DATETIME_TRUNC",
        "TIME": "TIME_TRUNC",
        "TIMESTAMP": "TIMESTAMP_TRUNC",
    }

    _time_grain_expressions = {
        None: "{col}",
        TimeGrain.SECOND: "CAST(TIMESTAMP_SECONDS("
        "UNIX_SECONDS(CAST({col} AS TIMESTAMP))"
        ") AS {type})",
        TimeGrain.MINUTE: "CAST(TIMESTAMP_SECONDS("