Row Predicate Pushdown
When scanning a table with Client.scan_table, Polars passes .filter() predicates to polars_bigquery, which compiles supported Polars expressions into a BigQuery Storage Read API row_restriction SQL filter.
Safe fallback and exact filtering
polars_bigquery always applies the original .filter() predicate in Polars after streaming batches from BigQuery. If an expression (or a branch of a conjunctive & filter) uses an unsupported operation or literal, polars_bigquery safely falls back to evaluating the unsupported portion in memory without data loss.
Supported Polars Expressions
Column and Literal Primitives
| Polars Expression |
BigQuery SQL Translation |
Notes |
pl.col("name") |
`name` |
Supports standard and flexible BigQuery column names, as well as _PARTITIONDATE and _PARTITIONTIME on ingestion-time partitioned tables. |
pl.lit(value) |
SQL literal |
See Supported Polars Data Types for literal formatting and bounds. |
Comparison Operators
All comparison operators require both operands to be exact, supported expressions and neither operand to be a bare Null literal (use .is_null() or .is_not_null() for NULL checks).
| Polars Expression |
Operator / Method |
BigQuery SQL Translation |
a == b |
Expr.eq |
(a=b) |
a != b |
Expr.ne |
(a!=b) |
a > b |
Expr.gt |
(a>b) |
a >= b |
Expr.ge |
(a>=b) |
a < b |
Expr.lt |
(a<b) |
a <= b |
Expr.le |
(a<=b) |
Boolean and Logical Operators
| Polars Expression |
Operator / Method |
BigQuery SQL Translation |
Notes |
a & b |
And (&) |
(aANDb) |
Supports partial pushdown: if one branch cannot be pushed down, the supported branch is still pushed down to BigQuery. |
pl.all_horizontal(a, b, ...) |
all_horizontal |
((aANDb) ...) |
Folded into left-associative AND expressions; supports partial pushdown. |
a \| b |
Or (\|) |
(aORb) |
Pushed down when both branches are supported and exact. |
~a |
Expr.not_ (~) |
(NOTa) |
Pushed down when the inner expression is exact. Negated disjunctions ~(a \| b) are automatically rewritten via De Morgan's Law into (~a) & (~b) so supported branches can still be pushed down. |
pl.when(p).then(t).otherwise(f) |
when / then / otherwise |
Boolean conditionals (AND / OR / NOT COALESCE(..., FALSE)), IF(p,t,f), or CASE WHENp1THENt1WHENp2THENt2... [ELSEf] END |
When used as a boolean filter (or inside & / \| or when predicates and boolean branches), falsy branches (False or NULL) and partial pushdown expressions are rewritten into null-safe boolean conditionals ((pANDt) or ((NOT COALESCE(p, FALSE)) ANDf)), supporting partial pushdown. Supported, exact when().then().otherwise() expressions compile to IF(...) or CASE WHEN ... END. Supports multi-predicate pl.when(p1, p2, ...). Requires no NULL predicates and at least one non-NULL result branch. |
Null Checks and Handling
| Polars Expression |
BigQuery SQL Translation |
Notes |
expr.is_null() |
(exprIS NULL) |
— |
expr.is_not_null() |
(exprIS NOT NULL) |
— |
expr.drop_nulls() |
... AND (exprIS NOT NULL) |
Replaces expr.drop_nulls() with expr and conjoins (exprIS NOT NULL) to the top-level row restriction when expr is supported, exact, and non-Null. |
pl.coalesce(a, b, ...) |
COALESCE(a,b, ...) |
Pushed down when all operands are supported, exact, and non-Null. |
expr.fill_null(value) |
IFNULL(expr,value) |
Pushed down when both expr and value are supported, exact, and non-Null. |
expr.fill_null(strategy="zero" \| "one") |
IFNULL(expr, 0) or IFNULL(expr, 1) |
Analytical and aggregate strategies ("forward", "backward", "min", "max", "mean") are not pushed down. |
Numeric Predicates
| Polars Expression |
BigQuery SQL Translation |
expr.is_nan() |
IS_NAN(expr) |
expr.is_not_nan() |
(NOT IS_NAN(expr)) |
expr.is_infinite() |
IS_INF(expr) |
expr.is_finite() |
(NOT IS_INF(expr) AND NOT IS_NAN(expr)) |
String Expressions (Expr.str)
| Polars Expression |
BigQuery SQL Translation |
Notes |
expr.str.starts_with(prefix) |
STARTS_WITH(expr,prefix) |
prefix can be a string literal or column expression. |
expr.str.ends_with(suffix) |
ENDS_WITH(expr,suffix) |
suffix can be a string literal or column expression. |
expr.str.contains(pattern, literal=False, strict=True) |
REGEXP_CONTAINS(expr,pattern) |
Default regex mode (literal=False, strict=True). strict=False with literal=False is not pushed down. |
expr.str.contains(pattern, literal=True) |
(STRPOS(expr,pattern) > 0) |
Literal substring match. |
expr.str.to_uppercase() |
UPPER(expr) |
Can be composed with comparisons or other string predicates. |
expr.str.to_lowercase() |
LOWER(expr) |
Can be composed with comparisons or other string predicates. |
Set Membership (Expr.is_in)
| Polars Expression |
BigQuery SQL Translation |
Notes |
expr.is_in(values, nulls_equal=False) |
(exprIN (v1, v2, ...)) |
values must be a non-empty homogeneous Python list or pl.Series of up to 10,000 non-null, non-NaN scalar literals. nulls_equal=True is not pushed down. |
Supported Polars Data Types
Literal and Predicate Data Types
The following Polars data types are supported as scalar literals in comparisons and as elements of list / pl.Series collections in Expr.is_in:
| Polars Data Type |
Python Type |
BigQuery SQL Representation |
Constraints & Bounds |
pl.Boolean |
bool |
TRUE, FALSE |
— |
pl.Int8, pl.Int16, pl.Int32, pl.Int64, pl.Int128 |
int |
INT64 integer literal |
Value must fit within signed 64-bit integer bounds (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807). |
pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64 |
int |
INT64 integer literal |
Value must fit within signed 64-bit integer bounds (0 to 9,223,372,036,854,775,807). |
pl.Float32, pl.Float64 |
float |
FLOAT64 literal |
Finite floats are pushed down in comparisons and is_in. For NaN or ±Inf checks, use .is_nan(), .is_not_nan(), .is_infinite(), or .is_finite(). |
pl.String |
str |
Single-quoted '...' |
Special characters (\, ', \n, \r, \t, \0) are automatically escaped. |
pl.Date |
datetime.date |
DATE(TIMESTAMP_SECONDS(days * 86400)) |
Must be within BigQuery DATE bounds (0001-01-01 to 9999-12-31). |
pl.Datetime(time_unit, time_zone=...) (timezone-aware) |
datetime.datetime (with tzinfo) |
TIMESTAMP_MICROS(...) or TIMESTAMP_MILLIS(...) |
Compiles to a BigQuery TIMESTAMP literal. Supports "ms", "us", and "ns" (when divisible by 1,000 for exact microsecond precision) within 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999Z. |
pl.Datetime(time_unit, time_zone=None) (timezone-naive) |
datetime.datetime (without tzinfo) |
DATETIME(TIMESTAMP_MICROS(...)) or DATETIME(TIMESTAMP_MILLIS(...)) |
Compiles to a BigQuery DATETIME literal. Supports "ms", "us", and "ns" (when divisible by 1,000 for exact microsecond precision) within 0001-01-01T00:00:00 to 9999-12-31T23:59:59.999999. |
pl.List / pl.Series |
list / pl.Series |
(v1, v2, ...) |
Supported as the right-hand side of Expr.is_in(). Must be non-empty, homogeneous, free of Null and NaN values, and contain at most 10,000 elements (and at most 1 MiB when serialized as an Arrow IPC Series). In Datetime lists, all elements must be consistently timezone-aware or timezone-naive. |
BigQuery-to-Polars Table Schema Mapping
When Client.scan_table inspects a BigQuery table's metadata (or when you pass an explicit schema to Client.scan_table), BigQuery column types correspond to the following Polars data types:
| BigQuery Type |
Polars Data Type |
BOOL / BOOLEAN |
pl.Boolean |
INT64 / INTEGER |
pl.Int64 |
FLOAT64 / FLOAT |
pl.Float64 |
NUMERIC / DECIMAL |
pl.Decimal(precision=38, scale=9) |
BIGNUMERIC / BIGDECIMAL |
pl.Decimal(precision=76, scale=38) |
STRING |
pl.String |
BYTES |
pl.Binary |
DATE |
pl.Date |
DATETIME |
pl.Datetime(time_unit="us", time_zone=None) |
TIME |
pl.Time |
TIMESTAMP |
pl.Datetime(time_unit="us", time_zone="utc") |
INTERVAL |
pl.Duration(time_unit="us") |
JSON |
pl.String |
GEOGRAPHY |
pl.String |
STRUCT / RECORD |
pl.Struct(...) |
ARRAY (mode="REPEATED") |
pl.List(...) |
For ingestion-time partitioned tables, Client.scan_table also exposes two pseudo-columns in the LazyFrame schema that can be used in .filter() predicates for partition pruning:
_PARTITIONDATE: pl.Date
_PARTITIONTIME: pl.Datetime(time_unit="us", time_zone="utc")
Column Naming Rules and Limits
- Column identifiers: Standard and flexible BigQuery column names are quoted with backticks (
`col`). Allowed characters include Unicode letters (\p{L}), numbers (\p{N}), marks (\p{M}), underscores (\p{Pc}), hyphens/dashes (\p{Pd}), spaces, and the flexible special characters &, %, =, +, :, ', <, >, #, and |. Column names containing characters disallowed by BigQuery (such as `, \, ., $, or !) raise a ValueError.
- Expression tree size: Predicate ASTs up to 10,000 nodes (
MAX_AST_NODES = 10_000) are compiled; subtrees exceeding this budget gracefully degrade to client-side filtering while preserving shallower conjunctive (&) filters.