@@ -432,33 +432,115 @@ def _materialize(
432432 return xr .Dataset (data_vars = data_vars , coords = coords_arg )
433433
434434
435+ _PURE_SCAN_NODES = {"Projection" , "Sort" , "TableScan" , "SubqueryAlias" }
436+
437+
438+ def _unfiltered_scan_table (inner_df : Any ) -> str | None :
439+ """Return the scanned table name iff the query is a pure unfiltered scan.
440+
441+ A pure scan only contains ``Projection``, ``Sort``, ``TableScan``,
442+ ``SubqueryAlias`` nodes and exactly one ``TableScan``. Anything else
443+ (``Filter``, ``Aggregate``, ``Join``, ``Union``, ``Limit``, multi-table
444+ joins, ...) returns ``None`` so the caller falls back to per-dim
445+ discovery. The returned name is the registered table the caller can
446+ look up to source coord arrays from.
447+ """
448+ try :
449+ lp = inner_df .logical_plan ()
450+ except Exception :
451+ return None
452+ table_name : str | None = None
453+ stack = [lp ]
454+ while stack :
455+ node = stack .pop ()
456+ try :
457+ variant = node .to_variant ()
458+ except Exception :
459+ return None
460+ cls = type (variant ).__name__
461+ if cls not in _PURE_SCAN_NODES :
462+ return None
463+ if cls == "TableScan" :
464+ try :
465+ this = variant .table_name ()
466+ except (AttributeError , TypeError ):
467+ return None
468+ if not isinstance (this , str ):
469+ return None
470+ if table_name is not None and table_name != this :
471+ return None # multi-table scan; not a single source
472+ table_name = this
473+ stack .extend (node .inputs ())
474+ return table_name
475+
476+
477+ def _maybe_template_coords (
478+ templates : dict [str , xr .Dataset ] | None ,
479+ dimension_columns : list [str ],
480+ inner_df : Any ,
481+ ) -> dict [str , np .ndarray ] | None :
482+ """Use the scanned table's registered coord arrays directly when safe.
483+
484+ Returns coord arrays sourced from the registered Dataset for the
485+ scanned table iff the query is an unfiltered scan over that single
486+ table and the registered Dataset carries all requested dims. Returns
487+ ``None`` otherwise so the caller falls back to per-dim discovery.
488+ Skipping discovery avoids one full plan execution per dim and
489+ preserves the source's coordinate order (xarray-sql#171).
490+
491+ Coord values come from the **scanned** registered Dataset, not from
492+ any user-supplied ``template=`` (which is for metadata recovery
493+ only). That keeps the fast path correct when a user with multiple
494+ registered Datasets passes a metadata template that differs from
495+ the query's source.
496+ """
497+ if not templates :
498+ return None
499+ table = _unfiltered_scan_table (inner_df )
500+ if table is None or table not in templates :
501+ return None
502+ source = templates [table ]
503+ if not all (d in source .coords for d in dimension_columns ):
504+ return None
505+ return {d : np .asarray (source .coords [d ].values ) for d in dimension_columns }
506+
507+
435508def _build_lazy_scan (
436509 inner_df : Any ,
437510 dimension_columns : list [str ],
438511 field_names : list [str ],
439512 field_types : dict [str , Any ],
513+ templates : dict [str , xr .Dataset ] | None = None ,
440514) -> xr .Dataset :
441515 """Build a lazy Dataset whose data vars are :class:`SQLBackendArray`.
442516
443517 Used when output chunking is requested: each data variable stays lazy and,
444518 once wrapped by ``Dataset.chunk``, every chunk reads its coordinate range via
445- a pushdown filter on first access. Coordinates are discovered per dim via
519+ a pushdown filter on first access. Coordinates come either from the
520+ scanned table's registered Dataset (fast path, for unfiltered scans -- see
521+ :func:`_maybe_template_coords`) or from per-dim
446522 ``inner_df.select(col(d)).distinct().sort(...).execute_stream()``; the table
447523 provider projects to that single coordinate column and skips data variables,
448524 so discovery reads coordinate values only (no data-variable I/O).
449525 """
450- coord_arrays : dict [str , np .ndarray ] = {}
451- for d in dimension_columns :
452- dim_only = (
453- inner_df .select (col (f'"{ d } "' )).distinct ().sort (col (f'"{ d } "' ).sort ())
454- )
455- chunks = [b .to_pyarrow () for b in dim_only .execute_stream ()]
456- if not chunks :
457- coord_arrays [d ] = np .asarray ([])
458- continue
459- coord_arrays [d ] = np .concatenate (
460- [c .column (0 ).to_numpy (zero_copy_only = False ) for c in chunks ]
461- )
526+ coord_arrays = _maybe_template_coords (
527+ templates , dimension_columns , inner_df
528+ )
529+ if coord_arrays is None :
530+ coord_arrays = {}
531+ for d in dimension_columns :
532+ dim_only = (
533+ inner_df .select (col (f'"{ d } "' ))
534+ .distinct ()
535+ .sort (col (f'"{ d } "' ).sort ())
536+ )
537+ chunks = [b .to_pyarrow () for b in dim_only .execute_stream ()]
538+ if not chunks :
539+ coord_arrays [d ] = np .asarray ([])
540+ continue
541+ coord_arrays [d ] = np .concatenate (
542+ [c .column (0 ).to_numpy (zero_copy_only = False ) for c in chunks ]
543+ )
462544 shape = tuple (len (coord_arrays [d ]) for d in dimension_columns )
463545
464546 data_vars : dict [str , xr .Variable ] = {}
@@ -550,6 +632,7 @@ def _result_to_xarray(
550632 sparsity : Sparsity ,
551633 fill_value : Any ,
552634 chunks : Mapping [str , int ] | str | None ,
635+ templates : dict [str , xr .Dataset ] | None = None ,
553636) -> xr .Dataset :
554637 """Reconstruct an ``xr.Dataset`` from a SQL result.
555638
@@ -583,7 +666,11 @@ def _result_to_xarray(
583666 ds = _materialize (inner_df , dimension_columns , field_names , field_types )
584667 else :
585668 ds = _build_lazy_scan (
586- inner_df , dimension_columns , field_names , field_types
669+ inner_df ,
670+ dimension_columns ,
671+ field_names ,
672+ field_types ,
673+ templates = templates ,
587674 )
588675
589676 if sparsity == "template" :
@@ -730,6 +817,7 @@ def to_dataset(
730817 sparsity = sparsity ,
731818 fill_value = fill_value ,
732819 chunks = resolved_chunks ,
820+ templates = self ._templates ,
733821 )
734822
735823 # ------------------------------------------------------------------
0 commit comments