From 194cd32b5d766d60e3ca442651d792c7fe54ea74 Mon Sep 17 00:00:00 2001 From: ZetaSQL Team Date: Wed, 21 Aug 2024 13:45:14 -0700 Subject: [PATCH] Export of internal ZetaSQL changes. -- Change by ZetaSQL Team : Updated the instructions for running execute_query with docker on MacOS with M1/M2 chips. -- Change by ZetaSQL Team : Add a note about MacOS users seeing the error `execute_query_macos cannot be opened because the developer cannot be verified.` -- Change by ZetaSQL Team : Refactoring in preparation for UPDATE constructor. -- Change by ZetaSQL Team : Change the ZetaSQL Dockerfile to support different build modes. -- Change by Jeff Shute : Add tests that check that a sql file runs successfully in execute_query. -- Change by ZetaSQL Team : add a new TO_JSON signature that supports arg `unsupported_fiels`. -- Change by Jeff Shute : Add some more example queries in examples/pipe_queries. -- Change by Brandon Dolphin : Begin adding Measure type to TypeProto. -- Change by ZetaSQL Team : Add per column OPTIONS and WITH COLUMN OPTIONS to analyzer. -- Change by ZetaSQL Team : Handle lambda functions directly in the BuiltinFunctionRegistry scalar function APIs. -- Change by ZetaSQL Team : Add per column OPTIONS and WITH COLUMN OPTIONS to analyzer. -- Change by ZetaSQL Team : Add optional_ref library. -- Change by John Fremlin : Add a testcase for deeply nested structs and arrays in JSON -- Change by ZetaSQL Team : Update the ZetaSQL documentation: -- Change by John Fremlin : Truncate output for deeply nested array expressions in unparser -- Change by ZetaSQL Team : Update pipe syntax docs with TW peer review edits -- Change by Jeff Shute : Fix execute_query command line help. -- Change by ZetaSQL Team : add a new named arg `unsupported_fiels` for the TO_JSON function. -- Change by John Fremlin : Truncate output for deeply nested CASE expressions in unparser -- Change by ZetaSQL Team : Add MAP_REPLACE signatures, and reference implementation for KV pairs version -- Change by ZetaSQL Team : Remove unnecessarily explicit function registrations from reference_impl/function.cc -- Change by Jeff Shute : Adjust text area size so results are more visible. -- Change by ZetaSQL Team : Disable formatting of SQL inside non-multiline string literals. -- Change by ZetaSQL Team : Fixed issue with formatting SQL inside string literals when input string contains \r\n line endings. -- Change by ZetaSQL Team : Format textproto inside annotated string literal. -- Change by ZetaSQL Team : Disambiguate between open and close brackets annotations for braced constructor syntax. -- Change by Jeff Shute : Improve multi-statement output in execute_query web. -- Change by ZetaSQL Team : add a new named arg `unsupported_fiels` for the TO_JSON function. -- Change by ZetaSQL Team : add a new built-in enum `UnsupportedFields` to be used by TO_JSON. -- Change by ZetaSQL Team : Record parse location for OrderByItem iff record type is not PARSE_LOCATION_RECORD_NONE. -- Change by ZetaSQL Team : Unify Lambda and non-lambda AlgebrizeFunctionCall codepaths -- Change by ZetaSQL Team : small formatting updates for named arguments -- Change by ZetaSQL Team : Fix the example Docker image name in the ZetaSQL doc. -- Change by ZetaSQL Team : Refactor the parse AST and the grammar to use postfix table operators (e.g. TABLESAMPLE) on ASTTableExpression. -- Change by ZetaSQL Team : Fix ZetaSQL documentation. GitOrigin-RevId: a68e25b308dadf3e78c4d22ec41adf72f8b08e5b Change-Id: I586b6974dbdb4e2bb4c99ba641ef96916ec33ba6 --- Dockerfile | 18 +- README.md | 66 +- docker_build.sh | 40 + docs/README.md | 17 +- docs/aggregate-dp-functions.md | 45 +- docs/aggregate-function-calls.md | 8 +- docs/array_functions.md | 18 +- docs/data-definition-language.md | 23 +- docs/functions-and-operators.md | 211 ++- docs/geography_functions.md | 34 +- docs/interval_functions.md | 9 +- docs/json_functions.md | 83 +- docs/pipe-syntax.md | 445 +++-- docs/protocol_buffer_functions.md | 12 +- docs/string_functions.md | 10 +- docs/user-defined-aggregates.md | 2 +- docs/user-defined-functions.md | 19 +- execute_query.md | 8 +- .../google/zetasql/BuiltinDescriptorPool.java | 2 +- javatests/com/google/zetasql/BUILD | 2 +- zetasql/analyzer/resolver.h | 130 +- zetasql/analyzer/resolver_dml.cc | 3 +- zetasql/analyzer/resolver_expr.cc | 286 ++- zetasql/analyzer/resolver_query.cc | 258 ++- zetasql/analyzer/rewrite_resolved_ast_test.cc | 1 - zetasql/analyzer/testdata/aggregation.test | 1662 ++--------------- .../analyzer/testdata/alias_ambiguity.test | 251 +-- .../analytic_function_named_window.test | 2 - ...analytic_function_partitionby_orderby.test | 4 - .../analyzer/testdata/analytic_functions.test | 16 - zetasql/analyzer/testdata/anonymization.test | 6 - .../testdata/anonymization_subquery.test | 2 - .../analyzer/testdata/array_aggregate.test | 18 - .../testdata/array_concat_aggregate.test | 12 - zetasql/analyzer/testdata/array_element.test | 2 - zetasql/analyzer/testdata/array_filter.test | 23 - .../analyzer/testdata/array_functions.test | 15 - zetasql/analyzer/testdata/array_path.test | 5 - .../analyzer/testdata/array_transform.test | 16 - .../testdata/binary_rewriter_functions.test | 4 - zetasql/analyzer/testdata/collation.test | 13 - .../testdata/correlated_aggr_subquery.test | 2 - .../testdata/correlated_expr_subquery.test | 12 - .../correlated_subquery_outer_aggr.test | 126 -- ...correlated_subquery_outer_aggr_struct.test | 201 -- .../testdata/differential_privacy.test | 2 - .../differential_privacy_subquery.test | 2 - zetasql/analyzer/testdata/dml_insert.test | 1 - zetasql/analyzer/testdata/expr_subquery.test | 7 - zetasql/analyzer/testdata/hints.test | 4 - zetasql/analyzer/testdata/json.test | 48 +- zetasql/analyzer/testdata/lambda.test | 3 - zetasql/analyzer/testdata/limit.test | 2 - zetasql/analyzer/testdata/literals.test | 1 - zetasql/analyzer/testdata/map_functions.test | 332 ++++ .../analyzer/testdata/match_recognize.test | 110 ++ .../analyzer/testdata/order_by_collate.test | 34 +- .../testdata/order_by_in_aggregate.test | 501 +---- .../analyzer/testdata/order_preservation.test | 17 - zetasql/analyzer/testdata/orderby.test | 615 +----- .../testdata/pipe_aggregate_with_order.test | 34 - zetasql/analyzer/testdata/pipe_call_tvf.test | 1 - zetasql/analyzer/testdata/pipe_order_by.test | 17 - .../testdata/pipe_order_preservation.test | 17 - .../pipe_parenthesized_query_alias.test | 1 - zetasql/analyzer/testdata/pipe_query.test | 1 - zetasql/analyzer/testdata/pipe_select.test | 2 +- .../testdata/pipe_static_describe.test | 1 - zetasql/analyzer/testdata/pivot.test | 2 - .../testdata/positional_query_params.test | 3 - .../testdata/proto_braced_constructors.test | 27 +- zetasql/analyzer/testdata/query_params.test | 2 - zetasql/analyzer/testdata/safe_function.test | 2 - zetasql/analyzer/testdata/select_dotstar.test | 11 - .../testdata/sql_function_inlining.test | 2 - .../testdata/sql_table_function_inlining.test | 2 - .../sql_template_function_inlining.test | 9 - .../analyzer/testdata/string_aggregate.test | 14 - .../testdata/struct_braced_constructors.test | 3 +- zetasql/analyzer/testdata/tablesample.test | 12 +- .../testdata/ternary_rewriter_functions.test | 10 - .../analyzer/testdata/tvf_relation_args.test | 1 - .../analyzer/testdata/tvf_scalar_args.test | 1 - .../testdata/unary_rewriter_functions.test | 11 - zetasql/analyzer/testdata/unpivot.test | 10 + zetasql/analyzer/testdata/value_tables.test | 1312 +++---------- zetasql/analyzer/testdata/with_recursive.test | 3 - zetasql/base/BUILD | 29 + zetasql/base/optional_ref.h | 224 +++ zetasql/base/optional_ref_matchers.h | 39 + zetasql/base/optional_ref_test.cc | 223 +++ zetasql/common/BUILD | 1 - zetasql/common/builtin_enum_type.cc | 8 - zetasql/common/builtin_function_internal.h | 11 +- zetasql/common/builtin_function_internal_3.cc | 64 +- zetasql/common/builtin_function_map.cc | 21 +- zetasql/compliance/BUILD | 1 - .../depth_limit_detector_test_cases.cc | 20 + .../compliance/functions_testlib_string_5.cc | 3 +- zetasql/compliance/run_compliance_driver.cc | 13 +- zetasql/compliance/sql_test_base.cc | 1 - zetasql/compliance/test_driver.proto | 1 - .../compliance/testdata/map_functions.test | 53 + zetasql/examples/pipe_queries/BUILD | 35 + zetasql/examples/pipe_queries/README.md | 15 + .../examples/pipe_queries/pipe_examples.sql | 218 +++ zetasql/examples/pipe_queries/pipe_pivot.sql | 68 + .../pipe_queries/walkthrough_7day.sql | 269 +++ zetasql/examples/tpch/BUILD | 29 + zetasql/examples/tpch/all_pipe_queries.sql | 16 + zetasql/examples/tpch/all_queries.sql | 16 + zetasql/examples/tpch/describe.sql | 16 + zetasql/examples/tpch/pipe_queries/1.sql | 16 + zetasql/examples/tpch/pipe_queries/10.sql | 16 + zetasql/examples/tpch/pipe_queries/11.sql | 16 + zetasql/examples/tpch/pipe_queries/12.sql | 16 + zetasql/examples/tpch/pipe_queries/13.sql | 16 + zetasql/examples/tpch/pipe_queries/14.sql | 16 + zetasql/examples/tpch/pipe_queries/15.sql | 16 + zetasql/examples/tpch/pipe_queries/16.sql | 16 + zetasql/examples/tpch/pipe_queries/17.sql | 16 + zetasql/examples/tpch/pipe_queries/18.sql | 16 + zetasql/examples/tpch/pipe_queries/19.sql | 16 + zetasql/examples/tpch/pipe_queries/2.sql | 16 + zetasql/examples/tpch/pipe_queries/20.sql | 16 + zetasql/examples/tpch/pipe_queries/21.sql | 16 + zetasql/examples/tpch/pipe_queries/22.sql | 16 + zetasql/examples/tpch/pipe_queries/3.sql | 16 + zetasql/examples/tpch/pipe_queries/4.sql | 16 + zetasql/examples/tpch/pipe_queries/5.sql | 16 + zetasql/examples/tpch/pipe_queries/6.sql | 16 + zetasql/examples/tpch/pipe_queries/7.sql | 16 + zetasql/examples/tpch/pipe_queries/8.sql | 16 + zetasql/examples/tpch/pipe_queries/9.sql | 16 + zetasql/examples/tpch/queries/1.sql | 16 + zetasql/examples/tpch/queries/10.sql | 16 + zetasql/examples/tpch/queries/11.sql | 16 + zetasql/examples/tpch/queries/12.sql | 16 + zetasql/examples/tpch/queries/13.sql | 16 + zetasql/examples/tpch/queries/14.sql | 16 + zetasql/examples/tpch/queries/15.sql | 16 + zetasql/examples/tpch/queries/16.sql | 16 + zetasql/examples/tpch/queries/17.sql | 16 + zetasql/examples/tpch/queries/18.sql | 16 + zetasql/examples/tpch/queries/19.sql | 16 + zetasql/examples/tpch/queries/2.sql | 16 + zetasql/examples/tpch/queries/20.sql | 16 + zetasql/examples/tpch/queries/21.sql | 16 + zetasql/examples/tpch/queries/22.sql | 16 + zetasql/examples/tpch/queries/3.sql | 16 + zetasql/examples/tpch/queries/4.sql | 16 + zetasql/examples/tpch/queries/5.sql | 16 + zetasql/examples/tpch/queries/6.sql | 16 + zetasql/examples/tpch/queries/7.sql | 16 + zetasql/examples/tpch/queries/8.sql | 16 + zetasql/examples/tpch/queries/9.sql | 16 + zetasql/local_service/local_service.cc | 14 +- zetasql/parser/bison_parser.y | 177 +- zetasql/parser/gen_parse_tree.py | 196 +- zetasql/parser/parser_internal.h | 16 + zetasql/parser/testdata/match_recognize.test | 186 +- .../testdata/proto_braced_constructors.test | 308 ++- .../testdata/struct_braced_constructors.test | 165 +- zetasql/parser/testdata/tablesample.test | 29 + zetasql/parser/unparser.cc | 44 +- zetasql/parser/unparser.h | 2 + zetasql/public/BUILD | 1 + zetasql/public/analyzer.cc | 15 +- zetasql/public/analyzer_options.cc | 4 + zetasql/public/analyzer_options.h | 6 + zetasql/public/builtin_function.cc | 6 +- zetasql/public/builtin_function.proto | 24 +- zetasql/public/functions/BUILD | 1 + zetasql/public/functions/to_json.cc | 38 +- zetasql/public/functions/to_json.h | 3 +- zetasql/public/functions/to_json_test.cc | 52 +- .../public/functions/unsupported_fields.proto | 25 +- zetasql/public/non_sql_function.cc | 2 +- zetasql/public/non_sql_function.h | 2 +- zetasql/public/options.proto | 2 +- zetasql/public/table_name_resolver.cc | 15 +- zetasql/public/table_name_resolver_test.cc | 42 +- zetasql/public/testing/error_matchers.h | 27 + zetasql/public/testing/error_matchers_test.cc | 6 + zetasql/public/type.proto | 3 + zetasql/public/types/enum_type.cc | 8 +- zetasql/public/types/type_factory.cc | 4 +- zetasql/reference_impl/BUILD | 8 + zetasql/reference_impl/algebrizer.cc | 197 +- zetasql/reference_impl/function.cc | 131 +- zetasql/reference_impl/function.h | 52 +- zetasql/reference_impl/function_test.cc | 80 + zetasql/reference_impl/functions/json.cc | 10 +- zetasql/reference_impl/functions/map.cc | 101 +- zetasql/reference_impl/operator.cc | 8 + zetasql/reference_impl/operator.h | 4 + zetasql/reference_impl/value_expr.cc | 10 +- zetasql/resolved_ast/sql_builder.h | 2 +- zetasql/tools/execute_query/BUILD | 24 + zetasql/tools/execute_query/build_rules.bzl | 42 + zetasql/tools/execute_query/execute_query.cc | 48 +- .../execute_query_internal_csv.cc | 7 - .../execute_query/execute_query_loop_test.cc | 16 - .../execute_query/execute_query_prompt.cc | 18 - .../execute_query/execute_query_prompt.h | 15 - .../execute_query_prompt_test.cc | 25 - .../tools/execute_query/execute_query_tool.cc | 35 + .../execute_query_web_handler_test.cc | 25 + .../execute_query/execute_query_web_writer.h | 11 + .../execute_query/execute_query_writer.h | 6 + .../execute_query/run_execute_query_test.sh | 44 + .../testdata/execute_query_tool.test | 113 ++ .../testdata/run_execute_query_test.sql | 23 + .../testdata/run_execute_query_test_bad.sql | 18 + .../tools/execute_query/web/page_body.html | 12 +- zetasql/tools/execute_query/web/style.css | 18 +- zetasql/tools/formatter/internal/chunk.cc | 33 +- zetasql/tools/formatter/internal/layout.cc | 4 +- zetasql/tools/formatter/internal/token.cc | 75 +- zetasql/tools/formatter/internal/token.h | 14 +- 220 files changed, 5902 insertions(+), 6180 deletions(-) create mode 100755 docker_build.sh create mode 100644 zetasql/analyzer/testdata/match_recognize.test create mode 100644 zetasql/base/optional_ref.h create mode 100644 zetasql/base/optional_ref_matchers.h create mode 100644 zetasql/base/optional_ref_test.cc create mode 100644 zetasql/examples/pipe_queries/BUILD create mode 100644 zetasql/examples/pipe_queries/README.md create mode 100644 zetasql/examples/pipe_queries/pipe_examples.sql create mode 100644 zetasql/examples/pipe_queries/pipe_pivot.sql create mode 100644 zetasql/examples/pipe_queries/walkthrough_7day.sql create mode 100644 zetasql/examples/tpch/BUILD create mode 100644 zetasql/tools/execute_query/build_rules.bzl create mode 100755 zetasql/tools/execute_query/run_execute_query_test.sh create mode 100644 zetasql/tools/execute_query/testdata/run_execute_query_test.sql create mode 100644 zetasql/tools/execute_query/testdata/run_execute_query_test_bad.sql diff --git a/Dockerfile b/Dockerfile index 252c6810c..d0721b8a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,17 +52,17 @@ USER zetasql ENV BAZEL_ARGS="--config=g++" -# Pre-build the binary for execute_query so that users can try out zetasql -# directly. Users can modify the target in the docker file or enter the -# container and build other targets as needed. -RUN cd zetasql && \ - CC=/usr/bin/gcc CXX=/usr/bin/g++ \ - bazel build ${BAZEL_ARGS} -c opt //zetasql/tools/execute_query:execute_query - -# Create a shortcut for execute_query. ENV HOME=/home/zetasql RUN mkdir -p $HOME/bin -RUN ln -s /zetasql/bazel-bin/zetasql/tools/execute_query/execute_query $HOME/bin/execute_query + +# Supported MODE: +# - `build` (default): Builds all ZetaSQL targets. +# - `execute_query`: Installs the `execute_query` tool only. Erases all other +# build artifacts. +ARG MODE=build + +RUN cd zetasql && ./docker_build.sh $MODE + ENV PATH=$PATH:$HOME/bin WORKDIR /zetasql diff --git a/README.md b/README.md index 91feeb2ac..463123188 100644 --- a/README.md +++ b/README.md @@ -11,21 +11,24 @@ giving errors for unuspported features. ZetaSQL's compliance test suite can be used to validate query engine implementations are correct and consistent. -ZetaSQL implements the ZetaSQL language, which is used across several of +ZetaSQL implements the GoogleSQL language, which is used across several of Google's SQL products, both publicly and internally, including BigQuery, Spanner, F1, BigTable, Dremel, Procella, and others. -ZetaSQL and ZetaSQL have been described in these publications: +GoogleSQL and ZetaSQL have been described in these publications: -* (CDMS 2022) [ZetaSQL: A SQL Language as a Component](https://cdmsworkshop.github.io/2022/Slides/Fri_C2.5_DavidWilhite.pptx) (Slides) +* (CDMS 2022) [GoogleSQL: A SQL Language as a Component](https://cdmsworkshop.github.io/2022/Slides/Fri_C2.5_DavidWilhite.pptx) (Slides) * (SIGMOD 2017) [Spanner: Becoming a SQL System](https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/46103.pdf) -- See section 6. -* (VLDB 2024) [SQL Has Problems. We Can Fix Them: Pipe Syntax in SQL](https://research.google/pubs/pub1005959/) -- Describes ZetaSQL's new pipe query syntax. +* (VLDB 2024) [SQL Has Problems. We Can Fix Them: Pipe Syntax in SQL](https://research.google/pubs/pub1005959/) -- Describes GoogleSQL's new pipe query syntax. Some other documentation: * [ZetaSQL Language Reference](docs/README.md) * [ZetaSQL Resolved AST](docs/resolved_ast.md), documenting the intermediate representation produced by the ZetaSQL analyzer. * [ZetaSQL Toolkit](https://github.com/GoogleCloudPlatform/zetasql-toolkit), a project using ZetaSQL to analyze and understand queries against BigQuery, and other ZetaSQL engines. +* Pipe query syntax + * See the [reference documentation](https://github.com/google/zetasql/blob/master/docs/pipe-syntax.md) and [research paper](https://research.google/pubs/pub1005959/). + * See some [example scripts](zetasql/examples/pipe_queries) and [TPC-H queries](zetasql/examples/tpch). ## Project Overview @@ -62,7 +65,8 @@ You can run it using binaries from instructions below. There are some runnable example queries in -[tpch examples](../zetasql/examples/tpch/README.md). +[`zetasql/examples/tpch`](zetasql/examples/tpch) and +[`zetasql/examples/pipe_queries`](zetasql/examples/pipe_queries). ### Getting and Running `execute_query` #### Pre-built Binaries @@ -72,14 +76,20 @@ the [Releases](https://github.com/google/zetasql/releases) page. You can run the downloaded binary like: ```bash +chmod +x execute_query_linux ./execute_query_linux --web ``` +MacOS users may see the error `execute_query_macos cannot be opened because the developer cannot be verified.`. +You can right click the `execute_query_macos` file, click "open", and then you +should be able to run the binary. + Note the prebuilt binaries require GCC-9+ and tzdata. If you run into dependency -issues, you can try running `execute_query` with Docker. See the -[Run with Docker](#run-with-docker) section. +issues or if the binary is incompatible with your platform, you can try running +`execute_query` with Docker. See the [Run with Docker](#run-with-docker) +section. -#### Running from a bazel build +#### Running from a Bazel Build You can build `execute_query` with Bazel from source and run it by: @@ -89,14 +99,29 @@ bazel run zetasql/tools/execute_query:execute_query -- --web #### Run with Docker -You can run `execute_query` using Docker. First download the pre-built Docker -image `zetasql` or build your own from Dockerfile. See the instructions in the -[Build With Docker](#build-with-docker) section. +You can run `execute_query` using Docker. Download the pre-built Docker image +file `zetasql_docker.tar.gz` from the +[Releases](https://github.com/google/zetasql/releases) page, and load the image +using: + +```bash +sudo docker load -i /path/to/the/downloaded/zetasql_docker.tar.gz +``` + +The Docker image name is `zetasql`. (You can also build a Docker image locally +using the instructions in the [Build with Docker](#build-with-docker) section.) -Assuming your Docker image name is MyZetaSQLImage, run: +You can then run `execute_query` using: ```bash -sudo docker run --init -it -h=$(hostname) -p 8080:8080 MyZetasqlImage execute_query --web +sudo docker run --init -it -h=$(hostname) -p 8080:8080 zetasql execute_query --web +``` + +If you are using MacOS with an Apple M1/M2 chip, add the additional argument +`--platform=linux/amd64`: + +```bash +sudo docker run --init -it -h=$(hostname) -p 8080:8080 --platform linux/amd64 zetasql execute_query --web ``` Argument descriptions: @@ -106,6 +131,7 @@ Argument descriptions: * `-h=$(hostname)`: Makes the hostname of the container the same as that of the host. * `-p 8080:8080`: Sets up port forwarding. +* `zetasql`: The docker image name. `-h=$(hostname)` and `-p 8080:8080` together make the URL address of the web server accessible from the host machine. @@ -114,7 +140,7 @@ Alternatively, you can run this to start a bash shell, and then run `execute_query` inside: ```bash -sudo docker run --init -it -h=$(hostname) -p 8080:8080 MyZetasqlImage +sudo docker run --init -it -h=$(hostname) -p 8080:8080 my-zetasql-image # Inside the container bash shell execute_query --web @@ -149,7 +175,7 @@ bazel build ... bazel run //zetasql/tools/execute_query:execute_query -- --web # The built binary can be found under bazel-bin and run directly. -bazel-bin/tools/execute_query:execute_query --web +bazel-bin/zetasql/tools/execute_query/execute_query --web # Build and run a test. bazel test //zetasql/parser:parser_set_test @@ -165,10 +191,10 @@ version can be found in the `zetasql_deps_step_2.bzl` file. ZetaSQL also provides a `Dockerfile` which configures all the dependencies so that users can build ZetaSQL more easily across different platforms. -To build the Docker image locally (called MyZetaSQLImage here), run: +To build the Docker image locally (called `my-zetasql-image` here), run: ```bash -sudo docker build . -t MyZetaSQLImage -f Dockerfile +sudo docker build . -t my-zetasql-image -f Dockerfile ``` Alternatively, ZetaSQL provides pre-built Docker images named `zetasql`. See the @@ -176,7 +202,7 @@ Alternatively, ZetaSQL provides pre-built Docker images named `zetasql`. See the downloaded image by: ```bash -sudo docker load -i /path/to/the/downloaded/zetasql_docker.tar +sudo docker load -i /path/to/the/downloaded/zetasql_docker.tar.gz ``` To run builds or other commands inside the Docker environment, run this command @@ -184,9 +210,11 @@ to open a bash shell inside the container: ```bash # Start a bash shell running inside the Docker container. -sudo docker run -it MyZetaSQLImage +sudo docker run -it my-zetasql-image ``` +Replace `my-zetasql-image` with `zetasql` if you use the pre-built Docker image. + Then you can run the commands from the [Build with Bazel](#build-with-bazel) section above. diff --git a/docker_build.sh b/docker_build.sh new file mode 100755 index 000000000..f90641216 --- /dev/null +++ b/docker_build.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e +set -x + +MODE=$1 + +CC=/usr/bin/gcc +CXX=/usr/bin/g++ + +if [ "$MODE" = "build" ]; then + # Build everything. + bazel build ${BAZEL_ARGS} -c opt ... +elif [ "$MODE" = "execute_query" ]; then + # Install the execute_query tool. + bazel build ${BAZEL_ARGS} -c opt --dynamic_mode=off //zetasql/tools/execute_query:execute_query + # Move the generated binary to the home directory so that users can run it + # directly. + cp /zetasql/bazel-bin/zetasql/tools/execute_query/execute_query $HOME/bin/execute_query + # Remove the downloaded and generated artifacts to keep the image small. + bazel clean --expunge +else + echo "Unknown mode: $MODE" + echo "Supported modes are: build, execute_query" + exit 1 +fi diff --git a/docs/README.md b/docs/README.md index a57f43cd9..2f9f1067b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,14 +7,15 @@ The topics in this section provide the reference information you need to work with ZetaSQL: -* [Lexical Structure and Syntax](https://github.com/google/zetasql/blob/master/docs/lexical.md) -* [Expressions, Functions, and Operators](https://github.com/google/zetasql/blob/master/docs/functions-and-operators.md) -* [Data Types](https://github.com/google/zetasql/blob/master/docs/data-types.md) -* [Query Syntax](https://github.com/google/zetasql/blob/master/docs/query-syntax.md) -* [Data Manipulation Language Reference](https://github.com/google/zetasql/blob/master/docs/data-manipulation-language.md) -* [Data Model](https://github.com/google/zetasql/blob/master/docs/data-model.md) -* [Data Definition Language Reference](https://github.com/google/zetasql/blob/master/docs/data-definition-language.md) -* [Modules](https://github.com/google/zetasql/blob/master/docs/modules.md) +* [Lexical Structure and Syntax](lexical.md) +* [Expressions, Functions, and Operators](functions-and-operators.md) +* [Data Types](data-types.md) +* [Query Syntax](query-syntax.md) +* [Pipe Query Syntax](pipe-syntax.md) +* [Data Manipulation Language Reference](data-manipulation-language.md) +* [Data Model](data-model.md) +* [Data Definition Language Reference](data-definition-language.md) +* [Modules](modules.md) ## License diff --git a/docs/aggregate-dp-functions.md b/docs/aggregate-dp-functions.md index 30513d467..5397fcdb2 100644 --- a/docs/aggregate-dp-functions.md +++ b/docs/aggregate-dp-functions.md @@ -186,7 +186,7 @@ determine the optimal privacy parameters for your dataset and organization. WITH DIFFERENTIAL_PRIVACY ... AVG( expression, - [contribution_bounds_per_group => (lower_bound, upper_bound)] + [ contribution_bounds_per_group => (lower_bound, upper_bound) ] ) ``` @@ -201,9 +201,9 @@ and can support the following arguments: + `expression`: The input expression. This can be any numeric input type, such as `INT64`. -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping for each group separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -330,7 +330,7 @@ noise, see [Remove noise][dp-noise]. WITH DIFFERENTIAL_PRIVACY ... COUNT( *, - [contribution_bounds_per_group => (lower_bound, upper_bound)] + [ contribution_bounds_per_group => (lower_bound, upper_bound) ] ) ``` @@ -343,9 +343,9 @@ is an aggregation across a privacy unit column. This function must be used with the [`DIFFERENTIAL_PRIVACY` clause][dp-syntax] and can support the following argument: -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping for each group separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -468,9 +468,9 @@ and can support these arguments: + `expression`: The input expression. This expression can be any numeric input type, such as `INT64`. -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping per each group separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -609,9 +609,9 @@ and can support these arguments: such as `INT64`. `NULL` values are always ignored. + `percentile`: The percentile to compute. The percentile must be a literal in the range `[0, 1]`. -+ `contribution_bounds_per_row`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each row separately before performing intermediate ++ `contribution_bounds_per_row`: A named argument with a + [contribution bounds][dp-clamped-named]. + Performs clamping for each row separately before performing intermediate grouping on the privacy unit column. `NUMERIC` and `BIGNUMERIC` arguments are not allowed. @@ -689,7 +689,7 @@ GROUP BY item; WITH DIFFERENTIAL_PRIVACY ... SUM( expression, - [contribution_bounds_per_group => (lower_bound, upper_bound)] + [ contribution_bounds_per_group => (lower_bound, upper_bound) ] ) ``` @@ -703,10 +703,9 @@ and can support these arguments: + `expression`: The input expression. This can be any numeric input type, such as `INT64`. `NULL` values are always ignored. -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate - grouping on the privacy unit column. ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. Performs clamping for each group + separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -830,7 +829,7 @@ noise, see [Use differential privacy][dp-noise]. WITH DIFFERENTIAL_PRIVACY ... VAR_POP( expression, - [contribution_bounds_per_row => (lower_bound, upper_bound)] + [ contribution_bounds_per_row => (lower_bound, upper_bound) ] ) ``` @@ -847,9 +846,9 @@ can support these arguments: + `expression`: The input expression. This can be any numeric input type, such as `INT64`. `NULL`s are always ignored. -+ `contribution_bounds_per_row`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each row separately before performing intermediate ++ `contribution_bounds_per_row`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping for each row separately before performing intermediate grouping on individual user values. `NUMERIC` and `BIGNUMERIC` arguments are not allowed. diff --git a/docs/aggregate-function-calls.md b/docs/aggregate-function-calls.md index a1412f4e8..0a70c2843 100644 --- a/docs/aggregate-function-calls.md +++ b/docs/aggregate-function-calls.md @@ -4,10 +4,10 @@ # Aggregate function calls -An aggregate function is a function that summarizes the rows of a group into a -single value. When an aggregate function is used with the `OVER` clause, it -becomes a window function, which computes values over a group of rows and then -returns a single result for each row. +An aggregate function summarizes the rows of a group into a single value. When +an aggregate function is used with the `OVER` clause, it becomes a window +function, which computes values over a group of rows and then returns a single +result for each row. ## Aggregate function call syntax diff --git a/docs/array_functions.md b/docs/array_functions.md index f2161de0e..233b23f3b 100644 --- a/docs/array_functions.md +++ b/docs/array_functions.md @@ -1333,7 +1333,7 @@ SELECT ARRAY_ZIP( array_input [ AS alias ], array_input [ AS alias ][, ... ] - [, transformation => lambda_expression ] + [, [ transformation => ] value ] [, mode => { 'STRICT' | 'TRUNCATE' | 'PAD' } ] ) ``` @@ -1348,13 +1348,13 @@ Combines the elements from two to four arrays into one array. inputs. `ARRAY_ZIP` supports two to four input arrays. + `alias`: An alias optionally supplied for an `array_input`. In the results, the alias is the name of the associated `STRUCT` field. -+ `transformation`: A optionally-named lambda argument. `lambda_expression` - specifies how elements are combined as they are zipped. This overrides - the default `STRUCT` creation behavior. -+ `mode`: A mandatory-named argument that determines how arrays of differing - lengths are zipped. If this optional argument is not supplied, the function - uses `STRICT` mode by default. This argument can be one of the following - values: ++ `transformation`: A named argument with a lambda expression. + The lambda expression specifies how elements are combined as they are + zipped. This overrides the default `STRUCT` creation behavior. ++ `mode`: A named argument with a `STRING` value. Determines how arrays of + differing lengths are zipped. If this argument isn't supplied, the + function uses `STRICT` mode. This argument can be one of the + following values: + `STRICT` (default): If the length of any array is different from the others, produce an error. @@ -1373,7 +1373,7 @@ Combines the elements from two to four arrays into one array. **Return type** -+ If `transformation` is used and `lambda_expression` returns type `T`, the ++ If `transformation` is used and returns type `T`, the return type is `ARRAY`. + Otherwise, the return type is `ARRAY`, with the `STRUCT` having a number of fields equal to the number of input arrays. Each field's name is diff --git a/docs/data-definition-language.md b/docs/data-definition-language.md index 12cf64d56..58ade6421 100644 --- a/docs/data-definition-language.md +++ b/docs/data-definition-language.md @@ -860,20 +860,19 @@ SELECT (DEFAULT_HEIGHT + 5) AS result; ## `CREATE AGGREGATE FUNCTION` -A user-defined aggregate function (UDA), enables you to create an -aggregate function using another SQL expression or another programming language. -These functions accept arguments and perform actions, returning the -result of those actions as a value. To create a UDA, -see [UDAs][udas]. +A user-defined aggregate function (UDA), lets you create an aggregate function +using another SQL expression or another programming language. These functions +accept arguments and perform actions, returning the result of those actions as a +value. To create a UDA, see [UDAs][udas]. [udas]: https://github.com/google/zetasql/blob/master/docs/user-defined-aggregates.md#udas ## `CREATE FUNCTION` -A user-defined function (UDF), enables you to create a scalar function using -another SQL expression or another programming language. -These functions accept arguments and perform actions, returning the -result of those actions as a value. To create a UDF, see [UDFs][udfs]. +A user-defined function (UDF), lets you create a scalar function using another +SQL expression or another programming language. These functions accept arguments +and perform actions, returning the result of those actions as a value. To create +a UDF, see [UDFs][udfs]. [udfs]: https://github.com/google/zetasql/blob/master/docs/user-defined-functions.md @@ -949,9 +948,9 @@ denied from exercising these privileges on the object. ## `CREATE TABLE FUNCTION` -A table function, also known as a table value function (TVF), is a function that -returns a table. A TVF is called in the `FROM` clause like a table subquery. -To create a TVF, see [TVFs][tvfs]. +A table function, also known as a table value function (TVF), returns a table. A +TVF is called in the `FROM` clause like a table subquery. To create a TVF, see +[TVFs][tvfs]. [tvfs]: https://github.com/google/zetasql/blob/master/docs/table-functions.md#tvfs diff --git a/docs/functions-and-operators.md b/docs/functions-and-operators.md index e146ace7d..306412075 100644 --- a/docs/functions-and-operators.md +++ b/docs/functions-and-operators.md @@ -8179,7 +8179,7 @@ SELECT ARRAY_ZIP( array_input [ AS alias ], array_input [ AS alias ][, ... ] - [, transformation => lambda_expression ] + [, [ transformation => ] value ] [, mode => { 'STRICT' | 'TRUNCATE' | 'PAD' } ] ) ``` @@ -8194,13 +8194,13 @@ Combines the elements from two to four arrays into one array. inputs. `ARRAY_ZIP` supports two to four input arrays. + `alias`: An alias optionally supplied for an `array_input`. In the results, the alias is the name of the associated `STRUCT` field. -+ `transformation`: A optionally-named lambda argument. `lambda_expression` - specifies how elements are combined as they are zipped. This overrides - the default `STRUCT` creation behavior. -+ `mode`: A mandatory-named argument that determines how arrays of differing - lengths are zipped. If this optional argument is not supplied, the function - uses `STRICT` mode by default. This argument can be one of the following - values: ++ `transformation`: A named argument with a lambda expression. + The lambda expression specifies how elements are combined as they are + zipped. This overrides the default `STRUCT` creation behavior. ++ `mode`: A named argument with a `STRING` value. Determines how arrays of + differing lengths are zipped. If this argument isn't supplied, the + function uses `STRICT` mode. This argument can be one of the + following values: + `STRICT` (default): If the length of any array is different from the others, produce an error. @@ -8219,7 +8219,7 @@ Combines the elements from two to four arrays into one array. **Return type** -+ If `transformation` is used and `lambda_expression` returns type `T`, the ++ If `transformation` is used and returns type `T`, the return type is `ARRAY`. + Otherwise, the return type is `ARRAY`, with the `STRUCT` having a number of fields equal to the number of input arrays. Each field's name is @@ -13680,7 +13680,7 @@ determine the optimal privacy parameters for your dataset and organization. WITH DIFFERENTIAL_PRIVACY ... AVG( expression, - [contribution_bounds_per_group => (lower_bound, upper_bound)] + [ contribution_bounds_per_group => (lower_bound, upper_bound) ] ) ``` @@ -13695,9 +13695,9 @@ and can support the following arguments: + `expression`: The input expression. This can be any numeric input type, such as `INT64`. -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping for each group separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -13824,7 +13824,7 @@ noise, see [Remove noise][dp-noise]. WITH DIFFERENTIAL_PRIVACY ... COUNT( *, - [contribution_bounds_per_group => (lower_bound, upper_bound)] + [ contribution_bounds_per_group => (lower_bound, upper_bound) ] ) ``` @@ -13837,9 +13837,9 @@ is an aggregation across a privacy unit column. This function must be used with the [`DIFFERENTIAL_PRIVACY` clause][dp-syntax] and can support the following argument: -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping for each group separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -13962,9 +13962,9 @@ and can support these arguments: + `expression`: The input expression. This expression can be any numeric input type, such as `INT64`. -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping per each group separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -14103,9 +14103,9 @@ and can support these arguments: such as `INT64`. `NULL` values are always ignored. + `percentile`: The percentile to compute. The percentile must be a literal in the range `[0, 1]`. -+ `contribution_bounds_per_row`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each row separately before performing intermediate ++ `contribution_bounds_per_row`: A named argument with a + [contribution bounds][dp-clamped-named]. + Performs clamping for each row separately before performing intermediate grouping on the privacy unit column. `NUMERIC` and `BIGNUMERIC` arguments are not allowed. @@ -14183,7 +14183,7 @@ GROUP BY item; WITH DIFFERENTIAL_PRIVACY ... SUM( expression, - [contribution_bounds_per_group => (lower_bound, upper_bound)] + [ contribution_bounds_per_group => (lower_bound, upper_bound) ] ) ``` @@ -14197,10 +14197,9 @@ and can support these arguments: + `expression`: The input expression. This can be any numeric input type, such as `INT64`. `NULL` values are always ignored. -+ `contribution_bounds_per_group`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each group separately before performing intermediate - grouping on the privacy unit column. ++ `contribution_bounds_per_group`: A named argument with a + [contribution bound][dp-clamped-named]. Performs clamping for each group + separately before performing intermediate grouping on the privacy unit column. **Return type** @@ -14324,7 +14323,7 @@ noise, see [Use differential privacy][dp-noise]. WITH DIFFERENTIAL_PRIVACY ... VAR_POP( expression, - [contribution_bounds_per_row => (lower_bound, upper_bound)] + [ contribution_bounds_per_row => (lower_bound, upper_bound) ] ) ``` @@ -14341,9 +14340,9 @@ can support these arguments: + `expression`: The input expression. This can be any numeric input type, such as `INT64`. `NULL`s are always ignored. -+ `contribution_bounds_per_row`: The - [contribution bounds named argument][dp-clamped-named]. - Perform clamping per each row separately before performing intermediate ++ `contribution_bounds_per_row`: A named argument with a + [contribution bound][dp-clamped-named]. + Performs clamping for each row separately before performing intermediate grouping on individual user values. `NUMERIC` and `BIGNUMERIC` arguments are not allowed. @@ -17647,7 +17646,10 @@ SELECT ST_GEOGFROM( ### `ST_GEOGFROMGEOJSON` ```sql -ST_GEOGFROMGEOJSON(geojson_string [, make_valid => constant_expression]) +ST_GEOGFROMGEOJSON( + geojson_string + [, make_valid => constant_expression ] +) ``` **Description** @@ -17658,10 +17660,9 @@ input [GeoJSON][geojson-link] representation. `ST_GEOGFROMGEOJSON` accepts input that is [RFC 7946][geojson-spec-link] compliant. -If the parameter `make_valid` is set to `TRUE`, the function attempts to repair -polygons that don't conform to [Open Geospatial Consortium][ogc-link] semantics. -This parameter uses named argument syntax, and should be specified using -`make_valid => argument_value` syntax. +If the named argument `make_valid` is set to `TRUE`, the function attempts to +repair polygons that don't conform to [Open Geospatial Consortium][ogc-link] +semantics. A ZetaSQL `GEOGRAPHY` has spherical geodesic edges, whereas a GeoJSON `Geometry` object explicitly has planar edges. @@ -17831,9 +17832,9 @@ ST_GEOGFROMWKB( ```sql ST_GEOGFROMWKB( wkb_hex_string_expression - [ , oriented => value ] - [ , planar => value ] - [ , make_valid => value ] + [, oriented => value ] + [, planar => value ] + [, make_valid => value ] ) ``` @@ -18067,11 +18068,11 @@ FROM example; ### `ST_HAUSDORFFDISTANCE` ```sql -ST_HAUSDORFFDISTANCE(geography_1, geography_2) -``` - -```sql -ST_HAUSDORFFDISTANCE(geography_1, geography_2, directed=>{ TRUE | FALSE }) +ST_HAUSDORFFDISTANCE( + geography_1, + geography_2 + [, directed => { TRUE | FALSE } ] +) ``` **Description** @@ -18084,9 +18085,9 @@ discrete point in another geography. + `geography_1`: A `GEOGRAPHY` value that represents the first geography. + `geography_2`: A `GEOGRAPHY` value that represents the second geography. -+ `directed`: Optional, required named argument that represents the type of - computation to use on the input geographies. If this argument is not - specified, `directed=>FALSE` is used by default. ++ `directed`: A named argument with a `BOOL` value. Represents the type of + computation to use on the input geographies. If this argument isn't + specified, `directed => FALSE` is used by default. + `FALSE`: The largest Hausdorff distance found in (`geography_1`, `geography_2`) and @@ -20125,7 +20126,14 @@ SELECT JUSTIFY_INTERVAL(INTERVAL '29 49:00:00' DAY TO SECOND) AS i ### `MAKE_INTERVAL` ```sql -MAKE_INTERVAL([year][, month][, day][, hour][, minute][, second]) +MAKE_INTERVAL( + [ [ year => ] value ] + [, [ month => ] value ] + [, [ day => ] value ] + [, [ hour => ] value ] + [, [ minute => ] value ] + [, [ second => ] value ] +) ``` **Description** @@ -20282,8 +20290,8 @@ behavior: - Functions that flexibly convert a JSON value to an SQL value - without returning errors. + Functions that flexibly convert a JSON value to a SQL value without + returning errors. @@ -21009,7 +21017,10 @@ SELECT BOOL_ARRAY(JSON 'null') AS result; -- Throws an error ```sql -DOUBLE(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +DOUBLE( + json_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -21026,8 +21037,8 @@ Arguments: If the JSON value is not a number, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, - which defines what happens with a number that cannot be ++ `wide_number_mode`: A named argument with a `STRING` value. + Defines what happens with a number that can't be represented as a `DOUBLE` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -21105,7 +21116,10 @@ SELECT SAFE.DOUBLE(JSON '"strawberry"') AS result; ```sql -DOUBLE_ARRAY(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +DOUBLE_ARRAY( + json_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -21122,8 +21136,8 @@ Arguments: If the JSON value is not an array of numbers, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, which defines what - happens with a number that cannot be represented as a ++ `wide_number_mode`: A named argument that takes a `STRING` value. Defines + what happens with a number that can't be represented as a `DOUBLE` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -21189,7 +21203,10 @@ SELECT DOUBLE_ARRAY(JSON '[18446744073709551615]', wide_number_mode=>'exact') as ```sql -FLOAT(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +FLOAT( + json_expr + [, [ wide_number_mode => ] { 'exact' | 'round' } ] +) ``` **Description** @@ -21206,7 +21223,7 @@ Arguments: If the JSON value is not a number, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, which defines what ++ `wide_number_mode`: A named argument with a `STRING` value. Defines what happens with a number that cannot be represented as a `FLOAT` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -21285,7 +21302,10 @@ SELECT SAFE.FLOAT(JSON '"strawberry"') AS result; ```sql -FLOAT_ARRAY(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +FLOAT_ARRAY( + json_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -21302,8 +21322,8 @@ Arguments: If the JSON value is not an array of numbers, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, which defines what - happens with a number that cannot be represented as a ++ `wide_number_mode`: A named argument with a `STRING` value. Defines + what happens with a number that can't be represented as a `FLOAT` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -21724,7 +21744,7 @@ SELECT JSON_ARRAY() AS json_data JSON_ARRAY_APPEND( json_expr, json_path_value_pair[, ...] - [, append_each_element=>{ TRUE | FALSE }] + [, append_each_element => { TRUE | FALSE } ] ) json_path_value_pair: @@ -21748,7 +21768,7 @@ Arguments: + `value`: A [JSON encoding-supported][json-encodings] value to append. -+ `append_each_element`: An optional, mandatory named argument. ++ `append_each_element`: A named argument with a `BOOL` value. + If `TRUE` (default), and `value` is a SQL array, appends each element individually. @@ -21915,7 +21935,7 @@ SELECT JSON_ARRAY_APPEND(JSON '{"a": 1}', '$.b', 2) AS json_data JSON_ARRAY_INSERT( json_expr, json_path_value_pair[, ...] - [, insert_each_element=>{ TRUE | FALSE }] + [, insert_each_element => { TRUE | FALSE } ] ) json_path_value_pair: @@ -21940,7 +21960,7 @@ Arguments: + `value`: A [JSON encoding-supported][json-encodings] value to insert. -+ `insert_each_element`: An optional, mandatory named argument. ++ `insert_each_element`: A named argument with a `BOOL` value. + If `TRUE` (default), and `value` is a SQL array, inserts each element individually. @@ -23763,7 +23783,7 @@ SELECT JSON_REMOVE(JSON 'null', '$.a.b') AS json_data JSON_SET( json_expr, json_path_value_pair[, ...] - [, create_if_missing=> { TRUE | FALSE }] + [, create_if_missing => { TRUE | FALSE } ] ) json_path_value_pair: @@ -23788,11 +23808,11 @@ Arguments: + `value`: A [JSON encoding-supported][json-encodings] value to insert. -+ `create_if_missing`: An optional, mandatory named argument. ++ `create_if_missing`: A named argument that takes a `BOOL` value. - + If TRUE (default), replaces or inserts data if the path does not exist. + + If `TRUE` (default), replaces or inserts data if the path doesn't exist. - + If FALSE, only _existing_ JSONPath values are replaced. If the path + + If `FALSE`, only existing JSONPath values are replaced. If the path doesn't exist, the set operation is ignored. Details: @@ -24052,9 +24072,9 @@ SELECT JSON_SET( ```sql JSON_STRIP_NULLS( json_expr - [, json_path] - [, include_arrays => { TRUE | FALSE }] - [, remove_empty => { TRUE | FALSE }] + [, json_path ] + [, include_arrays => { TRUE | FALSE } ] + [, remove_empty => { TRUE | FALSE } ] ) ``` @@ -24069,10 +24089,10 @@ Arguments: ``` + `json_path`: Remove JSON nulls at this [JSONPath][JSONPath-format] for `json_expr`. -+ `include_arrays`: An optional, mandatory named argument that is either ++ `include_arrays`: A named argument that's either `TRUE` (default) or `FALSE`. If `TRUE` or omitted, the function removes JSON nulls from JSON arrays. If `FALSE`, does not. -+ `remove_empty`: An optional, mandatory named argument that is either ++ `remove_empty`: A named argument that's either `TRUE` or `FALSE` (default). If `TRUE`, the function removes empty JSON objects after JSON nulls are removed. If `FALSE` or omitted, does not. @@ -27508,7 +27528,10 @@ SELECT LAX_UINT64_ARRAY(JSON '9.8') AS result; ### `PARSE_JSON` ```sql -PARSE_JSON(json_string_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +PARSE_JSON( + json_string_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -27522,9 +27545,10 @@ Arguments: ``` '{"class": {"students": [{"name": "Jane"}]}}' ``` -+ `wide_number_mode`: Optional mandatory-named argument that determines how to - handle numbers that cannot be stored in a `JSON` value without the loss of - precision. If used, `wide_number_mode` must include one of these values: ++ `wide_number_mode`: A named argument with a `STRING` value. Determines + how to handle numbers that can't be stored in a `JSON` value without the + loss of precision. If used, `wide_number_mode` must include one of the + following values: + `exact` (default): Only accept numbers that can be stored without loss of precision. If a number that cannot be stored without loss of @@ -27713,7 +27737,10 @@ SELECT STRING_ARRAY(JSON 'null') AS result; -- Throws an error ### `TO_JSON` ```sql -TO_JSON(sql_value[, stringify_wide_numbers=>{ TRUE | FALSE }]) +TO_JSON( + sql_value + [, stringify_wide_numbers => { TRUE | FALSE } ] +) ``` **Description** @@ -27725,7 +27752,7 @@ Arguments: + `sql_value`: The SQL value to convert to a JSON value. You can review the ZetaSQL data types that this function supports and their JSON encodings [here][json-encodings]. -+ `stringify_wide_numbers`: Optional mandatory-named argument that is either ++ `stringify_wide_numbers`: A named argument that's either `TRUE` or `FALSE` (default). + If `TRUE`, numeric values outside of the @@ -35215,13 +35242,14 @@ FROM AlbumList; ### `FILTER_FIELDS` ```sql -FILTER_FIELDS(proto_expression, proto_field_list [, reset_fields_named_arg]) +FILTER_FIELDS( + proto_expression, + proto_field_list + [, reset_cleared_required_fields => { TRUE | FALSE } ] +) proto_field_list: {+|-}proto_field_path[, ...] - -reset_fields_named_arg: - RESET_CLEARED_REQUIRED_FIELDS => { TRUE | FALSE } ``` **Description** @@ -35240,8 +35268,7 @@ Input values: + `proto_field_path`: The protocol buffer field to include or exclude. If the field represents an [extension][querying-proto-extensions], you can use syntax for that extension in the path. -+ `reset_fields_named_arg`: You can optionally add the - `RESET_CLEARED_REQUIRED_FIELDS` named argument. ++ `reset_cleared_required_fields`: Named argument with a `BOOL` value. If not explicitly set, `FALSE` is used implicitly. If `FALSE`, you must include all protocol buffer `required` fields in the `FILTER_FIELDS` function. If `TRUE`, you do not need to include all required @@ -39098,7 +39125,11 @@ FROM Employees; ### `EDIT_DISTANCE` ```sql -EDIT_DISTANCE(value1, value2, [max_distance => max_distance_value]) +EDIT_DISTANCE( + value1, + value2, + [ max_distance => max_distance_value ] +) ``` **Description** @@ -39110,8 +39141,8 @@ Computes the [Levenshtein distance][l-distance] between two `STRING` or + `value1`: The first `STRING` or `BYTES` value to compare. + `value2`: The second `STRING` or `BYTES` value to compare. -+ `max_distance`: Optional mandatory-named argument. Takes a non-negative - `INT64` value that represents the maximum distance between the two values ++ `max_distance`: A named argument with a `INT64` value that is greater than + or equal to zero. Represents the maximum distance between the two values to compute. If this distance is exceeded, the function returns this value. diff --git a/docs/geography_functions.md b/docs/geography_functions.md index 793141828..e8bf315c7 100644 --- a/docs/geography_functions.md +++ b/docs/geography_functions.md @@ -2338,7 +2338,10 @@ SELECT ST_GEOGFROM( ### `ST_GEOGFROMGEOJSON` ```sql -ST_GEOGFROMGEOJSON(geojson_string [, make_valid => constant_expression]) +ST_GEOGFROMGEOJSON( + geojson_string + [, make_valid => constant_expression ] +) ``` **Description** @@ -2349,10 +2352,9 @@ input [GeoJSON][geojson-link] representation. `ST_GEOGFROMGEOJSON` accepts input that is [RFC 7946][geojson-spec-link] compliant. -If the parameter `make_valid` is set to `TRUE`, the function attempts to repair -polygons that don't conform to [Open Geospatial Consortium][ogc-link] semantics. -This parameter uses named argument syntax, and should be specified using -`make_valid => argument_value` syntax. +If the named argument `make_valid` is set to `TRUE`, the function attempts to +repair polygons that don't conform to [Open Geospatial Consortium][ogc-link] +semantics. A ZetaSQL `GEOGRAPHY` has spherical geodesic edges, whereas a GeoJSON `Geometry` object explicitly has planar edges. @@ -2522,9 +2524,9 @@ ST_GEOGFROMWKB( ```sql ST_GEOGFROMWKB( wkb_hex_string_expression - [ , oriented => value ] - [ , planar => value ] - [ , make_valid => value ] + [, oriented => value ] + [, planar => value ] + [, make_valid => value ] ) ``` @@ -2758,11 +2760,11 @@ FROM example; ### `ST_HAUSDORFFDISTANCE` ```sql -ST_HAUSDORFFDISTANCE(geography_1, geography_2) -``` - -```sql -ST_HAUSDORFFDISTANCE(geography_1, geography_2, directed=>{ TRUE | FALSE }) +ST_HAUSDORFFDISTANCE( + geography_1, + geography_2 + [, directed => { TRUE | FALSE } ] +) ``` **Description** @@ -2775,9 +2777,9 @@ discrete point in another geography. + `geography_1`: A `GEOGRAPHY` value that represents the first geography. + `geography_2`: A `GEOGRAPHY` value that represents the second geography. -+ `directed`: Optional, required named argument that represents the type of - computation to use on the input geographies. If this argument is not - specified, `directed=>FALSE` is used by default. ++ `directed`: A named argument with a `BOOL` value. Represents the type of + computation to use on the input geographies. If this argument isn't + specified, `directed => FALSE` is used by default. + `FALSE`: The largest Hausdorff distance found in (`geography_1`, `geography_2`) and diff --git a/docs/interval_functions.md b/docs/interval_functions.md index 2df061a69..016a93c7f 100644 --- a/docs/interval_functions.md +++ b/docs/interval_functions.md @@ -233,7 +233,14 @@ SELECT JUSTIFY_INTERVAL(INTERVAL '29 49:00:00' DAY TO SECOND) AS i ### `MAKE_INTERVAL` ```sql -MAKE_INTERVAL([year][, month][, day][, hour][, minute][, second]) +MAKE_INTERVAL( + [ [ year => ] value ] + [, [ month => ] value ] + [, [ day => ] value ] + [, [ hour => ] value ] + [, [ minute => ] value ] + [, [ second => ] value ] +) ``` **Description** diff --git a/docs/json_functions.md b/docs/json_functions.md index f06427fb4..dfbd81577 100644 --- a/docs/json_functions.md +++ b/docs/json_functions.md @@ -127,8 +127,8 @@ behavior: - Functions that flexibly convert a JSON value to an SQL value - without returning errors. + Functions that flexibly convert a JSON value to a SQL value without + returning errors. @@ -854,7 +854,10 @@ SELECT BOOL_ARRAY(JSON 'null') AS result; -- Throws an error ```sql -DOUBLE(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +DOUBLE( + json_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -871,8 +874,8 @@ Arguments: If the JSON value is not a number, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, - which defines what happens with a number that cannot be ++ `wide_number_mode`: A named argument with a `STRING` value. + Defines what happens with a number that can't be represented as a `DOUBLE` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -950,7 +953,10 @@ SELECT SAFE.DOUBLE(JSON '"strawberry"') AS result; ```sql -DOUBLE_ARRAY(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +DOUBLE_ARRAY( + json_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -967,8 +973,8 @@ Arguments: If the JSON value is not an array of numbers, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, which defines what - happens with a number that cannot be represented as a ++ `wide_number_mode`: A named argument that takes a `STRING` value. Defines + what happens with a number that can't be represented as a `DOUBLE` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -1034,7 +1040,10 @@ SELECT DOUBLE_ARRAY(JSON '[18446744073709551615]', wide_number_mode=>'exact') as ```sql -FLOAT(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +FLOAT( + json_expr + [, [ wide_number_mode => ] { 'exact' | 'round' } ] +) ``` **Description** @@ -1051,7 +1060,7 @@ Arguments: If the JSON value is not a number, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, which defines what ++ `wide_number_mode`: A named argument with a `STRING` value. Defines what happens with a number that cannot be represented as a `FLOAT` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -1130,7 +1139,10 @@ SELECT SAFE.FLOAT(JSON '"strawberry"') AS result; ```sql -FLOAT_ARRAY(json_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +FLOAT_ARRAY( + json_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -1147,8 +1159,8 @@ Arguments: If the JSON value is not an array of numbers, an error is produced. If the expression is a SQL `NULL`, the function returns SQL `NULL`. -+ `wide_number_mode`: Optional mandatory-named argument, which defines what - happens with a number that cannot be represented as a ++ `wide_number_mode`: A named argument with a `STRING` value. Defines + what happens with a number that can't be represented as a `FLOAT` without loss of precision. This argument accepts one of the two case-sensitive values: @@ -1569,7 +1581,7 @@ SELECT JSON_ARRAY() AS json_data JSON_ARRAY_APPEND( json_expr, json_path_value_pair[, ...] - [, append_each_element=>{ TRUE | FALSE }] + [, append_each_element => { TRUE | FALSE } ] ) json_path_value_pair: @@ -1593,7 +1605,7 @@ Arguments: + `value`: A [JSON encoding-supported][json-encodings] value to append. -+ `append_each_element`: An optional, mandatory named argument. ++ `append_each_element`: A named argument with a `BOOL` value. + If `TRUE` (default), and `value` is a SQL array, appends each element individually. @@ -1760,7 +1772,7 @@ SELECT JSON_ARRAY_APPEND(JSON '{"a": 1}', '$.b', 2) AS json_data JSON_ARRAY_INSERT( json_expr, json_path_value_pair[, ...] - [, insert_each_element=>{ TRUE | FALSE }] + [, insert_each_element => { TRUE | FALSE } ] ) json_path_value_pair: @@ -1785,7 +1797,7 @@ Arguments: + `value`: A [JSON encoding-supported][json-encodings] value to insert. -+ `insert_each_element`: An optional, mandatory named argument. ++ `insert_each_element`: A named argument with a `BOOL` value. + If `TRUE` (default), and `value` is a SQL array, inserts each element individually. @@ -3608,7 +3620,7 @@ SELECT JSON_REMOVE(JSON 'null', '$.a.b') AS json_data JSON_SET( json_expr, json_path_value_pair[, ...] - [, create_if_missing=> { TRUE | FALSE }] + [, create_if_missing => { TRUE | FALSE } ] ) json_path_value_pair: @@ -3633,11 +3645,11 @@ Arguments: + `value`: A [JSON encoding-supported][json-encodings] value to insert. -+ `create_if_missing`: An optional, mandatory named argument. ++ `create_if_missing`: A named argument that takes a `BOOL` value. - + If TRUE (default), replaces or inserts data if the path does not exist. + + If `TRUE` (default), replaces or inserts data if the path doesn't exist. - + If FALSE, only _existing_ JSONPath values are replaced. If the path + + If `FALSE`, only existing JSONPath values are replaced. If the path doesn't exist, the set operation is ignored. Details: @@ -3897,9 +3909,9 @@ SELECT JSON_SET( ```sql JSON_STRIP_NULLS( json_expr - [, json_path] - [, include_arrays => { TRUE | FALSE }] - [, remove_empty => { TRUE | FALSE }] + [, json_path ] + [, include_arrays => { TRUE | FALSE } ] + [, remove_empty => { TRUE | FALSE } ] ) ``` @@ -3914,10 +3926,10 @@ Arguments: ``` + `json_path`: Remove JSON nulls at this [JSONPath][JSONPath-format] for `json_expr`. -+ `include_arrays`: An optional, mandatory named argument that is either ++ `include_arrays`: A named argument that's either `TRUE` (default) or `FALSE`. If `TRUE` or omitted, the function removes JSON nulls from JSON arrays. If `FALSE`, does not. -+ `remove_empty`: An optional, mandatory named argument that is either ++ `remove_empty`: A named argument that's either `TRUE` or `FALSE` (default). If `TRUE`, the function removes empty JSON objects after JSON nulls are removed. If `FALSE` or omitted, does not. @@ -7353,7 +7365,10 @@ SELECT LAX_UINT64_ARRAY(JSON '9.8') AS result; ### `PARSE_JSON` ```sql -PARSE_JSON(json_string_expr[, wide_number_mode=>{ 'exact' | 'round' }]) +PARSE_JSON( + json_string_expr + [, wide_number_mode => { 'exact' | 'round' } ] +) ``` **Description** @@ -7367,9 +7382,10 @@ Arguments: ``` '{"class": {"students": [{"name": "Jane"}]}}' ``` -+ `wide_number_mode`: Optional mandatory-named argument that determines how to - handle numbers that cannot be stored in a `JSON` value without the loss of - precision. If used, `wide_number_mode` must include one of these values: ++ `wide_number_mode`: A named argument with a `STRING` value. Determines + how to handle numbers that can't be stored in a `JSON` value without the + loss of precision. If used, `wide_number_mode` must include one of the + following values: + `exact` (default): Only accept numbers that can be stored without loss of precision. If a number that cannot be stored without loss of @@ -7558,7 +7574,10 @@ SELECT STRING_ARRAY(JSON 'null') AS result; -- Throws an error ### `TO_JSON` ```sql -TO_JSON(sql_value[, stringify_wide_numbers=>{ TRUE | FALSE }]) +TO_JSON( + sql_value + [, stringify_wide_numbers => { TRUE | FALSE } ] +) ``` **Description** @@ -7570,7 +7589,7 @@ Arguments: + `sql_value`: The SQL value to convert to a JSON value. You can review the ZetaSQL data types that this function supports and their JSON encodings [here][json-encodings]. -+ `stringify_wide_numbers`: Optional mandatory-named argument that is either ++ `stringify_wide_numbers`: A named argument that's either `TRUE` or `FALSE` (default). + If `TRUE`, numeric values outside of the diff --git a/docs/pipe-syntax.md b/docs/pipe-syntax.md index 4cc6777f6..d8e348d13 100644 --- a/docs/pipe-syntax.md +++ b/docs/pipe-syntax.md @@ -6,33 +6,25 @@ ZetaSQL supports pipe query syntax, which is a simpler and more concise alternative to [standard query syntax][query-syntax]. Pipe syntax -supports the same operators as standard syntax, but follows these rules. +supports many of the same operators as standard syntax, and improves some areas +of SQL query functionality. -Syntax: +## Pipe syntax + -* Pipe syntax consists of a pipe and an angle bracket `|>`, an operator name, +Pipe syntax has the following key characteristics: + ++ Pipe syntax consists of a pipe and an angle bracket `|>`, an operator name, and any arguments: \ `|> operator_name argument_list` -* Pipe operators can be added on the end of any valid query. -* Pipe operators can be applied in any order, any number of times. -* Pipe syntax works anywhere standard syntax is supported, in queries, views, ++ Pipe operators can be added to the end of any valid query. ++ Pipe operators can be applied in any order, any number of times. ++ Pipe syntax works anywhere standard syntax is supported: in queries, views, table-valued functions (TVFs), and other contexts. -* Pipe syntax can be mixed with standard syntax in the same query. For ++ Pipe syntax can be mixed with standard syntax in the same query. For example, subqueries can use different syntax from the parent query. - -`FROM` queries: - -* A query can start with a [`FROM` clause][from-clause] and use any standard - `FROM` syntax, including tables, joins, subqueries, table aliases with `AS`, - and other usual elements. -* Pipe operators can optionally be added after the `FROM` clause. - -Semantics: - -* Each pipe operator is self-contained and can only reference columns - available from its immediate input table. A pipe operator can't reference - columns from earlier in the same query. -* Each pipe operator produces a new result table as its output. ++ A query can [start with a `FROM` clause][from-queries], and pipe + operators can optionally be added after the `FROM` clause. Compare the following equivalent queries that count open tickets assigned to a user: @@ -43,8 +35,8 @@ assigned to a user: SELECT component_id, COUNT(*) FROM ticketing_system_table WHERE - assignee_user.email = 'username@email.com' - AND status IN ('NEW', 'ASSIGNED', 'ACCEPTED') + assignee_user.email = 'username@email.com' + AND status IN ('NEW', 'ASSIGNED', 'ACCEPTED') GROUP BY component_id ORDER BY component_id DESC; ``` @@ -60,15 +52,60 @@ FROM ticketing_system_table GROUP AND ORDER BY component_id DESC; ``` +## Pipe operator semantics + + +Pipe operators have the following semantic behavior: + ++ Each pipe operator performs a self-contained operation. ++ A pipe operator consumes the input table passed to it through the pipe + character and produces a new table as output. ++ A pipe operator can reference only columns from its immediate input table. + Columns from earlier in the same query aren't visible. Inside subqueries, + correlated references to outer columns are still allowed. + +## `FROM` queries + + +In pipe syntax, a query can start with a standard [`FROM` clause][from-clause] +and use any standard `FROM` syntax, including tables, joins, subqueries, +`UNNEST` operations, and table-valued functions (TVFs). Table aliases can be +assigned to each input item using the [`AS alias` clause][using-aliases]. + +A query with only a `FROM` clause, like `FROM table_name`, is allowed in pipe +syntax and returns all rows from the table. For tables with columns, +`FROM table_name` in pipe syntax is similar to +[`SELECT * FROM table_name`][select-star] in standard syntax. + For [value tables][value-tables], `FROM table_name` in +pipe syntax returns the row values without expanding fields, similar to +[`SELECT value FROM table_name AS value`][select-as-value] in standard +syntax. + +**Examples** + +
+-- Return a table row that matches a condition.
+FROM table_name
+|> WHERE value_column IS NULL
+|> LIMIT 1;
+
+ +
+-- Join tables in the FROM clause and then apply pipe operators.
+FROM Table1 AS t1 JOIN Table2 AS t2 USING (key)
+|> AGGREGATE SUM(t2.value)
+   GROUP BY t1.key;
+
+ ## Pipe operators -ZetaSQL supports the following pipe operators. Each operator -description includes a link to documentation for the corresponding operation in -standard syntax, where applicable. That documentation describes syntax features -and behavior in more detail. This pipe operator documentation highlights any -differences between the pipe syntax and the standard syntax for each operator. +ZetaSQL supports the following pipe operators. For operators that +correspond or relate to similar operations in standard syntax, the operator +descriptions highlight similarities and differences and link to more detailed +documentation on the corresponding syntax. -### `SELECT` +### `SELECT` pipe operator +
 |> SELECT expression [[AS] alias] [, ...]
@@ -76,24 +113,32 @@ differences between the pipe syntax and the standard syntax for each operator.
 
 **Description**
 
-Produces a new table with exactly the listed columns, similar to the outermost
+Produces a new table with the listed columns, similar to the outermost
 [`SELECT` clause][select-clause] in a table subquery in standard syntax.
 Supports standard output modifiers like `SELECT AS STRUCT`, and supports
 [window functions][window-functions]. Doesn't support aggregations or
-`WITH ANONYMIZATION`.
+anonymization.
 
-In pipe syntax, using `SELECT` operators in a query is optional. `SELECT` can be
-used near the end of a query to specify the list of output columns. The final
-query result includes all columns from the output of the last pipe operator. If
-`SELECT` isn't used, the output is similar to what would be produced by
-[`SELECT *`][select-star].
+In pipe syntax, the `SELECT` operator in a query is optional. The `SELECT`
+operator can be used near the end of a query to specify the list of output
+columns. The final query result contains the columns returned from the last pipe
+operator. If the `SELECT` operator isn't used to select specific columns, the
+output includes the full row, similar to what the
+[`SELECT *` statement][select-star] in standard syntax produces.
+ For [value tables][value-tables], the result is the
+row value, without field expansion.
 
-For some operations normally done with `SELECT` in standard syntax, pipe syntax
-supports other operators that might be more convenient. For example, aggregation
-is always done with [`AGGREGATE`][aggregate-pipes-operator]. To compute new
-columns, you can use [`EXTEND`][extend-pipes-operator]. To update specific
-columns, you can use [`SET`][set-pipes-operator] or
-[`DROP`][drop-pipes-operator].
+In pipe syntax, the `SELECT` clause doesn't perform aggregation. Use the
+[`AGGREGATE` operator][aggregate-pipe-operator] instead.
+
+For cases where `SELECT` would be used in standard syntax to rearrange columns,
+pipe syntax supports other operators:
+
++   The [`EXTEND` operator][extend-pipe-operator] adds columns.
++   The [`SET` operator][set-pipe-operator] updates the value of an existing
+    column.
++   The [`DROP` operator][drop-pipe-operator] removes columns.
++   The [`RENAME` operator][rename-pipe-operator] renames columns.
 
 **Example**
 
@@ -101,7 +146,8 @@ columns, you can use [`SET`][set-pipes-operator] or
 |> SELECT account_id AS Account
 
-### `EXTEND` +### `EXTEND` pipe operator +
 |> EXTEND expression [[AS] alias] [, ...]
@@ -120,11 +166,12 @@ Propagates the existing table and adds a computed column, similar to
 
--- Window function, with `OVER`
+-- Window function, with OVER
 |> EXTEND SUM(val) OVER (ORDER BY k) AS val_over_k
 
-### `SET` +### `SET` pipe operator +
 |> SET column_name = expression [, ...]
@@ -134,11 +181,11 @@ Propagates the existing table and adds a computed column, similar to
 
 Replaces the value of a column in the current table, similar to
 [`SELECT * REPLACE (expression AS column)`][select-replace] in standard syntax.
-Each reference column must exist exactly once in the input table. Table aliases
-(like `t.x`) still point to the original column.
+Each referenced column must exist exactly once in the input table.
 
-This pipe operator doesn't correspond to the [set operators][set-operators] in
-standard syntax, which combine input query results.
+After a `SET` operation, the referenced top-level columns (like `x`) are
+updated, but table aliases (like `t`) still refer to the original row values.
+Therefore, `t.x` will still refer to the original value.
 
 **Example**
 
@@ -146,7 +193,8 @@ standard syntax, which combine input query results.
 |> SET x = 5, y = CAST(y AS INT32)
 
-### `DROP` +### `DROP` pipe operator +
 |> DROP column_name [, ...]
@@ -155,11 +203,16 @@ standard syntax, which combine input query results.
 **Description**
 
 Removes listed columns from the current table, similar to
-[`SELECT * EXCEPT (column)`][select-except] in standard syntax. Each reference
-column must exist at least once in the input table.
+[`SELECT * EXCEPT (column)`][select-except] in standard syntax. Each
+referenced column must exist at least once in the input table.
+
+After a `DROP` operation, the referenced top-level columns (like `x`) are
+removed, but table aliases (like `t`) still refer to the original row values.
+Therefore, `t.x` will still refer to the original value.
 
-This pipe operator doesn't correspond to [`DROP` statements][drop-statement] in
-data definition language (DDL), which delete persistent schema objects.
+The `DROP` operator doesn't correspond to the
+[`DROP` statement][drop-statement] in data definition language (DDL), which
+deletes persistent schema objects.
 
 **Example**
 
@@ -167,7 +220,8 @@ data definition language (DDL), which delete persistent schema objects.
 |> DROP account_id, user_id
 
-### `RENAME` +### `RENAME` pipe operator +
 |> RENAME old_column_name [AS] new_column_name [, ...]
@@ -175,10 +229,14 @@ data definition language (DDL), which delete persistent schema objects.
 
 **Description**
 
-Renames specified columns. Each old column name must exist exactly once in the
-input table. The `RENAME` operator can't rename value table fields,
-pseudo-columns, range variables, or objects that aren't columns on the pipe
-input table.
+Renames specified columns. Each column to be renamed must exist exactly once in
+the input table. The `RENAME` operator can't rename value table fields,
+pseudo-columns, range variables, or objects that aren't columns in the input
+table.
+
+After a `RENAME` operation, the referenced top-level columns (like `x`) are
+renamed, but table aliases (like `t`) still refer to the original row
+values. Therefore, `t.x` will still refer to the original value.
 
 **Example**
 
@@ -186,7 +244,8 @@ input table.
 |> RENAME last_name AS surname
 
-### `AS` +### `AS` pipe operator +
 |> AS alias
@@ -194,14 +253,14 @@ input table.
 
 **Description**
 
-Introduces a table alias for the pipe input table, similar to applying
-[`AS alias`][using-aliases] on a table subquery in standard syntax. Any existing
-table aliases are removed and the new alias becomes the table alias for all
-columns in the row.
+Introduces a table alias for the input table, similar to applying the
+[`AS alias` clause][using-aliases] on a table subquery in standard syntax. Any
+existing table aliases are removed and the new alias becomes the table alias for
+all columns in the row.
 
-This operator can be useful after operators like
-[`SELECT`][select-pipes-operator], [`EXTEND`][extend-pipes-operator], or
-[`AGGREGATE`][aggregate-pipes-operator] that add columns but can't give table
+The `AS` operator can be useful after operators like
+[`SELECT`][select-pipe-operator], [`EXTEND`][extend-pipe-operator], or
+[`AGGREGATE`][aggregate-pipe-operator] that add columns but can't give table
 aliases to them.
 
 **Example**
@@ -212,7 +271,8 @@ aliases to them.
 |> WHERE table_alias.y = 10
 
-### `WHERE` +### `WHERE` pipe operator +
 |> WHERE boolean_expression
@@ -220,14 +280,15 @@ aliases to them.
 
 **Description**
 
-Same behavior as the [`WHERE` clause][where-clause] in standard syntax: Filters
-the results of the `FROM` clause.
+Filters the results of the input table. The `WHERE` operator behaves the same
+as the [`WHERE` clause][where-clause] in standard syntax.
 
-In pipe syntax, `WHERE` also replaces the [`HAVING` clause][having-clause] and
-[`QUALIFY` clause][qualify-clause]. For example, after performing aggregation
-with [`AGGREGATE`][aggregate-pipes-operator], use `WHERE` instead of `HAVING`.
-For [window functions][window-functions] inside a `QUALIFY` clause, use window
-functions inside a `WHERE` clause instead.
+In pipe syntax, the `WHERE` operator also replaces the
+[`HAVING` clause][having-clause] and [`QUALIFY` clause][qualify-clause] in
+standard syntax. For example, after performing aggregation with the
+[`AGGREGATE` operator][aggregate-pipe-operator], use the `WHERE` operator
+instead of the `HAVING` clause. For [window functions][window-functions] inside
+a `QUALIFY` clause, use window functions inside a `WHERE` clause instead.
 
 **Example**
 
@@ -235,7 +296,8 @@ functions inside a `WHERE` clause instead.
 |> WHERE assignee_user.email = 'username@email.com'
 
-### `LIMIT...OFFSET` +### `LIMIT` pipe operator +
 |> LIMIT count [OFFSET skip_rows]
@@ -243,18 +305,22 @@ functions inside a `WHERE` clause instead.
 
 **Description**
 
-Same behavior as the [`LIMIT` and `OFFSET` clause][limit-offset-clause] in
-standard syntax: Limits the number of rows to return in a query, and optionally
-skips over rows. In pipe syntax, the `OFFSET` clause is used only with the
-`LIMIT` clause.
+Limits the number of rows to return in a query, with an optional `OFFSET` clause
+to skip over rows. The `LIMIT` operator behaves the same as the
+[`LIMIT` and `OFFSET` clause][limit-offset-clause] in standard syntax.
 
-**Example**
+**Examples**
+
+
+|> LIMIT 10
+
 |> LIMIT 10 OFFSET 2
 
-### `AGGREGATE...GROUP BY` +### `AGGREGATE` pipe operator +
 -- Full-table aggregation
@@ -265,42 +331,55 @@ skips over rows. In pipe syntax, the `OFFSET` clause is used only with the
 |> AGGREGATE [aggregate_expression [[AS] alias] [, ...]]
    GROUP BY groupable_items [[AS] alias] [, ...]
 
+
+-- Aggregation with grouping and shorthand ordering syntax
+|> AGGREGATE [aggregate_expression [order_suffix] [[AS] alias] [, ...]]
+   GROUP [AND ORDER] BY groupable_item [order_suffix] [[AS] alias] [, ...]
+
+order_suffix: {ASC | DESC} [{NULLS FIRST | NULLS LAST}]
+
**Description** Performs aggregation on data across grouped rows or an entire table. The `AGGREGATE` operator is similar to a query in standard syntax that contains a [`GROUP BY` clause][group-by-clause] or a `SELECT` list with -[aggregate functions][aggregate-functions] or both. +[aggregate functions][aggregate-functions] or both. In pipe syntax, the +`GROUP BY` clause is part of the `AGGREGATE` operator. Pipe syntax +doesn't support a standalone `GROUP BY` operator. Without the `GROUP BY` clause, the `AGGREGATE` operator performs full-table -aggregation and always produces exactly one output row. +aggregation and produces one output row. With the `GROUP BY` clause, the `AGGREGATE` operator performs aggregation with grouping, producing one row for each set of distinct values for the grouping expressions. -The `AGGREGATE` list corresponds to the aggregated expressions in a `SELECT` -list in standard syntax. Each expression in the `AGGREGATE` list must include -aggregation. Aggregate expressions can also include scalar expressions (for -example, `sqrt(SUM(x*x))`). Aliases are allowed. Window functions aren't -allowed, but [`EXTEND`][extend-pipes-operator] can be used before `AGGREGATE` to -compute window functions. - -The `GROUP BY` list corresponds to the `GROUP BY` clause in standard syntax. -Unlike in standard syntax, aliases can be assigned on `GROUP BY` items. Standard -modifiers like `GROUPING SETS`, `ROLLUP`, and `CUBE` are supported. - -The output columns from `AGGREGATE` include all grouping columns first, followed -by all aggregate columns, using the aliases assigned in either list as the +The `AGGREGATE` expression list corresponds to the aggregated expressions in a +`SELECT` list in standard syntax. Each expression in the `AGGREGATE` list must +include an aggregate function. Aggregate expressions can also include scalar +expressions (for example, `sqrt(SUM(x*x))`). Column aliases can be assigned +using the [`AS` operator][as-pipe-operator]. Window functions aren't allowed, +but the [`EXTEND` operator][extend-pipe-operator] can be used before the +`AGGREGATE` operator to compute window functions. + +The `GROUP BY` clause in the `AGGREGATE` operator corresponds to the `GROUP BY` +clause in standard syntax. Unlike in standard syntax, aliases can be assigned to +`GROUP BY` items. Standard grouping operators like `GROUPING SETS`, `ROLLUP`, +and `CUBE` are supported. + +The output columns from the `AGGREGATE` operator include all grouping columns +first, followed by all aggregate columns, using their assigned aliases as the column names. Unlike in standard syntax, grouping expressions aren't repeated across `SELECT` -and `GROUP BY`. In pipe syntax, the grouping expressions are listed only once, -in `GROUP BY`, and are automatically included as output columns. +and `GROUP BY` clauses. In pipe syntax, the grouping expressions are listed +once, in the `GROUP BY` clause, and are automatically included as output columns +for the `AGGREGATE` operator. -Because output columns are fully specified by this operator, `SELECT` isn't usually -needed after `AGGREGATE` to specify the columns to return. +Because output columns are fully specified by the `AGGREGATE` operator, the +`SELECT` operator isn't needed after the `AGGREGATE` operator unless +you want to produce a list of columns different from the default. **Examples** @@ -325,39 +404,40 @@ GROUP BY id, month
--- Aggregation in pipe syntax
+-- The same aggregation in pipe syntax
 FROM table
 |> AGGREGATE SUM(value) AS total
    GROUP BY id, EXTRACT(MONTH FROM date) AS month
 
-#### Shorthand syntax for `AGGREGATE` with `ORDER BY` +#### Shorthand ordering syntax with `AGGREGATE` + -Pipe syntax supports the following additional shorthand syntax for the -[`ORDER BY`][order-by-pipes-operator] pipe operator as part of `AGGREGATE` -without repeating the column list: +The `AGGREGATE` operator supports a shorthand ordering syntax, which is +equivalent to applying the [`ORDER BY` operator][order-by-pipe-operator] as part +of the `AGGREGATE` operator without repeating the column list:
-|> AGGREGATE [
-     aggregate_expression [{ASC | DESC} {NULLS FIRST | NULLS LAST}] [[AS] alias]
-     [, ...]]
-   GROUP [AND ORDER] BY
-     groupable_item [{ASC | DESC} {NULLS FIRST| NULLS LAST}] [[AS] alias]
-     [, ...]
+-- Aggregation with grouping and shorthand ordering syntax
+|> AGGREGATE [aggregate_expression [order_suffix] [[AS] alias] [, ...]]
+   GROUP [AND ORDER] BY groupable_item [order_suffix] [[AS] alias] [, ...]
+
+order_suffix: {ASC | DESC} [{NULLS FIRST | NULLS LAST}]
 
-The `GROUP AND ORDER BY` clause is equivalent to an `ORDER BY` on all groupable -items. By default, the groupable items are sorted in ascending order with null -values first. Ordering suffixes like `ASC`, `DESC`, `NULLS FIRST`, and -`NULLS LAST` can be used for other orders. +The `GROUP AND ORDER BY` clause is equivalent to an `ORDER BY` clause on all +`groupable_items`. By default, each `groupable_item` is sorted in ascending +order with `NULL` values first. Other ordering suffixes like `DESC` or `NULLS +LAST` can be used for other orders. -Without `GROUP AND ORDER BY`, the `ASC` or `DESC` suffixes can be added on -individual columns in the `GROUP BY` or `AGGREGATE` lists or both. The -`NULLS FIRST` or `NULLS LAST` suffixes can be used to further modify sorting. +Without the `GROUP AND ORDER BY` clause, the `ASC` or `DESC` suffixes can be +added on individual columns in the `GROUP BY` list or `AGGREGATE` list or both. +The `NULLS FIRST` and `NULLS LAST` suffixes can be used to further modify `NULL` +sorting. -If any of these ordering suffixes are present, the syntax behavior is equivalent -to adding an `ORDER BY` that includes all of the suffixed columns, with the -grouping columns first, matching the left-to-right output column order. +Adding these suffixes is equivalent to adding an `ORDER BY` clause that includes +all of the suffixed columns with the suffixed grouping columns first, matching +the left-to-right output column order. **Examples** @@ -379,7 +459,8 @@ The ordering in the previous example is equivalent to using The ordering in the previous example is equivalent to using `|> ORDER BY last_name ASC, COUNT(*) DESC`. -### `ORDER BY` +### `ORDER BY` pipe operator +
 |> ORDER BY expression [sort_options] [, ...]
@@ -387,13 +468,14 @@ The ordering in the previous example is equivalent to using
 
 **Description**
 
-Same behavior as the [`ORDER BY` clause][order-by-clause] in standard syntax:
-Sorts results by a list of expressions. Suffixes like `ASC`, `DESC`, and
-`NULLS LAST` are supported for customizing the ordering for each expression.
+Sorts results by a list of expressions. The `ORDER BY` operator behaves the same
+as the [`ORDER BY` clause][order-by-clause] in standard syntax. Suffixes like
+`ASC`, `DESC`, and `NULLS LAST` are supported for customizing the ordering for
+each expression.
 
-For queries with [`AGGREGATE...GROUP BY`][aggregate-pipes-operator] and
-`ORDER BY` together, pipe syntax also supports a `GROUP AND ORDER BY` clause and
-other [shorthand ordering suffixes][order-by-with-aggregate] for aggregation.
+In pipe syntax, the [`AGGREGATE` operator][aggregate-pipe-operator] also
+supports [shorthand ordering suffixes][shorthand-order-pipe-syntax] to
+apply `ORDER BY` behavior more concisely as part of aggregation.
 
 **Example**
 
@@ -401,26 +483,28 @@ other [shorthand ordering suffixes][order-by-with-aggregate] for aggregation.
 |> ORDER BY last_name DESC
 
-### `JOIN` +### `JOIN` pipe operator +
-|> [join_type] JOIN from_item [[AS] alias] [on_clause | using_clause]
+|> [join_type] JOIN from_item [[AS] alias] [{on_clause | using_clause}]
 
**Description** -Same behavior as the [`JOIN` operation][join-operation] in standard syntax: -Merges two items so that they can be queried as one source. The join operation -treats the pipe input table as the left side of the join and the `JOIN` argument -as the right side of the join. Standard join inputs are supported, including -tables, subqueries, `UNNEST`, and table-valued function (TVF) calls. Standard -join modifiers like `LEFT`, `INNER`, and `CROSS` are allowed before `JOIN`. +Joins rows from the input table with rows from a second table provided as an +argument. The `JOIN` operator behaves the same as the +[`JOIN` operation][join-operation] in standard syntax. The input table is the +left side of the join and the `JOIN` argument is the right side of the join. +Standard join inputs are supported, including tables, subqueries, `UNNEST` +operations, and table-valued function (TVF) calls. Standard join modifiers like +`LEFT`, `INNER`, and `CROSS` are allowed before the `JOIN` keyword. -An alias can be assigned to the input item on the right side of the join, but -not to the pipe input table on the left side of the join. If an alias on the -pipe input table is needed, perhaps to disambiguate columns in an -[`ON` expression][on-clause], then an alias can be added using -[`AS`][as-pipes-operator] before the `JOIN`. +An alias can be assigned to the input table on the right side of the join, but +not to the input table on the left side of the join. If an alias on the +input table is needed, perhaps to disambiguate columns in an +[`ON` expression][on-clause], then an alias can be added using the +[`AS` operator][as-pipe-operator] before the `JOIN` arguments. **Example** @@ -429,7 +513,8 @@ pipe input table is needed, perhaps to disambiguate columns in an ON bug_table.component_id = CAST(components.component_id AS int64) -### `CALL` +### `CALL` pipe operator +
 |> CALL table_function (argument [, ...]) [[AS] alias]
@@ -437,13 +522,14 @@ pipe input table is needed, perhaps to disambiguate columns in an
 
 **Description**
 
-Calls a [table-valued function][tvf] (TVF), similar to [table function
-calls][table-function-calls] in standard syntax. TVFs in standard syntax can be
-called in the `FROM` clause or in a `JOIN` operation. These are both allowed in
-pipe syntax as well.
+Calls a [table-valued function][tvf] (TVF), similar to
+[table function calls][table-function-calls] in standard syntax.
+
+TVFs in standard syntax can be called in the `FROM` clause or in a `JOIN`
+operation. These are both allowed in pipe syntax as well.
 
 In pipe syntax, TVFs that take a table argument can also be called with the
-`CALL` operator. The first table argument comes from the pipe input table and
+`CALL` operator. The first table argument comes from the input table and
 must be omitted in the arguments. An optional table alias can be added for the
 output table.
 
@@ -469,7 +555,8 @@ SELECT * FROM table
 |> CALL tvf(arg1, arg2)
 
-### `WINDOW` +### `WINDOW` pipe operator +
 |> WINDOW window_expression [[AS] alias] [, ...]
@@ -483,9 +570,9 @@ existing rows, similar to calling [window functions][window-functions] in a
 window expression must include a window function with an
 [`OVER` clause][over-clause].
 
-The [`EXTEND`][extend-pipes-operator] pipe operator also supports window
-expressions. Using `EXTEND` with window functions is usually preferred instead
-of `WINDOW`.
+The [`EXTEND` operator][extend-pipe-operator] is recommended for window
+functions instead of the `WINDOW` operator because it also supports window
+expressions and covers the same use cases.
 
 **Example**
 
@@ -493,7 +580,8 @@ of `WINDOW`.
 |> WINDOW SUM(val) OVER (ORDER BY k)
 
-### `TABLESAMPLE` +### `TABLESAMPLE` pipe operator +
 |> TABLESAMPLE sample_method (sample_size {PERCENT | ROWS}) [, ...]
@@ -501,8 +589,9 @@ of `WINDOW`.
 
 **Description**
 
-Same behavior as the [`TABLESAMPLE` operator][tablesample-operator] in standard
-syntax: Selects a random sample of rows from the input table.
+Selects a random sample of rows from the input table. The `TABLESAMPLE` pipe
+operator behaves the same as [`TABLESAMPLE` operator][tablesample-operator] in
+standard syntax.
 
 **Example**
 
@@ -510,7 +599,8 @@ syntax: Selects a random sample of rows from the input table.
 |> TABLESAMPLE BERNOULLI (0.1 PERCENT)
 
-### `PIVOT` +### `PIVOT` pipe operator +
 |> PIVOT (aggregate_expression FOR input_column IN (pivot_column [, ...])) [[AS] alias]
@@ -518,8 +608,8 @@ syntax: Selects a random sample of rows from the input table.
 
 **Description**
 
-Same behavior as the [`PIVOT` operator][pivot-operator] in standard syntax:
-Rotates rows into columns.
+Rotates rows into columns. The `PIVOT` pipe operator behaves the same as the
+[`PIVOT` operator][pivot-operator] in standard syntax.
 
 **Example**
 
@@ -528,7 +618,8 @@ Rotates rows into columns.
 |> PIVOT (SUM(num_users) FOR username IN ('Jeff', 'Jeffrey', 'Jeffery'))
 
-### `UNPIVOT` +### `UNPIVOT` pipe operator +
 |> UNPIVOT (values_column FOR name_column IN (column_to_unpivot [, ...])) [[AS] alias]
@@ -536,8 +627,8 @@ Rotates rows into columns.
 
 **Description**
 
-Same behavior as the [`UNPIVOT` operator][unpivot-operator] in standard syntax:
-Rotates columns into rows.
+Rotates columns into rows. The `UNPIVOT` pipe operator behaves the same as the
+[`UNPIVOT` operator][unpivot-operator] in standard syntax.
 
 **Example**
 
@@ -546,7 +637,8 @@ Rotates columns into rows.
 |> ORDER BY year, cnt
 
-### `ASSERT` +### `ASSERT` pipe operator +
 |> ASSERT expression [, payload_expression [, ...]]
@@ -571,8 +663,7 @@ assertion expression.
 
 The `ASSERT` operator has no equivalent operation in standard syntax.
  The [`ASSERT` statement][assert-statement] is
-a related feature that verifies that a single expression
-is true.
+a related feature that verifies that a single expression is true.
 
 **Example**
 
@@ -586,8 +677,12 @@ FROM table
 
 [query-syntax]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md
 
+[from-queries]: #from_queries
+
 [from-clause]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md#from_clause
 
+[select-as-value]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md#select_as_value
+
 [select-clause]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md#select_list
 
 [select-star]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md#select_
@@ -602,21 +697,23 @@ FROM table
 
 [where-clause]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md#where_clause
 
-[aggregate-pipes-operator]: #aggregategroup_by
+[aggregate-pipe-operator]: #aggregate_pipe_operator
 
-[extend-pipes-operator]: #extend
+[extend-pipe-operator]: #extend_pipe_operator
 
-[select-pipes-operator]: #select
+[select-pipe-operator]: #select_pipe_operator
 
-[set-pipes-operator]: #set
+[set-pipe-operator]: #set_pipe_operator
 
-[drop-pipes-operator]: #drop
+[drop-pipe-operator]: #drop_pipe_operator
 
-[as-pipes-operator]: #as
+[rename-pipe-operator]: #rename_pipe_operator
 
-[order-by-pipes-operator]: #order_by
+[as-pipe-operator]: #as_pipe_operator
 
-[order-by-with-aggregate]: #shorthand_syntax_for_aggregate_with_order_by
+[order-by-pipe-operator]: #order_by_pipe_operator
+
+[shorthand-order-pipe-syntax]: #shorthand_order_pipe_syntax
 
 [limit-offset-clause]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md#limit_and_offset_clause
 
@@ -640,7 +737,7 @@ FROM table
 
 [over-clause]: https://github.com/google/zetasql/blob/master/docs/window-function-calls.md#def_over_clause
 
-[window-pipes-operator]: #window
+[window-pipe-operator]: #window_pipe_operator
 
 [table-function-calls]: https://github.com/google/zetasql/blob/master/docs/query-syntax.md#table-function-calls
 
@@ -656,5 +753,7 @@ FROM table
 
 [assert-statement]: https://github.com/google/zetasql/blob/master/docs/debugging-statements.md#assert
 
+[value-tables]: https://github.com/google/zetasql/blob/master/docs/data-model.md#value_tables
+
 
 
diff --git a/docs/protocol_buffer_functions.md b/docs/protocol_buffer_functions.md
index a710695de..29d89bb19 100644
--- a/docs/protocol_buffer_functions.md
+++ b/docs/protocol_buffer_functions.md
@@ -324,13 +324,14 @@ FROM AlbumList;
 ### `FILTER_FIELDS`
 
 ```sql
-FILTER_FIELDS(proto_expression, proto_field_list [, reset_fields_named_arg])
+FILTER_FIELDS(
+  proto_expression,
+  proto_field_list
+  [, reset_cleared_required_fields => { TRUE | FALSE } ]
+)
 
 proto_field_list:
   {+|-}proto_field_path[, ...]
-
-reset_fields_named_arg:
-  RESET_CLEARED_REQUIRED_FIELDS => { TRUE | FALSE }
 ```
 
 **Description**
@@ -349,8 +350,7 @@ Input values:
 + `proto_field_path`: The protocol buffer field to include or exclude.
   If the field represents an [extension][querying-proto-extensions], you can use
   syntax for that extension in the path.
-+ `reset_fields_named_arg`: You can optionally add the
- `RESET_CLEARED_REQUIRED_FIELDS` named argument.
++ `reset_cleared_required_fields`: Named argument with a `BOOL` value.
   If not explicitly set, `FALSE` is used implicitly.
   If `FALSE`, you must include all protocol buffer `required` fields in the
   `FILTER_FIELDS` function. If `TRUE`, you do not need to include all required
diff --git a/docs/string_functions.md b/docs/string_functions.md
index 6089ce2f3..9d204c05d 100644
--- a/docs/string_functions.md
+++ b/docs/string_functions.md
@@ -1009,7 +1009,11 @@ FROM Employees;
 ### `EDIT_DISTANCE`
 
 ```sql
-EDIT_DISTANCE(value1, value2, [max_distance => max_distance_value])
+EDIT_DISTANCE(
+  value1,
+  value2,
+  [ max_distance => max_distance_value ]
+)
 ```
 
 **Description**
@@ -1021,8 +1025,8 @@ Computes the [Levenshtein distance][l-distance] between two `STRING` or
 
 +   `value1`: The first `STRING` or `BYTES` value to compare.
 +   `value2`: The second `STRING` or `BYTES` value to compare.
-+   `max_distance`: Optional mandatory-named argument. Takes a non-negative
-    `INT64` value that represents the maximum distance between the two values
++   `max_distance`: A named argument with a `INT64` value that is greater than
+    or equal to zero. Represents the maximum distance between the two values
     to compute.
 
     If this distance is exceeded, the function returns this value.
diff --git a/docs/user-defined-aggregates.md b/docs/user-defined-aggregates.md
index 5ee4b4fe8..320faed6f 100644
--- a/docs/user-defined-aggregates.md
+++ b/docs/user-defined-aggregates.md
@@ -7,7 +7,7 @@
 
 ZetaSQL supports user-defined aggregate functions (UDAs).
 
-An SQL UDA is a user-defined function that
+A SQL UDA is a user-defined function that
 performs a calculation on a group of rows at a time and returns the result of
 that calculation as a single value.
 
diff --git a/docs/user-defined-functions.md b/docs/user-defined-functions.md
index c12c68a10..1439bd528 100644
--- a/docs/user-defined-functions.md
+++ b/docs/user-defined-functions.md
@@ -6,16 +6,15 @@
 
 ZetaSQL supports user-defined functions (UDFs).
 
-A UDF enables you to
-create a function using another SQL expression or another programming
-language, such as JavaScript or Lua. These functions accept columns of input
-and perform actions, returning the result of those actions as a value.
+A UDF lets you create a function using another SQL expression or another
+programming language, such as JavaScript or Lua. These functions accept columns
+of input and perform actions, returning the result of those actions as a value.
 
 ## SQL UDFs 
 
 
-A SQL user-defined function (UDF) is a user-defined function that operates on
-one row at a time and returns the result of that calculation as a single value.
+A SQL user-defined function (UDF) operates on one row at a time and returns the
+result of that calculation as a single value.
 
 All of the arguments are expressions that are computed in the context of a
 single row.
@@ -173,8 +172,8 @@ FROM
 ## JavaScript UDFs 
 
 
-A JavaScript UDF is a JavaScript user-defined function that executes
-JavaScript code and returns the result as a single value.
+A JavaScript user-defined function (UDF) runs JavaScript code and returns the
+result as a single value.
 
 ### Create a JavaScript UDF
 
@@ -749,8 +748,8 @@ FROM Input AS t;
 ## Lua UDFs 
 
 
-A Lua UDF is a SQL user-defined function that executes Lua code and returns the
-result as a single value.
+A Lua user-defined function (UDF) runs Lua code and returns the result as a
+single value.
 
 ### Create a Lua UDF
 
diff --git a/execute_query.md b/execute_query.md
index c24af3810..20e4d8ee9 100644
--- a/execute_query.md
+++ b/execute_query.md
@@ -18,7 +18,7 @@ optional `--port` flag (the default port is 8080).
 execute_query --web
 ```
 
-[!NOTE]
+> [!NOTE]
 > Note that this tool is intended primarily as a way to explore the language and
 > debug its implementation. It executes queries using the internal reference
 > implementation which is intended primarily for testing. It is not performant
@@ -106,3 +106,9 @@ DDL statements that update the catalog:
 SQL functions and TVFs will be executable.  Non-SQL functions and TVFs don't
 have an implementation, so queries using them can be analyzed but can't be
 executed.
+
+### Example queries
+
+There are some runnable example queries in
+[`zetasql/examples/tpch`](zetasql/examples/tpch) and
+[`zetasql/examples/pipe_queries`](zetasql/examples/pipe_queries).
diff --git a/java/com/google/zetasql/BuiltinDescriptorPool.java b/java/com/google/zetasql/BuiltinDescriptorPool.java
index a16ff6a40..0549b115a 100644
--- a/java/com/google/zetasql/BuiltinDescriptorPool.java
+++ b/java/com/google/zetasql/BuiltinDescriptorPool.java
@@ -35,7 +35,7 @@
 import com.google.zetasql.functions.ZetaSQLNormalizeMode.NormalizeMode;
 import com.google.zetasql.functions.ZetaSQLRangeSessionizeMode.RangeSessionizeEnums.RangeSessionizeMode;
 import com.google.zetasql.functions.ZetaSQLRoundingMode.RoundingMode;
-import com.google.zetasql.functions.ZetaSQLUnsupportedFields.UnsupportedFields;
+import com.google.zetasql.functions.ZetaSQLUnsupportedFields.UnsupportedFieldsEnum.UnsupportedFields;
 import com.google.type.Date;
 import com.google.type.LatLng;
 import com.google.type.TimeOfDay;
diff --git a/javatests/com/google/zetasql/BUILD b/javatests/com/google/zetasql/BUILD
index 2136164f3..c16c866c1 100644
--- a/javatests/com/google/zetasql/BUILD
+++ b/javatests/com/google/zetasql/BUILD
@@ -32,7 +32,6 @@ java_library(
         "@maven//:com_google_guava_guava_testlib",
         "//java/com/google/zetasql:analyzer",
         "//java/com/google/zetasql:client",
-        "//zetasql/public:annotation_java_proto",
         "//java/com/google/zetasql:types",
         "//java/com/google/zetasql/resolvedast",
         "//zetasql/local_service:local_service_java_proto",
@@ -40,6 +39,7 @@ java_library(
         "//zetasql/proto:options_java_proto",
         "//zetasql/proto:simple_catalog_java_proto",
         # "//zetasql/public/functions:datetime_java_proto",
+        "//zetasql/public:annotation_java_proto",
         "//zetasql/public:builtin_function_java_proto",
         "//zetasql/public:deprecation_warning_java_proto",
         "//zetasql/public:error_location_java_proto",
diff --git a/zetasql/analyzer/resolver.h b/zetasql/analyzer/resolver.h
index a16e4262c..be4461920 100644
--- a/zetasql/analyzer/resolver.h
+++ b/zetasql/analyzer/resolver.h
@@ -89,6 +89,8 @@
 #include "absl/types/optional.h"
 #include "absl/types/span.h"
 #include "absl/types/variant.h"
+#include "google/protobuf/descriptor.h"
+#include "zetasql/base/general_trie.h"
 #include "zetasql/base/ret_check.h"
 
 namespace zetasql {
@@ -2190,25 +2192,19 @@ class Resolver {
   };
 
   struct ResolvedBuildProtoArg {
-    ResolvedBuildProtoArg(
-        const ASTNode* ast_location_in,
-        std::unique_ptr expr_in,
-        std::unique_ptr alias_or_ast_path_expr_in,
-        const google::protobuf::FieldDescriptor* field_descriptor_in = nullptr,
-        const Type* proto_field_type_in = nullptr)
+    ResolvedBuildProtoArg(const ASTNode* ast_location_in,
+                          std::unique_ptr expr_in,
+                          const Type* leaf_field_type_in,
+                          const std::vector
+                              field_descriptor_path_in)
         : ast_location(ast_location_in),
           expr(std::move(expr_in)),
-          alias_or_ast_path_expr(std::move(alias_or_ast_path_expr_in)),
-          field_descriptor(field_descriptor_in),
-          proto_field_type(proto_field_type_in) {}
+          leaf_field_type(leaf_field_type_in),
+          field_descriptor_path(field_descriptor_path_in) {}
     const ASTNode* ast_location;
     std::unique_ptr expr;
-    std::unique_ptr alias_or_ast_path_expr;
-
-    // The following two fields can be set if available so that they don't have
-    // to be computed again.
-    const google::protobuf::FieldDescriptor* field_descriptor;
-    const Type* proto_field_type;
+    const Type* leaf_field_type;
+    std::vector field_descriptor_path;
   };
 
   // Create a ResolvedMakeProto from a type and a vector of arguments.
@@ -2263,8 +2259,34 @@ class Resolver {
       const ProtoType* root_type,
       std::vector* field_descriptors);
 
-  // Parses , filling  and/or 
-  // as appropriate, with the struct and proto fields that correspond to each of
+  // The output of FindFieldsFromPathExpression.
+  struct FindFieldsOutput {
+    struct StructFieldInfo {
+      explicit StructFieldInfo(int field_index_in,
+                               const StructType::StructField* field_in)
+          : field_index(field_index_in), field(field_in) {}
+
+      int field_index;
+      const StructType::StructField* field;
+    };
+    std::vector struct_path;
+    std::vector field_descriptor_path;
+  };
+
+  void AppendFindFieldsOutput(const FindFieldsOutput& to_append,
+                              FindFieldsOutput* output) {
+    output->struct_path.insert(output->struct_path.end(),
+                               to_append.struct_path.begin(),
+                               to_append.struct_path.end());
+    output->field_descriptor_path.insert(
+        output->field_descriptor_path.end(),
+        to_append.field_descriptor_path.begin(),
+        to_append.field_descriptor_path.end());
+  }
+
+  // Parses , filling in  and/or
+  //  in FindFieldsOutput (returned as a value) as
+  // appropriate, with the struct and proto fields that correspond to each of
   // the fields in the path. The first field is looked up with respect to
   // . Both  and  may be populated if
   //  contains accesses to fields of a proto nested within a
@@ -2273,23 +2295,21 @@ class Resolver {
   // . If  is true, the
   //  can traverse array or repeated fields. 
   // is for generating error messages.
-  absl::Status FindFieldsFromPathExpression(
+  absl::StatusOr FindFieldsFromPathExpression(
       absl::string_view function_name,
       const ASTGeneralizedPathExpression* generalized_path,
-      const Type* root_type, bool can_traverse_array_fields,
-      std::vector>* struct_path,
-      std::vector* field_descriptors);
-
-  // Returns a vector of StructFields and their indexes corresponding to the
-  // fields in the path represented by . The first field in the
-  // returned vector is looked up with respect to . If a field of
-  // proto type is encountered in the path, it will be inserted into
-  //  and the function will return without examining any further
-  // fields in the path.
+      const Type* root_type, bool can_traverse_array_fields);
+
+  // Fills in a vector of FindFieldsOutput::StructFieldInfo which contains
+  // StructFields and their indexes corresponding to the fields in the path
+  // represented by . The first field in the returned vector is
+  // looked up with respect to . If a field of proto type is
+  // encountered in the path, it will be inserted into  and the
+  // function will return without examining any further fields in the path.
   absl::Status FindStructFieldPrefix(
       absl::Span path_vector,
       const StructType* root_struct,
-      std::vector>* struct_path);
+      std::vector* struct_path);
 
   // Looks up a proto message type name first in  and then in
   // . Returns NULL if the type name is not found. If
@@ -4027,14 +4047,25 @@ class Resolver {
       ExprResolutionInfo* expr_resolution_info,
       std::unique_ptr* resolved_expr_out);
 
-  absl::Status ResolveBracedConstructorFieldValue(
-      const ASTBracedConstructorFieldValue* ast_braced_constructor_field_value,
-      const Type* inferred_type, ExprResolutionInfo* expr_resolution_info,
-      std::unique_ptr* resolved_expr_out);
+  // A container that has state about the 'lhs' part of a braced constructor
+  // field.
+  struct BracedConstructorField {
+    const ASTNode* location;
+    FindFieldsOutput field_info;
+  };
+
+  // Resolves the 'lhs' of a braced constructor field and returns a container
+  // with state.
+  absl::StatusOr ResolveBracedConstructorLhs(
+      const ASTBracedConstructorLhs* ast_braced_constructor_lhs,
+      const ProtoType* parent_type);
 
+  // Resolves a braced constructor field and returns a container that has state
+  // to build the protocol buffer field.
   absl::StatusOr ResolveBracedConstructorField(
       const ASTBracedConstructorField* ast_braced_constructor_field,
-      const ProtoType* parent_type, int field_index,
+      const ProtoType* parent_type, int field_index, bool allow_field_paths,
+      const ResolvedExpr* update_constructor_expr_to_modify,
       ExprResolutionInfo* expr_resolution_info);
 
   absl::Status ResolveBracedConstructor(
@@ -4940,7 +4971,7 @@ class Resolver {
       const ASTTVF* ast_tvf, const std::vector& arg_locations,
       const SignatureMatchResult& signature_match_result,
       const TableValuedFunction& tvf_catalog_entry, const std::string& tvf_name,
-      const std::vector& input_arg_types, int signature_idx);
+      absl::Span input_arg_types, int signature_idx);
 
   // Struct to control the features to be resolved by
   // ResolveCreateTableStmtBaseProperties.
@@ -5200,6 +5231,35 @@ class Resolver {
           resolved_array_expr_list,
       std::vector& resolved_element_column_list);
 
+  // Fills in `last_field_type` with the type of a the last element found in
+  // FindFieldsOutput.
+  static absl::Status GetLastSeenFieldType(
+      const FindFieldsOutput& output,
+      absl::Span catalog_name_path,
+      TypeFactory* type_factory, const Type** last_field_type);
+
+  // Builds the string representation of a field path using `struct_path_prefix`
+  // and `proto_field_path_suffix` and attempts to add it to `field_path_trie`.
+  // If non-empty, the field path is expanded starting with the fields in
+  // `struct_path_prefix`. Returns an error if this path string overlaps with a
+  // path that is already present in `field_path_trie`. For example,
+  // message.nested and message.nested.field are overlapping field paths, but
+  // message.nested.field1 and message.nested.field2 are not overlapping. Two
+  // paths that modify the same OneOf field are considered overlapping only when
+  // the language feature FEATURE_V_1_4_REPLACE_FIELDS_ALLOW_MULTI_ONEOF is not
+  // enabled. `oneof_path_to_full_path` maps from paths of OneOf fields that
+  // have already been modified to the corresponding path expression that
+  // accessed the OneOf path. If `proto_field_path_suffix` modifies a OneOf
+  // field that has not already been modified, it will be added to
+  // `oneof_path_to_full_path`.
+  static absl::Status AddToFieldPathTrie(
+      const LanguageOptions& language_options, const ASTNode* path_location,
+      const std::vector& struct_path_prefix,
+      const std::vector&
+          proto_field_path_suffix,
+      absl::flat_hash_map* oneof_path_to_full_path,
+      zetasql_base::GeneralTrie* field_path_trie);
+
   friend class AnalyticFunctionResolver;
   friend class FunctionResolver;
   friend class FunctionResolverTest;
diff --git a/zetasql/analyzer/resolver_dml.cc b/zetasql/analyzer/resolver_dml.cc
index 83b0639c6..1394293ba 100644
--- a/zetasql/analyzer/resolver_dml.cc
+++ b/zetasql/analyzer/resolver_dml.cc
@@ -2354,7 +2354,8 @@ absl::Status Resolver::ResolveGeneratedColumnsForDml(
   CycleDetector* cycle_detector =
       analyzer_options_.find_options().cycle_detector();
   ZETASQL_RET_CHECK(cycle_detector != nullptr)
-      << "Cycle detector needs to be set in analyzer options";
+      << "Cycle detector needs to be set in AnalyzerOptions when analyzing "
+         "DML statements";
 
   std::vector
       topologically_sorted_generated_columns;
diff --git a/zetasql/analyzer/resolver_expr.cc b/zetasql/analyzer/resolver_expr.cc
index 773eb4413..4b1ba66d3 100644
--- a/zetasql/analyzer/resolver_expr.cc
+++ b/zetasql/analyzer/resolver_expr.cc
@@ -308,23 +308,10 @@ absl::Status Resolver::ResolveBuildProto(
 
   for (int i = 0; i < arguments->size(); ++i) {
     ResolvedBuildProtoArg& argument = (*arguments)[i];
-    const AliasOrASTPathExpression& alias_or_ast_path_expr =
-        *argument.alias_or_ast_path_expr;
-    IdString field_alias;  // Empty if we are using a path expression.
-    const google::protobuf::FieldDescriptor* field = argument.field_descriptor;
-    const Type* proto_field_type = argument.proto_field_type;
-    if (field == nullptr || proto_field_type == nullptr) {
-      ZETASQL_ASSIGN_OR_RETURN(
-          field,
-          FindFieldDescriptor(descriptor, alias_or_ast_path_expr,
-                              argument.ast_location, i, argument_description));
-      ZETASQL_ASSIGN_OR_RETURN(proto_field_type,
-                       FindProtoFieldType(field, argument.ast_location,
-                                          proto_type->CatalogNamePath()));
-    }
-
-    if (alias_or_ast_path_expr.kind() == AliasOrASTPathExpression::ALIAS) {
-      field_alias = alias_or_ast_path_expr.alias();
+    const google::protobuf::FieldDescriptor* field =
+        argument.field_descriptor_path.back();
+    const Type* proto_field_type = argument.leaf_field_type;
+    if (!field->is_extension()) {
       if (field->is_required()) {
         // Note that required fields may be listed twice, so this erase can
         // be a no-op.  This condition will eventually trigger a duplicate
@@ -339,13 +326,13 @@ absl::Status Resolver::ResolveBuildProto(
       // effort to print simple error messages for common cases.
       const google::protobuf::FieldDescriptor* other_field = insert_result.first->second;
       if (other_field->full_name() == field->full_name()) {
-        if (!field_alias.empty()) {
+        if (!field->is_extension()) {
           // Very common case (regular field accessed twice) or a very weird
           // case (regular field accessed sometime after accessing an extension
           // field with the same tag number and name).
           return MakeSqlErrorAt(argument.ast_location)
                  << query_description << " has duplicate column name "
-                 << ToIdentifierLiteral(field_alias)
+                 << ToIdentifierLiteral(field->name())
                  << " so constructing proto field " << field->full_name()
                  << " is ambiguous";
         } else {
@@ -2467,33 +2454,30 @@ static absl::Status MakeCannotAccessFieldError(
          << " on a value with type " << invalid_type_name;
 }
 
-static absl::Status GetLastSeenFieldType(
-    const std::vector>*
-        struct_path,
-    const std::vector& field_descriptors,
+absl::Status Resolver::GetLastSeenFieldType(
+    const Resolver::FindFieldsOutput& output,
     absl::Span catalog_name_path, TypeFactory* type_factory,
     const Type** last_field_type) {
-  if (!field_descriptors.empty()) {
-    return type_factory->GetProtoFieldType(field_descriptors.back(),
+  if (!output.field_descriptor_path.empty()) {
+    return type_factory->GetProtoFieldType(output.field_descriptor_path.back(),
                                            catalog_name_path, last_field_type);
   }
   // No proto fields have been extracted, therefore return the type of the
   // last struct field.
-  ZETASQL_RET_CHECK(struct_path);
-  ZETASQL_RET_CHECK(!struct_path->empty());
-  *last_field_type = struct_path->back().second->type;
+  ZETASQL_RET_CHECK(!output.struct_path.empty());
+  *last_field_type = output.struct_path.back().field->type;
   return absl::OkStatus();
 }
 
-absl::Status Resolver::FindFieldsFromPathExpression(
+absl::StatusOr
+Resolver::FindFieldsFromPathExpression(
     absl::string_view function_name,
     const ASTGeneralizedPathExpression* generalized_path, const Type* root_type,
-    bool can_traverse_array_fields,
-    std::vector>* struct_path,
-    std::vector* field_descriptors) {
+    bool can_traverse_array_fields) {
   RETURN_ERROR_IF_OUT_OF_STACK_SPACE();
   ZETASQL_RET_CHECK(generalized_path != nullptr);
   ZETASQL_RET_CHECK(root_type->IsStructOrProto());
+  Resolver::FindFieldsOutput output;
   switch (generalized_path->node_kind()) {
     case AST_PATH_EXPRESSION: {
       auto path_expression = generalized_path->GetAsOrDie();
@@ -2503,24 +2487,26 @@ absl::Status Resolver::FindFieldsFromPathExpression(
               const google::protobuf::FieldDescriptor* field_descriptor,
               FindExtensionFieldDescriptor(path_expression,
                                            root_type->AsProto()->descriptor()));
-          field_descriptors->push_back(field_descriptor);
+          output.field_descriptor_path.push_back(field_descriptor);
         } else {
           ZETASQL_RETURN_IF_ERROR(FindFieldDescriptors(path_expression->names(),
                                                root_type->AsProto(),
-                                               field_descriptors));
+                                               &output.field_descriptor_path));
         }
       } else {
-        ZETASQL_RET_CHECK(struct_path);
-        ZETASQL_RETURN_IF_ERROR(FindStructFieldPrefix(
-            path_expression->names(), root_type->AsStruct(), struct_path));
-        const Type* last_struct_field_type = struct_path->back().second->type;
+        ZETASQL_RETURN_IF_ERROR(FindStructFieldPrefix(path_expression->names(),
+                                              root_type->AsStruct(),
+                                              &output.struct_path));
+        const Type* last_struct_field_type =
+            output.struct_path.back().field->type;
         if (last_struct_field_type->IsProto() &&
-            path_expression->num_names() != struct_path->size()) {
+            path_expression->num_names() != output.struct_path.size()) {
           // There are proto extractions in this path expression.
           ZETASQL_RETURN_IF_ERROR(FindFieldDescriptors(
               path_expression->names().last(path_expression->num_names() -
-                                            struct_path->size()),
-              last_struct_field_type->AsProto(), field_descriptors));
+                                            output.struct_path.size()),
+              last_struct_field_type->AsProto(),
+              &output.field_descriptor_path));
         }
       }
       break;
@@ -2533,14 +2519,14 @@ absl::Status Resolver::FindFieldsFromPathExpression(
       auto generalized_path_expr =
           dot_generalized_ast->expr()
               ->GetAsOrNull();
-      ZETASQL_RETURN_IF_ERROR(FindFieldsFromPathExpression(
-          function_name, generalized_path_expr, root_type,
-          can_traverse_array_fields, struct_path, field_descriptors));
+      ZETASQL_ASSIGN_OR_RETURN(output, FindFieldsFromPathExpression(
+                                   function_name, generalized_path_expr,
+                                   root_type, can_traverse_array_fields));
 
       // The extension should be extracted from the last seen field in the path,
       // which must be of proto type.
       const Type* last_seen_type;
-      ZETASQL_RETURN_IF_ERROR(GetLastSeenFieldType(struct_path, *field_descriptors,
+      ZETASQL_RETURN_IF_ERROR(GetLastSeenFieldType(output,
                                            GetTypeCatalogNamePath(root_type),
                                            type_factory_, &last_seen_type));
       if (can_traverse_array_fields && last_seen_type->IsArray()) {
@@ -2557,7 +2543,7 @@ absl::Status Resolver::FindFieldsFromPathExpression(
                        FindExtensionFieldDescriptor(
                            dot_generalized_ast->path(),
                            last_seen_type->AsProto()->descriptor()));
-      field_descriptors->push_back(field_descriptor);
+      output.field_descriptor_path.push_back(field_descriptor);
       break;
     }
     case AST_DOT_IDENTIFIER: {
@@ -2569,14 +2555,14 @@ absl::Status Resolver::FindFieldsFromPathExpression(
       auto generalized_path_expr =
           dot_identifier_ast->expr()
               ->GetAsOrNull();
-      ZETASQL_RETURN_IF_ERROR(FindFieldsFromPathExpression(
-          function_name, generalized_path_expr, root_type,
-          can_traverse_array_fields, struct_path, field_descriptors));
+      ZETASQL_ASSIGN_OR_RETURN(output, FindFieldsFromPathExpression(
+                                   function_name, generalized_path_expr,
+                                   root_type, can_traverse_array_fields));
 
       // The field should be extracted from the last seen field in the path,
       // which must be of proto type.
       const Type* last_seen_type;
-      ZETASQL_RETURN_IF_ERROR(GetLastSeenFieldType(struct_path, *field_descriptors,
+      ZETASQL_RETURN_IF_ERROR(GetLastSeenFieldType(output,
                                            GetTypeCatalogNamePath(root_type),
                                            type_factory_, &last_seen_type));
       if (last_seen_type->IsArray() && IsFilterFields(function_name)) {
@@ -2591,7 +2577,7 @@ absl::Status Resolver::FindFieldsFromPathExpression(
       }
       ZETASQL_RETURN_IF_ERROR(FindFieldDescriptors({dot_identifier_ast->name()},
                                            last_seen_type->AsProto(),
-                                           field_descriptors));
+                                           &output.field_descriptor_path));
       break;
     }
     case AST_ARRAY_ELEMENT: {
@@ -2605,38 +2591,24 @@ absl::Status Resolver::FindFieldsFromPathExpression(
     }
   }
 
-  return absl::OkStatus();
+  return output;
 }
 
-// Builds the string representation of a field path using 'struct_path_prefix'
-// and 'proto_field_path_suffix' and attempts to add it to 'field_path_trie'. If
-// non-empty, the field path is expanded starting with the fields in
-// 'struct_path_prefix'. Returns an error if this path string overlaps with a
-// path that is already present in 'field_path_trie'. For example,
-// message.nested and message.nested.field are overlapping field paths, but
-// message.nested.field1 and message.nested.field2 are not overlapping. Two
-// paths that modify the same OneOf field are considered overlapping only when
-// the language feature FEATURE_V_1_4_REPLACE_FIELDS_ALLOW_MULTI_ONEOF is not
-// enabled. 'oneof_path_to_full_path' maps from paths of OneOf fields that have
-// already been modified to the corresponding path expression that accessed the
-// OneOf path. If 'proto_field_path_suffix' modifies a OneOf field that has not
-// already been modified, it will be added to 'oneof_path_to_full_path'.
-static absl::Status AddToFieldPathTrie(
+absl::Status Resolver::AddToFieldPathTrie(
     const LanguageOptions& language_options, const ASTNode* path_location,
-    const std::vector>&
-        struct_path_prefix,
+    const std::vector& struct_path_prefix,
     const std::vector& proto_field_path_suffix,
     absl::flat_hash_map* oneof_path_to_full_path,
     zetasql_base::GeneralTrie* field_path_trie) {
   std::string path_string;
   bool overlapping_oneof = false;
   std::string shortest_oneof_path;
-  for (const std::pair& struct_field :
+  for (const FindFieldsOutput::StructFieldInfo& struct_field :
        struct_path_prefix) {
     if (!path_string.empty()) {
       absl::StrAppend(&path_string, ".");
     }
-    absl::StrAppend(&path_string, struct_field.second->name);
+    absl::StrAppend(&path_string, struct_field.field->name);
   }
   for (const google::protobuf::FieldDescriptor* field : proto_field_path_suffix) {
     if (field->real_containing_oneof() != nullptr &&
@@ -2708,7 +2680,7 @@ static absl::Status AddToFieldPathTrie(
 absl::Status Resolver::FindStructFieldPrefix(
     absl::Span path_vector,
     const StructType* root_struct,
-    std::vector>* struct_path) {
+    std::vector* struct_path) {
   ZETASQL_RET_CHECK(root_struct != nullptr);
   const StructType* current_struct = root_struct;
   for (const ASTIdentifier* const current_field : path_vector) {
@@ -2716,7 +2688,7 @@ absl::Status Resolver::FindStructFieldPrefix(
       // There was an attempt to modify a field of a non-message type field.
       return MakeCannotAccessFieldError(
           current_field, current_field->GetAsString(),
-          struct_path->back().second->type->ShortTypeName(product_mode()),
+          struct_path->back().field->type->ShortTypeName(product_mode()),
           /*is_extension=*/false);
     }
     bool is_ambiguous = false;
@@ -2733,7 +2705,8 @@ absl::Status Resolver::FindStructFieldPrefix(
              << "Struct " << current_struct->ShortTypeName(product_mode())
              << " does not have field named " << current_field->GetAsString();
     }
-    struct_path->emplace_back(found_index, field);
+    struct_path->emplace_back(
+        Resolver::FindFieldsOutput::StructFieldInfo(found_index, field));
     if (field->type->IsProto()) {
       return absl::OkStatus();
     }
@@ -2917,20 +2890,19 @@ absl::Status Resolver::ResolveFilterFieldsFunctionCall(
     const ASTGeneralizedPathExpression* generalized_path_expression =
         unary->operand()->GetAs();
 
-    std::vector field_descriptor_path;
-
-    ZETASQL_RETURN_IF_ERROR(FindFieldsFromPathExpression(
-        "FILTER_FIELDS", generalized_path_expression, proto_to_modify->type(),
-        /*can_traverse_array_fields=*/true,
-        /*struct_path=*/nullptr, &field_descriptor_path));
+    ZETASQL_ASSIGN_OR_RETURN(Resolver::FindFieldsOutput output,
+                     FindFieldsFromPathExpression(
+                         "FILTER_FIELDS", generalized_path_expression,
+                         proto_to_modify->type(),
+                         /*can_traverse_array_fields=*/true));
     if (absl::Status status =
-            validator.ValidateFieldPath(include, field_descriptor_path);
+            validator.ValidateFieldPath(include, output.field_descriptor_path);
         !status.ok()) {
       return MakeSqlErrorAt(unary) << status.message();
     }
 
     filter_field_args.push_back(
-        MakeResolvedFilterFieldArg(include, field_descriptor_path));
+        MakeResolvedFilterFieldArg(include, output.field_descriptor_path));
   }
   // Validator also ensures that there is at least one field path.
   if (absl::Status status = validator.FinalValidation(
@@ -2986,24 +2958,24 @@ absl::Status Resolver::ResolveReplaceFieldsExpression(
   for (const ASTReplaceFieldsArg* replace_arg :
        ast_replace_fields->arguments()) {
     // Get the field descriptors for the field to be modified.
-    std::vector field_descriptor_path;
-    std::vector> struct_path;
-    ZETASQL_RETURN_IF_ERROR(FindFieldsFromPathExpression(
-        "REPLACE_FIELDS", replace_arg->path_expression(),
-        expr_to_modify->type(), /*can_traverse_array_fields=*/false,
-        &struct_path, &field_descriptor_path));
-    ZETASQL_RETURN_IF_ERROR(AddToFieldPathTrie(
-        language(), replace_arg->path_expression(), struct_path,
-        field_descriptor_path, &oneof_path_to_full_path, &field_path_trie));
+    ZETASQL_ASSIGN_OR_RETURN(
+        Resolver::FindFieldsOutput output,
+        FindFieldsFromPathExpression(
+            "REPLACE_FIELDS", replace_arg->path_expression(),
+            expr_to_modify->type(), /*can_traverse_array_fields=*/false));
+    ZETASQL_RETURN_IF_ERROR(
+        AddToFieldPathTrie(language(), replace_arg->path_expression(),
+                           output.struct_path, output.field_descriptor_path,
+                           &oneof_path_to_full_path, &field_path_trie));
 
     // Add a cast to the modified value if it needs to be coerced to the type
     // of the field.
     const Type* field_type;
-    if (field_descriptor_path.empty()) {
-      field_type = struct_path.back().second->type;
+    if (output.field_descriptor_path.empty()) {
+      field_type = output.struct_path.back().field->type;
     } else {
       ZETASQL_RETURN_IF_ERROR(type_factory_->GetProtoFieldType(
-          field_descriptor_path.back(),
+          output.field_descriptor_path.back(),
           GetTypeCatalogNamePath(expr_to_modify->type()), &field_type));
     }
     // Resolve the new value passing down the inferred type.
@@ -3016,14 +2988,14 @@ absl::Status Resolver::ResolveReplaceFieldsExpression(
         "Cannot replace field of type $0 with value of type $1",
         &replaced_field_expr));
     std::vector struct_index_path;
-    struct_index_path.reserve(struct_path.size());
-    for (const std::pair& struct_field :
-         struct_path) {
-      struct_index_path.push_back(struct_field.first);
+    struct_index_path.reserve(output.struct_path.size());
+    for (const FindFieldsOutput::StructFieldInfo& struct_field :
+         output.struct_path) {
+      struct_index_path.push_back(struct_field.field_index);
     }
-    resolved_modify_items.push_back(
-        MakeResolvedReplaceFieldItem(std::move(replaced_field_expr),
-                                     struct_index_path, field_descriptor_path));
+    resolved_modify_items.push_back(MakeResolvedReplaceFieldItem(
+        std::move(replaced_field_expr), struct_index_path,
+        output.field_descriptor_path));
   }
 
   const Type* field_type = expr_to_modify->type();
@@ -6794,9 +6766,9 @@ absl::Status Resolver::ResolveNewConstructor(
     ZETASQL_RETURN_IF_ERROR(
         ResolveExpr(ast_arg->expression(), expr_resolution_info, &expr, type));
 
-    arguments.emplace_back(ast_arg->expression(), std::move(expr),
-                           std::move(alias_or_ast_path_expr), field_descriptor,
-                           type);
+    arguments.emplace_back(
+        ast_arg->expression(), std::move(expr), type,
+        std::vector{field_descriptor});
   }
 
   ZETASQL_RETURN_IF_ERROR(ResolveBuildProto(
@@ -6806,63 +6778,65 @@ absl::Status Resolver::ResolveNewConstructor(
   return absl::OkStatus();
 }
 
-absl::Status Resolver::ResolveBracedConstructorFieldValue(
-    const ASTBracedConstructorFieldValue* ast_braced_constructor_field_value,
-    const Type* inferred_type, ExprResolutionInfo* expr_resolution_info,
-    std::unique_ptr* resolved_expr_out) {
+absl::StatusOr
+Resolver::ResolveBracedConstructorLhs(
+    const ASTBracedConstructorLhs* ast_braced_constructor_lhs,
+    const ProtoType* parent_type) {
   RETURN_ERROR_IF_OUT_OF_STACK_SPACE();
 
-  ZETASQL_RETURN_IF_ERROR(ResolveExpr(ast_braced_constructor_field_value->expression(),
-                              expr_resolution_info, resolved_expr_out,
-                              inferred_type));
-  return absl::OkStatus();
+  BracedConstructorField field;
+  ZETASQL_ASSIGN_OR_RETURN(
+      Resolver::FindFieldsOutput output,
+      FindFieldsFromPathExpression(
+          "BracedConstructor", ast_braced_constructor_lhs->extended_path_expr(),
+          parent_type, /*can_traverse_array_fields=*/true));
+  field.location = ast_braced_constructor_lhs->extended_path_expr();
+  field.field_info = output;
+
+  return field;
 }
 
 absl::StatusOr
 Resolver::ResolveBracedConstructorField(
     const ASTBracedConstructorField* ast_braced_constructor_field,
-    const ProtoType* parent_type, int field_index,
+    const ProtoType* parent_type, int field_index, bool allow_field_paths,
+    const ResolvedExpr* update_constructor_expr_to_modify,
     ExprResolutionInfo* expr_resolution_info) {
   RETURN_ERROR_IF_OUT_OF_STACK_SPACE();
 
-  const ASTNode* location = nullptr;
-  std::unique_ptr alias_or_ast_path_expr;
-  if (ast_braced_constructor_field->identifier() != nullptr) {
-    alias_or_ast_path_expr = std::make_unique(
-        ast_braced_constructor_field->identifier()->GetAsIdString());
-    location = ast_braced_constructor_field->identifier();
-  } else if (ast_braced_constructor_field->parenthesized_path() != nullptr) {
-    if (!language().LanguageFeatureEnabled(
-            FEATURE_V_1_2_PROTO_EXTENSIONS_WITH_NEW)) {
-      return MakeSqlErrorAt(ast_braced_constructor_field->parenthesized_path())
-             << "NEW constructor does not support proto extensions";
-    }
-    alias_or_ast_path_expr = std::make_unique(
-        ast_braced_constructor_field->parenthesized_path());
-    location = ast_braced_constructor_field->parenthesized_path();
-  } else {
+  const ASTBracedConstructorLhs* braced_constructor_lhs =
+      ast_braced_constructor_field->braced_constructor_lhs();
+  ZETASQL_ASSIGN_OR_RETURN(
+      BracedConstructorField field,
+      ResolveBracedConstructorLhs(braced_constructor_lhs, parent_type));
+  const std::vector& field_descriptor_path =
+      field.field_info.field_descriptor_path;
+  if (field_descriptor_path.empty()) {
     ZETASQL_RET_CHECK_FAIL() << "Cannot construct proto because field "
                      << (field_index + 1)
-                     << " does not specify field name/extension path. This "
-                        "should be a parser error.";
+                     << " does not specify field name/extension path. "
+                     << "This should be a parser error.";
+  }
+  if (!allow_field_paths && field_descriptor_path.size() > 1) {
+    return MakeSqlErrorAt(field.location)
+           << "Braced constructor supports only singular field paths, "
+           << "nested paths should use additional sets of braces";
   }
 
-  ZETASQL_ASSIGN_OR_RETURN(
-      const google::protobuf::FieldDescriptor* field_descriptor,
-      FindFieldDescriptor(parent_type->descriptor(), *alias_or_ast_path_expr,
-                          location, field_index, "Field"));
-  ZETASQL_ASSIGN_OR_RETURN(const Type* type,
-                   FindProtoFieldType(field_descriptor, location,
+  const google::protobuf::FieldDescriptor* leaf_field_descriptor =
+      field_descriptor_path.back();
+  ZETASQL_ASSIGN_OR_RETURN(const Type* lhs_type,
+                   FindProtoFieldType(leaf_field_descriptor, field.location,
                                       parent_type->CatalogNamePath()));
-
+  // Resolve the field value.
+  const ASTExpression* field_value =
+      ast_braced_constructor_field->value()->expression();
   std::unique_ptr expr;
   ZETASQL_RETURN_IF_ERROR(
-      ResolveBracedConstructorFieldValue(ast_braced_constructor_field->value(),
-                                         type, expr_resolution_info, &expr));
+      ResolveExpr(field_value, expr_resolution_info, &expr, lhs_type));
 
-  return ResolvedBuildProtoArg(location, std::move(expr),
-                               std::move(alias_or_ast_path_expr),
-                               field_descriptor, type);
+  return ResolvedBuildProtoArg(field.location, std::move(expr), lhs_type,
+                               field_descriptor_path);
 }
 
 // TODO: The noinline attribute is to prevent the stack usage
@@ -6925,16 +6899,16 @@ absl::Status Resolver::ResolveBracedConstructorForStruct(
     if (is_first_field) {
       is_first_field = false;
     }
-    // Proto extension is parsed as parenthesized_path, otherwise identifier.
-    if (ast_field->parenthesized_path() != nullptr) {
+    const ASTGeneralizedPathExpression* generalized_path =
+        ast_field->braced_constructor_lhs()->extended_path_expr();
+    if (generalized_path->node_kind() != AST_PATH_EXPRESSION ||
+        generalized_path->GetAsOrDie()->num_names() != 1) {
       return MakeSqlErrorAt(ast_field)
-             << "STRUCT Braced constructor is not allowed to use proto "
-                "extension.";
+             << "Fields in STRUCT Braced constructor should always have a "
+                "single identifier specified, not a path expression";
     }
-    ZETASQL_RET_CHECK_NE(ast_field->identifier(), nullptr)
-        << "Fields in STRUCT Braced constructor should always have a "
-           "single identifier specified, not a path expression";
-    identifiers.push_back(ast_field->identifier());
+    identifiers.push_back(
+        generalized_path->GetAsOrDie()->first_name());
     field_expressions.push_back(ast_field->value()->expression());
   }
   // Skip name matching for all bare struct.
@@ -6956,10 +6930,12 @@ absl::Status Resolver::ResolveBracedConstructorForProto(
     const ASTBracedConstructorField* ast_field =
         ast_braced_constructor->fields(i);
 
-    ZETASQL_ASSIGN_OR_RETURN(
-        ResolvedBuildProtoArg arg,
-        ResolveBracedConstructorField(ast_field, inferred_type->AsProto(), i,
-                                      expr_resolution_info));
+    ZETASQL_ASSIGN_OR_RETURN(ResolvedBuildProtoArg arg,
+                     ResolveBracedConstructorField(
+                         ast_field, inferred_type->AsProto(), i,
+                         /*allow_field_paths=*/false,
+                         /*update_constructor_expr_to_modify=*/nullptr,
+                         expr_resolution_info));
     resolved_build_proto_args.emplace_back(std::move(arg));
   }
 
@@ -7293,7 +7269,7 @@ absl::Status Resolver::ResolveStructConstructorWithParens(
   return ResolveStructConstructorImpl(
       ast_struct_constructor,
       /*ast_struct_type=*/nullptr, ast_struct_constructor->field_expressions(),
-      /*ast_field_names*/ {}, inferred_type, /*require_name_match*/ false,
+      /*ast_field_identifiers*/ {}, inferred_type, /*require_name_match*/ false,
       expr_resolution_info, resolved_expr_out);
 }
 
diff --git a/zetasql/analyzer/resolver_query.cc b/zetasql/analyzer/resolver_query.cc
index 40ca7f099..4a5298339 100644
--- a/zetasql/analyzer/resolver_query.cc
+++ b/zetasql/analyzer/resolver_query.cc
@@ -33,6 +33,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
@@ -2714,8 +2715,9 @@ absl::Status Resolver::ResolveOrderByItems(
       auto resolved_order_by_item = MakeResolvedOrderByItem(
           std::move(resolved_column_ref), std::move(resolved_collation_name),
           item_info.is_descending, item_info.null_order);
-      resolved_order_by_item->SetParseLocationRange(
-          item_info.ast_location->GetParseLocationRange());
+
+      MaybeRecordParseLocation(item_info.ast_location->GetParseLocationRange(),
+                               resolved_order_by_item.get());
       if (language().LanguageFeatureEnabled(FEATURE_V_1_3_COLLATION_SUPPORT)) {
         ZETASQL_RETURN_IF_ERROR(
             CollationAnnotation::ResolveCollationForResolvedOrderByItem(
@@ -6386,9 +6388,17 @@ absl::Status Resolver::ConvertScanToProto(
     std::unique_ptr expr =
         MakeColumnRef(named_column.column());
     MaybeRecordParseLocation(ast_column_location, expr.get());
+    ZETASQL_ASSIGN_OR_RETURN(
+        const google::protobuf::FieldDescriptor* field_descriptor,
+        FindFieldDescriptor(proto_type->descriptor(),
+                            AliasOrASTPathExpression(named_column.name()),
+                            ast_column_location, i, "Column"));
+    ZETASQL_ASSIGN_OR_RETURN(const Type* type,
+                     FindProtoFieldType(field_descriptor, ast_column_location,
+                                        proto_type->CatalogNamePath()));
     arguments.emplace_back(
-        ast_column_location, std::move(expr),
-        std::make_unique(named_column.name()));
+        ast_column_location, std::move(expr), type,
+        std::vector{field_descriptor});
   }
 
   std::unique_ptr resolved_build_proto_expr;
@@ -6714,7 +6724,7 @@ absl::Status Resolver::SetOperationResolver::CheckNoValueTable(
 // `named_columns` should not contain any duplicate names.
 static absl::StatusOr<
     absl::flat_hash_set>
-ToColumnNameSet(const std::vector& named_columns) {
+ToColumnNameSet(absl::Span named_columns) {
   absl::flat_hash_set
       column_names;
   for (const NamedColumn& named_column : named_columns) {
@@ -6726,7 +6736,7 @@ ToColumnNameSet(const std::vector& named_columns) {
 // Similar to `ToColumnNameSet` but allows the input `named_columns` to have
 // duplicate names.
 static absl::flat_hash_set
-ToColumnNameSetAllowDuplicates(const std::vector& named_columns) {
+ToColumnNameSetAllowDuplicates(absl::Span named_columns) {
   absl::flat_hash_set
       column_names;
   for (const NamedColumn& named_column : named_columns) {
@@ -8573,6 +8583,34 @@ absl::Status Resolver::ResolveFromClauseAndCreateScan(
   return absl::OkStatus();
 }
 
+// The current rules are arbitrary, and we should simply allow all operators
+// to compose.
+static absl::Status CheckPostfixTableOperators(
+    const ASTTableExpression* table_expr) {
+  if (table_expr->postfix_operators().empty() ||
+      table_expr->postfix_operators().size() == 1) {
+    return absl::OkStatus();
+  }
+
+  const ASTPostfixTableOperator* target_op = table_expr->postfix_operators(1);
+
+  // Currently, the only allowed combination is PIVOT or UNPIVOT, followed by
+  // TABLESAMPLE.
+  if (table_expr->postfix_operators(1)->node_kind() == AST_SAMPLE_CLAUSE &&
+      (table_expr->postfix_operators(0)->node_kind() == AST_PIVOT_CLAUSE ||
+       table_expr->postfix_operators(0)->node_kind() == AST_UNPIVOT_CLAUSE)) {
+    if (table_expr->postfix_operators().size() == 2) {
+      return absl::OkStatus();
+    }
+
+    target_op = table_expr->postfix_operators(2);
+  }
+
+  return MakeSqlErrorAt(target_op)
+         << "Unsupported combination of table operators. The only allowed "
+            "combination is PIVOT/UNPIVOT followed by TABLESAMPLE. ";
+}
+
 // This is a self-contained table expression.  It can be an UNNEST, but
 // only as a leaf - not one that has to wrap another scan and flatten it.
 absl::Status Resolver::ResolveTableExpression(
@@ -8580,6 +8618,9 @@ absl::Status Resolver::ResolveTableExpression(
     const NameScope* local_scope, std::unique_ptr* output,
     std::shared_ptr* output_name_list) {
   RETURN_ERROR_IF_OUT_OF_STACK_SPACE();
+
+  ZETASQL_RETURN_IF_ERROR(CheckPostfixTableOperators(table_expr));
+
   switch (table_expr->node_kind()) {
     case AST_TABLE_PATH_EXPRESSION:
       return ResolveTablePathExpression(
@@ -9441,14 +9482,10 @@ absl::Status Resolver::ResolveTablePathExpression(
       return MakeSqlErrorAt(for_system_time)
              << "FOR SYSTEM_TIME AS OF is not allowed with array scans";
     }
-    if (table_ref->pivot_clause() != nullptr) {
-      return MakeSqlErrorAt(table_ref->pivot_clause())
-             << "PIVOT is not allowed with array scans";
-    }
-
-    if (table_ref->unpivot_clause() != nullptr) {
-      return MakeSqlErrorAt(table_ref->unpivot_clause())
-             << "UNPIVOT is not allowed with array scans";
+    if (!table_ref->postfix_operators().empty()) {
+      const auto* first_op = table_ref->postfix_operators(0);
+      return MakeSqlErrorAt(first_op)
+             << first_op->Name() << " is not allowed with array scans";
     }
 
     // When  contains explicit UNNEST, do not supply .
@@ -9563,25 +9600,36 @@ absl::Status Resolver::ResolveTablePathExpression(
   ZETASQL_RET_CHECK(this_scan != nullptr);
   ZETASQL_RET_CHECK(name_list != nullptr);
 
-  if (table_ref->pivot_clause() != nullptr) {
-    ZETASQL_RET_CHECK_EQ(for_system_time, nullptr)
-        << "Parser should not allow PIVOT and FOR SYSTEM TIME AS OF to coexist";
-    ZETASQL_RETURN_IF_ERROR(
-        ResolvePivotClause(std::move(this_scan), name_list, external_scope,
-                           /*input_is_subquery=*/false,
-                           table_ref->pivot_clause(), &this_scan, &name_list));
-  }
-  if (table_ref->unpivot_clause() != nullptr) {
-    ZETASQL_RET_CHECK_EQ(for_system_time, nullptr)
-        << "Parser should not allow UNPIVOT and FOR SYSTEM TIME AS OF to "
-           "coexist";
-    ZETASQL_RETURN_IF_ERROR(ResolveUnpivotClause(
-        std::move(this_scan), name_list, external_scope,
-        table_ref->unpivot_clause(), &this_scan, &name_list));
-  }
-  if (table_ref->sample_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(ResolveTablesampleClause(table_ref->sample_clause(),
-                                             &name_list, &this_scan));
+  for (const auto* op : table_ref->postfix_operators()) {
+    switch (op->node_kind()) {
+      case AST_PIVOT_CLAUSE:
+        if (for_system_time != nullptr) {
+          ZETASQL_RET_CHECK_EQ(for_system_time, nullptr)
+              << "Parser should not allow PIVOT and FOR SYSTEM TIME AS OF to "
+                 "coexist";
+        }
+        ZETASQL_RETURN_IF_ERROR(ResolvePivotClause(
+            std::move(this_scan), name_list, external_scope,
+            /*input_is_subquery=*/false, op->GetAsOrDie(),
+            &this_scan, &name_list));
+        break;
+      case AST_UNPIVOT_CLAUSE:
+        ZETASQL_RET_CHECK_EQ(for_system_time, nullptr)
+            << "Parser should not allow UNPIVOT and FOR SYSTEM TIME AS OF to "
+               "coexist";
+        ZETASQL_RETURN_IF_ERROR(ResolveUnpivotClause(
+            std::move(this_scan), name_list, external_scope,
+            op->GetAsOrDie(), &this_scan, &name_list));
+        break;
+      case AST_SAMPLE_CLAUSE:
+        ZETASQL_RETURN_IF_ERROR(ResolveTablesampleClause(
+            op->GetAsOrDie(), &name_list, &this_scan));
+        break;
+      case AST_MATCH_RECOGNIZE_CLAUSE:
+        return MakeSqlErrorAt(op) << "MATCH_RECOGNIZE is not supported";
+      default:
+        ZETASQL_RET_CHECK_FAIL() << "Unsupported postfix operator: " << op->node_kind();
+    }
   }
 
   *output_name_list = name_list;
@@ -9706,21 +9754,30 @@ absl::Status Resolver::ResolveTableSubquery(
         table_ref, alias, subquery_name_list, output_name_list));
   }
 
-  if (table_ref->pivot_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(ResolvePivotClause(
-        std::move(resolved_subquery), *output_name_list, scope,
-        /*input_is_subquery=*/true, table_ref->pivot_clause(),
-        &resolved_subquery, output_name_list));
-  }
-
-  if (table_ref->unpivot_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(ResolveUnpivotClause(
-        std::move(resolved_subquery), *output_name_list, scope,
-        table_ref->unpivot_clause(), &resolved_subquery, output_name_list));
-  }
-  if (table_ref->sample_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(ResolveTablesampleClause(
-        table_ref->sample_clause(), output_name_list, &resolved_subquery));
+  for (const auto* op : table_ref->postfix_operators()) {
+    switch (op->node_kind()) {
+      case AST_PIVOT_CLAUSE:
+        ZETASQL_RETURN_IF_ERROR(ResolvePivotClause(
+            std::move(resolved_subquery), *output_name_list, scope,
+            /*input_is_subquery=*/true, op->GetAsOrDie(),
+            &resolved_subquery, output_name_list));
+        break;
+      case AST_UNPIVOT_CLAUSE:
+        ZETASQL_RETURN_IF_ERROR(ResolveUnpivotClause(
+            std::move(resolved_subquery), *output_name_list, scope,
+            op->GetAsOrDie(), &resolved_subquery,
+            output_name_list));
+        break;
+      case AST_SAMPLE_CLAUSE:
+        ZETASQL_RETURN_IF_ERROR(
+            ResolveTablesampleClause(op->GetAsOrDie(),
+                                     output_name_list, &resolved_subquery));
+        break;
+      case AST_MATCH_RECOGNIZE_CLAUSE:
+        return MakeSqlErrorAt(op) << "MATCH_RECOGNIZE is not supported";
+      default:
+        ZETASQL_RET_CHECK_FAIL() << "Unsupported postfix operator: " << op->node_kind();
+    }
   }
 
   *output = std::move(resolved_subquery);
@@ -10580,9 +10637,18 @@ absl::Status Resolver::ResolveParenthesizedJoin(
   ZETASQL_RETURN_IF_ERROR(ResolveJoin(parenthesized_join->join(), external_scope,
                               local_scope, &resolved_join, output_name_list));
 
-  if (parenthesized_join->sample_clause()) {
-    ZETASQL_RETURN_IF_ERROR(ResolveTablesampleClause(
-        parenthesized_join->sample_clause(), output_name_list, &resolved_join));
+  for (const auto* op : parenthesized_join->postfix_operators()) {
+    switch (op->node_kind()) {
+      case AST_SAMPLE_CLAUSE:
+        ZETASQL_RETURN_IF_ERROR(
+            ResolveTablesampleClause(op->GetAsOrDie(),
+                                     output_name_list, &resolved_join));
+        break;
+      case AST_MATCH_RECOGNIZE_CLAUSE:
+        return MakeSqlErrorAt(op) << "MATCH_RECOGNIZE is not supported";
+      default:
+        ZETASQL_RET_CHECK_FAIL() << "Unsupported postfix operator: " << op->node_kind();
+    }
   }
 
   *output = std::move(resolved_join);
@@ -10981,28 +11047,35 @@ absl::Status Resolver::ResolveTVF(
   MaybeRecordTVFCallParseLocation(ast_tvf, tvf_scan.get());
   *output = std::move(tvf_scan);
 
-  // Resolve the PIVOT clause, if present.
-  if (ast_tvf->pivot_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(ResolvePivotClause(
-        std::move(*output), *output_name_list, external_scope,
-        /*input_is_subquery=*/false, ast_tvf->pivot_clause(), output,
-        output_name_list));
-  }
-
-  if (ast_tvf->unpivot_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(ResolveUnpivotClause(
-        std::move(*output), *output_name_list, external_scope,
-        ast_tvf->unpivot_clause(), output, output_name_list));
-  }
-  // Resolve the TABLESAMPLE clause, if present.
-  if (ast_tvf->sample() != nullptr) {
-    if (!language().LanguageFeatureEnabled(
-            FEATURE_TABLESAMPLE_FROM_TABLE_VALUED_FUNCTIONS)) {
-      return MakeSqlErrorAt(ast_tvf->sample())
-             << "TABLESAMPLE from table-valued function calls is not supported";
+  for (const auto* op : ast_tvf->postfix_operators()) {
+    switch (op->node_kind()) {
+      case AST_PIVOT_CLAUSE:
+        ZETASQL_RETURN_IF_ERROR(ResolvePivotClause(
+            std::move(*output), *output_name_list, external_scope,
+            /*input_is_subquery=*/false, op->GetAsOrDie(),
+            output, output_name_list));
+        break;
+      case AST_UNPIVOT_CLAUSE:
+        ZETASQL_RETURN_IF_ERROR(ResolveUnpivotClause(
+            std::move(*output), *output_name_list, external_scope,
+            op->GetAsOrDie(), output, output_name_list));
+        break;
+      case AST_SAMPLE_CLAUSE: {
+        if (!language().LanguageFeatureEnabled(
+                FEATURE_TABLESAMPLE_FROM_TABLE_VALUED_FUNCTIONS)) {
+          return MakeSqlErrorAt(op)
+                 << "TABLESAMPLE from table-valued function calls is not "
+                    "supported";
+        }
+        ZETASQL_RETURN_IF_ERROR(ResolveTablesampleClause(
+            op->GetAsOrDie(), output_name_list, output));
+        break;
+      }
+      case AST_MATCH_RECOGNIZE_CLAUSE:
+        return MakeSqlErrorAt(op) << "MATCH_RECOGNIZE is not supported";
+      default:
+        ZETASQL_RET_CHECK_FAIL() << "Unsupported postfix operator: " << op->node_kind();
     }
-    ZETASQL_RETURN_IF_ERROR(
-        ResolveTablesampleClause(ast_tvf->sample(), output_name_list, output));
   }
   return absl::OkStatus();
 }
@@ -11551,7 +11624,7 @@ absl::Status Resolver::GenerateTVFNotMatchError(
     const ASTTVF* ast_tvf, const std::vector& arg_locations,
     const SignatureMatchResult& signature_match_result,
     const TableValuedFunction& tvf_catalog_entry, const std::string& tvf_name,
-    const std::vector& input_arg_types, int signature_idx) {
+    absl::Span input_arg_types, int signature_idx) {
   const ASTNode* ast_location = ast_tvf;
   if (signature_match_result.bad_argument_index() != -1) {
     ZETASQL_RET_CHECK_LT(signature_match_result.bad_argument_index(),
@@ -12225,34 +12298,19 @@ absl::Status Resolver::ResolveArrayScan(
   ZETASQL_RET_CHECK(resolved_input_scan != nullptr);
   ZETASQL_RET_CHECK_EQ(*resolved_input_scan == nullptr, name_list_input == nullptr);
 
-  if (table_ref->sample_clause() != nullptr) {
-    return MakeSqlErrorAt(table_ref->sample_clause())
-           << "TABLESAMPLE is not allowed with array scans";
-  }
-  if (table_ref->pivot_clause() != nullptr) {
+  for (const auto* op : table_ref->postfix_operators()) {
     if (language().LanguageFeatureEnabled(
-            FEATURE_V_1_4_DISALLOW_PIVOT_AND_UNPIVOT_ON_ARRAY_SCANS)) {
-      return MakeSqlErrorAt(table_ref->pivot_clause())
-             << "PIVOT is not allowed with array scans";
-    } else {
-      ZETASQL_RETURN_IF_ERROR(AddDeprecationWarning(
-          table_ref->pivot_clause(),
-          DeprecationWarning::PIVOT_OR_UNPIVOT_ON_ARRAY_SCAN,
-          "PIVOT is not allowed with array scans. This will become an error"));
-    }
-  }
-  if (table_ref->unpivot_clause() != nullptr) {
-    if (language().LanguageFeatureEnabled(
-            FEATURE_V_1_4_DISALLOW_PIVOT_AND_UNPIVOT_ON_ARRAY_SCANS)) {
-      return MakeSqlErrorAt(table_ref->unpivot_clause())
-             << "UNPIVOT is not allowed with array scans";
-    } else {
-      ZETASQL_RETURN_IF_ERROR(AddDeprecationWarning(
-          table_ref->unpivot_clause(),
-          DeprecationWarning::PIVOT_OR_UNPIVOT_ON_ARRAY_SCAN,
-          "UNPIVOT is not allowed with array scans. This will become an "
-          "error"));
-    }
+            FEATURE_V_1_4_DISALLOW_PIVOT_AND_UNPIVOT_ON_ARRAY_SCANS) ||
+        (op->node_kind() != AST_PIVOT_CLAUSE &&
+         op->node_kind() != AST_UNPIVOT_CLAUSE)) {
+      return MakeSqlErrorAt(op)
+             << op->Name() << " is not allowed with array scans";
+    }
+    ZETASQL_RETURN_IF_ERROR(AddDeprecationWarning(
+        op, DeprecationWarning::PIVOT_OR_UNPIVOT_ON_ARRAY_SCAN,
+        absl::StrCat(op->Name(),
+                     " is not allowed with array scans. This will become "
+                     "an error")));
   }
 
   // We have either an array reference or UNNEST.
diff --git a/zetasql/analyzer/rewrite_resolved_ast_test.cc b/zetasql/analyzer/rewrite_resolved_ast_test.cc
index 898b0ada7..d2a6f26a3 100644
--- a/zetasql/analyzer/rewrite_resolved_ast_test.cc
+++ b/zetasql/analyzer/rewrite_resolved_ast_test.cc
@@ -130,7 +130,6 @@ TEST(RewriteResolvedAstTest, RewriterDoesNotConflictWithExpressionColumnNames) {
 |       |           |   |           +-ColumnRef(type=STRING, column=$subquery1.k#1, is_correlated=TRUE)
 |       |           |   +-order_by_item_list=
 |       |           |     +-OrderByItem
-|       |           |       +-parse_location=192-203
 |       |           |       +-column_ref=
 |       |           |       | +-ColumnRef(type=INT64, column=$array_offset.offset#4)
 |       |           |       +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/aggregation.test b/zetasql/analyzer/testdata/aggregation.test
index c583153ef..689b8ea6e 100644
--- a/zetasql/analyzer/testdata/aggregation.test
+++ b/zetasql/analyzer/testdata/aggregation.test
@@ -3059,7 +3059,6 @@ QueryStmt
     |           +-Literal(type=INT64, value=0)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=169-209
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
 ==
@@ -3073,8 +3072,6 @@ group by {{T.|}}KitchenSink.nested_value.nested_int64
 having {{T.|}}KitchenSink.nested_value.nested_int64 > 0
 order by {{T.|}}KitchenSink.nested_value.nested_int64 + 2
 --
-ALTERNATION GROUP: T.,T.,T.,T.
---
 QueryStmt
 +-output_column_list=
 | +-$query.$col1#5 AS `$col1` [INT64]
@@ -3125,1057 +3122,154 @@ QueryStmt
     |           +-Literal(type=INT64, value=0)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=177-220
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
---
-ALTERNATION GROUP: T.,T.,T.,
+==
+
+select {{T.|}}KitchenSink.nested_value.nested_int64, count(n)
+from TestTable T, T.KitchenSink.repeated_double_val n
+group by {{T.|}}KitchenSink.nested_value.nested_int64
+having {{T.|}}KitchenSink.nested_value.nested_int64 > 0
+order by {{T.|}}KitchenSink.nested_value.nested_int64 + 2
 --
 QueryStmt
 +-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
+| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
+| +-$aggregate.$agg1#5 AS `$col2` [INT64]
 +-query=
   +-OrderByScan
-    +-column_list=[$query.$col1#5]
+    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
     +-is_ordered=TRUE
     +-input_scan=
     | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5, $orderby.$orderbycol1#6]
+    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
     |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
+    |   | +-$orderbycol1#8 :=
     |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
+    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
     |   |     +-Literal(type=INT64, value=2)
     |   +-input_scan=
     |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
+    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
     |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       |   +-expr_list=
-    |       |   | +-$col1#5 :=
-    |       |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |       |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |       |   |     +-Literal(type=INT64, value=1)
+    |       | +-AggregateScan
+    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
     |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.nested_int64#4]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-nested_int64#4 :=
-    |       |           +-GetProtoField
-    |       |             +-type=INT64
-    |       |             +-expr=
-    |       |             | +-GetProtoField
-    |       |             |   +-type=PROTO
-    |       |             |   +-expr=
-    |       |             |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |             |   +-field_descriptor=nested_value
-    |       |             |   +-default_value=NULL
-    |       |             +-field_descriptor=nested_int64
-    |       |             +-default_value=88
+    |       |   | +-ProjectScan
+    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
+    |       |   |   +-expr_list=
+    |       |   |   | +-nested_int64#6 :=
+    |       |   |   |   +-GetProtoField
+    |       |   |   |     +-type=INT64
+    |       |   |   |     +-expr=
+    |       |   |   |     | +-GetProtoField
+    |       |   |   |     |   +-type=PROTO
+    |       |   |   |     |   +-expr=
+    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
+    |       |   |   |     |   +-field_descriptor=nested_value
+    |       |   |   |     |   +-default_value=NULL
+    |       |   |   |     +-field_descriptor=nested_int64
+    |       |   |   |     +-default_value=88
+    |       |   |   +-input_scan=
+    |       |   |     +-ArrayScan
+    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
+    |       |   |       +-input_scan=
+    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
+    |       |   |       +-array_expr_list=
+    |       |   |       | +-GetProtoField
+    |       |   |       |   +-type=ARRAY
+    |       |   |       |   +-expr=
+    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
+    |       |   |       |   +-field_descriptor=repeated_double_val
+    |       |   |       |   +-default_value=[]
+    |       |   |       +-element_column_list=[$array.n#4]
+    |       |   +-group_by_list=
+    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
+    |       |   +-aggregate_list=
+    |       |     +-$agg1#5 :=
+    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
+    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
     |       +-filter_expr=
     |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
+    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
     |           +-Literal(type=INT64, value=0)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=177-218
         +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
+          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
+==
+
+# Accessing 'n' is not valid after grouping.
+select {{T.|}}KitchenSink.nested_value.nested_int64, n
+from TestTable T, T.KitchenSink.repeated_double_val n
+group by {{T.|}}KitchenSink.nested_value.nested_int64
 --
 ALTERNATION GROUPS:
-    T.,T.,,T.
-    T.,,T.,T.
-    T.,T.,T.
+    T.,T.
+    T.,
 --
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       |   +-expr_list=
-    |       |   | +-$col1#5 :=
-    |       |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |       |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |       |   |     +-Literal(type=INT64, value=1)
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.nested_int64#4]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-nested_int64#4 :=
-    |       |           +-GetProtoField
-    |       |             +-type=INT64
-    |       |             +-expr=
-    |       |             | +-GetProtoField
-    |       |             |   +-type=PROTO
-    |       |             |   +-expr=
-    |       |             |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |             |   +-field_descriptor=nested_value
-    |       |             |   +-default_value=NULL
-    |       |             +-field_descriptor=nested_int64
-    |       |             +-default_value=88
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=175-218
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
+ERROR: SELECT list expression references table alias n which is neither grouped nor aggregated [at 1:49]
+select T.KitchenSink.nested_value.nested_int64, n
+                                                ^
 --
 ALTERNATION GROUPS:
-    T.,T.,,
-    T.,,T.,
-    T.,T.,
+    T.
+    
+--
+ERROR: SELECT list expression references table alias n which is neither grouped nor aggregated [at 1:47]
+select KitchenSink.nested_value.nested_int64, n
+                                              ^
+==
+
+# GROUP BY struct field reference.
+select sq.a
+from (select as struct key as a, value as b from KeyValue) as sq
+group by sq.a
 --
 QueryStmt
 +-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
+| +-$groupby.a#4 AS a [INT64]
 +-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#5]
-    +-is_ordered=TRUE
+  +-ProjectScan
+    +-column_list=[$groupby.a#4]
     +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       |   +-expr_list=
-    |       |   | +-$col1#5 :=
-    |       |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |       |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |       |   |     +-Literal(type=INT64, value=1)
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.nested_int64#4]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-nested_int64#4 :=
-    |       |           +-GetProtoField
-    |       |             +-type=INT64
-    |       |             +-expr=
-    |       |             | +-GetProtoField
-    |       |             |   +-type=PROTO
-    |       |             |   +-expr=
-    |       |             |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |             |   +-field_descriptor=nested_value
-    |       |             |   +-default_value=NULL
-    |       |             +-field_descriptor=nested_int64
-    |       |             +-default_value=88
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=175-216
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
---
-ALTERNATION GROUPS:
-    T.,,,T.
-    T.,,T.
-    T.,T.
+      +-AggregateScan
+        +-column_list=[$groupby.a#4]
+        +-input_scan=
+        | +-ProjectScan
+        |   +-column_list=[$make_struct.$struct#3]
+        |   +-expr_list=
+        |   | +-$struct#3 :=
+        |   |   +-MakeStruct
+        |   |     +-type=STRUCT
+        |   |     +-field_list=
+        |   |       +-ColumnRef(type=INT64, column=KeyValue.Key#1)
+        |   |       +-ColumnRef(type=STRING, column=KeyValue.Value#2)
+        |   +-input_scan=
+        |     +-ProjectScan
+        |       +-column_list=KeyValue.[Key#1, Value#2]
+        |       +-input_scan=
+        |         +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
+        +-group_by_list=
+          +-a#4 :=
+            +-GetStructField
+              +-type=INT64
+              +-expr=
+              | +-ColumnRef(type=STRUCT, column=$make_struct.$struct#3)
+              +-field_idx=0
+==
+
+# GROUP BY literal struct field reference.
+select sq.a
+from (select as struct 1 as a, 2 as b) as sq
+group by sq.a
 --
 QueryStmt
 +-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
+| +-$groupby.a#4 AS a [INT64]
 +-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       |   +-expr_list=
-    |       |   | +-$col1#5 :=
-    |       |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |       |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |       |   |     +-Literal(type=INT64, value=1)
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.nested_int64#4]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-nested_int64#4 :=
-    |       |           +-GetProtoField
-    |       |             +-type=INT64
-    |       |             +-expr=
-    |       |             | +-GetProtoField
-    |       |             |   +-type=PROTO
-    |       |             |   +-expr=
-    |       |             |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |             |   +-field_descriptor=nested_value
-    |       |             |   +-default_value=NULL
-    |       |             +-field_descriptor=nested_int64
-    |       |             +-default_value=88
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=173-216
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
---
-ALTERNATION GROUPS:
-    T.,,,
-    T.,,
-    T.,
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       |   +-expr_list=
-    |       |   | +-$col1#5 :=
-    |       |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |       |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |       |   |     +-Literal(type=INT64, value=1)
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.nested_int64#4]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-nested_int64#4 :=
-    |       |           +-GetProtoField
-    |       |             +-type=INT64
-    |       |             +-expr=
-    |       |             | +-GetProtoField
-    |       |             |   +-type=PROTO
-    |       |             |   +-expr=
-    |       |             |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |             |   +-field_descriptor=nested_value
-    |       |             |   +-default_value=NULL
-    |       |             +-field_descriptor=nested_int64
-    |       |             +-default_value=88
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=173-214
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
---
-ALTERNATION GROUP: T.
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       |   +-expr_list=
-    |       |   | +-$col1#5 :=
-    |       |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |       |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |       |   |     +-Literal(type=INT64, value=1)
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.nested_int64#4]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-nested_int64#4 :=
-    |       |           +-GetProtoField
-    |       |             +-type=INT64
-    |       |             +-expr=
-    |       |             | +-GetProtoField
-    |       |             |   +-type=PROTO
-    |       |             |   +-expr=
-    |       |             |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |             |   +-field_descriptor=nested_value
-    |       |             |   +-default_value=NULL
-    |       |             +-field_descriptor=nested_int64
-    |       |             +-default_value=88
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=171-214
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
---
-ALTERNATION GROUP: 
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.nested_int64#4, $query.$col1#5]
-    |       |   +-expr_list=
-    |       |   | +-$col1#5 :=
-    |       |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |       |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |       |   |     +-Literal(type=INT64, value=1)
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.nested_int64#4]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-nested_int64#4 :=
-    |       |           +-GetProtoField
-    |       |             +-type=INT64
-    |       |             +-expr=
-    |       |             | +-GetProtoField
-    |       |             |   +-type=PROTO
-    |       |             |   +-expr=
-    |       |             |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |             |   +-field_descriptor=nested_value
-    |       |             |   +-default_value=NULL
-    |       |             +-field_descriptor=nested_int64
-    |       |             +-default_value=88
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#4)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=171-212
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
-==
-
-select {{T.|}}KitchenSink.nested_value.nested_int64, count(n)
-from TestTable T, T.KitchenSink.repeated_double_val n
-group by {{T.|}}KitchenSink.nested_value.nested_int64
-having {{T.|}}KitchenSink.nested_value.nested_int64 > 0
-order by {{T.|}}KitchenSink.nested_value.nested_int64 + 2
---
-ALTERNATION GROUP: T.,T.,T.,T.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=220-263
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
---
-ALTERNATION GROUP: T.,T.,T.,
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=220-261
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
---
-ALTERNATION GROUPS:
-    T.,T.,,T.
-    T.,,T.,T.
-    T.,T.,T.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=218-261
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
---
-ALTERNATION GROUPS:
-    T.,T.,,
-    T.,,T.,
-    T.,T.,
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=218-259
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
---
-ALTERNATION GROUPS:
-    T.,,,T.
-    T.,,T.
-    T.,T.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=216-259
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
---
-ALTERNATION GROUPS:
-    T.,,,
-    T.,,
-    T.,
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=216-257
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
---
-ALTERNATION GROUP: T.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=214-257
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
---
-ALTERNATION GROUP: 
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.nested_int64#7 AS nested_int64 [INT64]
-| +-$aggregate.$agg1#5 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5, $orderby.$orderbycol1#8]
-    |   +-expr_list=
-    |   | +-$orderbycol1#8 :=
-    |   |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |     +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |   |     +-Literal(type=INT64, value=2)
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       +-input_scan=
-    |       | +-AggregateScan
-    |       |   +-column_list=[$groupby.nested_int64#7, $aggregate.$agg1#5]
-    |       |   +-input_scan=
-    |       |   | +-ProjectScan
-    |       |   |   +-column_list=[TestTable.KitchenSink#3, $array.n#4, $pre_groupby.nested_int64#6]
-    |       |   |   +-expr_list=
-    |       |   |   | +-nested_int64#6 :=
-    |       |   |   |   +-GetProtoField
-    |       |   |   |     +-type=INT64
-    |       |   |   |     +-expr=
-    |       |   |   |     | +-GetProtoField
-    |       |   |   |     |   +-type=PROTO
-    |       |   |   |     |   +-expr=
-    |       |   |   |     |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |   |     |   +-field_descriptor=nested_value
-    |       |   |   |     |   +-default_value=NULL
-    |       |   |   |     +-field_descriptor=nested_int64
-    |       |   |   |     +-default_value=88
-    |       |   |   +-input_scan=
-    |       |   |     +-ArrayScan
-    |       |   |       +-column_list=[TestTable.KitchenSink#3, $array.n#4]
-    |       |   |       +-input_scan=
-    |       |   |       | +-TableScan(column_list=[TestTable.KitchenSink#3], table=TestTable, column_index_list=[2], alias="T")
-    |       |   |       +-array_expr_list=
-    |       |   |       | +-GetProtoField
-    |       |   |       |   +-type=ARRAY
-    |       |   |       |   +-expr=
-    |       |   |       |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-    |       |   |       |   +-field_descriptor=repeated_double_val
-    |       |   |       |   +-default_value=[]
-    |       |   |       +-element_column_list=[$array.n#4]
-    |       |   +-group_by_list=
-    |       |   | +-nested_int64#7 := ColumnRef(type=INT64, column=$pre_groupby.nested_int64#6)
-    |       |   +-aggregate_list=
-    |       |     +-$agg1#5 :=
-    |       |       +-AggregateFunctionCall(ZetaSQL:count(DOUBLE) -> INT64)
-    |       |         +-ColumnRef(type=DOUBLE, column=$array.n#4)
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-    |           +-ColumnRef(type=INT64, column=$groupby.nested_int64#7)
-    |           +-Literal(type=INT64, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=214-255
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
-==
-
-# Accessing 'n' is not valid after grouping.
-select {{T.|}}KitchenSink.nested_value.nested_int64, n
-from TestTable T, T.KitchenSink.repeated_double_val n
-group by {{T.|}}KitchenSink.nested_value.nested_int64
---
-ALTERNATION GROUPS:
-    T.,T.
-    T.,
---
-ERROR: SELECT list expression references table alias n which is neither grouped nor aggregated [at 1:49]
-select T.KitchenSink.nested_value.nested_int64, n
-                                                ^
---
-ALTERNATION GROUPS:
-    T.
-    
---
-ERROR: SELECT list expression references table alias n which is neither grouped nor aggregated [at 1:47]
-select KitchenSink.nested_value.nested_int64, n
-                                              ^
-==
-
-# GROUP BY struct field reference.
-select sq.a
-from (select as struct key as a, value as b from KeyValue) as sq
-group by sq.a
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.a#4 AS a [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=[$groupby.a#4]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$groupby.a#4]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[$make_struct.$struct#3]
-        |   +-expr_list=
-        |   | +-$struct#3 :=
-        |   |   +-MakeStruct
-        |   |     +-type=STRUCT
-        |   |     +-field_list=
-        |   |       +-ColumnRef(type=INT64, column=KeyValue.Key#1)
-        |   |       +-ColumnRef(type=STRING, column=KeyValue.Value#2)
-        |   +-input_scan=
-        |     +-ProjectScan
-        |       +-column_list=KeyValue.[Key#1, Value#2]
-        |       +-input_scan=
-        |         +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
-        +-group_by_list=
-          +-a#4 :=
-            +-GetStructField
-              +-type=INT64
-              +-expr=
-              | +-ColumnRef(type=STRUCT, column=$make_struct.$struct#3)
-              +-field_idx=0
-==
-
-# GROUP BY literal struct field reference.
-select sq.a
-from (select as struct 1 as a, 2 as b) as sq
-group by sq.a
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.a#4 AS a [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=[$groupby.a#4]
+  +-ProjectScan
+    +-column_list=[$groupby.a#4]
     +-input_scan=
       +-AggregateScan
         +-column_list=[$groupby.a#4]
@@ -4986,475 +4080,95 @@ select sum() from KeyValue;
        ^
 ==
 
-[language_features={{DISALLOW_GROUP_BY_FLOAT|}}]
-select count(distinct int64), count(distinct float) from SimpleTypes
---
-ALTERNATION GROUP: DISALLOW_GROUP_BY_FLOAT
---
-ERROR: Aggregate functions with DISTINCT cannot be used with arguments of type FLOAT [at 1:31]
-select count(distinct int64), count(distinct float) from SimpleTypes
-                              ^
---
-ALTERNATION GROUP: 
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#20 AS `$col1` [INT64]
-| +-$aggregate.$agg2#21 AS `$col2` [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=$aggregate.[$agg1#20, $agg2#21]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=$aggregate.[$agg1#20, $agg2#21]
-        +-input_scan=
-        | +-TableScan(column_list=SimpleTypes.[int64#2, float#8], table=SimpleTypes, column_index_list=[1, 7])
-        +-aggregate_list=
-          +-$agg1#20 :=
-          | +-AggregateFunctionCall(ZetaSQL:count(INT64) -> INT64)
-          |   +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-          |   +-distinct=TRUE
-          +-$agg2#21 :=
-            +-AggregateFunctionCall(ZetaSQL:count(FLOAT) -> INT64)
-              +-ColumnRef(type=FLOAT, column=SimpleTypes.float#8)
-              +-distinct=TRUE
-==
-
-[language_features={{DISALLOW_GROUP_BY_FLOAT|}}]
-select double, count(*) from SimpleTypes group by double
---
-ALTERNATION GROUP: DISALLOW_GROUP_BY_FLOAT
---
-ERROR: Grouping by expressions of type DOUBLE is not allowed [at 1:51]
-select double, count(*) from SimpleTypes group by double
-                                                  ^
---
-ALTERNATION GROUP: 
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.double#21 AS double [DOUBLE]
-| +-$aggregate.$agg1#20 AS `$col2` [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=[$groupby.double#21, $aggregate.$agg1#20]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$groupby.double#21, $aggregate.$agg1#20]
-        +-input_scan=
-        | +-TableScan(column_list=[SimpleTypes.double#9], table=SimpleTypes, column_index_list=[8])
-        +-group_by_list=
-        | +-double#21 := ColumnRef(type=DOUBLE, column=SimpleTypes.double#9)
-        +-aggregate_list=
-          +-$agg1#20 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
-==
-
-select count(*), count(CAST(NULL AS STRUCT)) from KeyValue
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#3 AS `$col1` [INT64]
-| +-$aggregate.$agg2#4 AS `$col2` [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=$aggregate.[$agg1#3, $agg2#4]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=$aggregate.[$agg1#3, $agg2#4]
-        +-input_scan=
-        | +-TableScan(table=KeyValue)
-        +-aggregate_list=
-          +-$agg1#3 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
-          +-$agg2#4 :=
-            +-AggregateFunctionCall(ZetaSQL:count(STRUCT) -> INT64)
-              +-Literal(type=STRUCT, value=NULL, has_explicit_type=TRUE)
-==
-
-SELECT DISTINCT MOD(int64, 10) int64_col,
-       CAST(MAX(int64+1) AS UINT64) uint64_col
-FROM SimpleTypes
-GROUP BY {{1|int64_col}}
-ORDER BY {{1|int64_col}}, {{2|uint64_col}}
---
-ALTERNATION GROUP: 1,1,2
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.int64_col#24 AS int64_col [INT64]
-| +-$distinct.uint64_col#25 AS uint64_col [UINT64]
-+-query=
-  +-OrderByScan
-    +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20, $query.uint64_col#23]
-    |   |   +-expr_list=
-    |   |   | +-uint64_col#23 :=
-    |   |   |   +-Cast(INT64 -> UINT64)
-    |   |   |     +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
-    |   |   +-input_scan=
-    |   |     +-AggregateScan
-    |   |       +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20]
-    |   |       +-input_scan=
-    |   |       | +-ProjectScan
-    |   |       |   +-column_list=[SimpleTypes.int64#2, $pre_groupby.int64_col#21]
-    |   |       |   +-expr_list=
-    |   |       |   | +-int64_col#21 :=
-    |   |       |   |   +-FunctionCall(ZetaSQL:mod(INT64, INT64) -> INT64)
-    |   |       |   |     +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |       |   |     +-Literal(type=INT64, value=10)
-    |   |       |   +-input_scan=
-    |   |       |     +-TableScan(column_list=[SimpleTypes.int64#2], table=SimpleTypes, column_index_list=[1])
-    |   |       +-group_by_list=
-    |   |       | +-int64_col#22 := ColumnRef(type=INT64, column=$pre_groupby.int64_col#21)
-    |   |       +-aggregate_list=
-    |   |         +-$agg1#20 :=
-    |   |           +-AggregateFunctionCall(ZetaSQL:max(INT64) -> INT64)
-    |   |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |               +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |               +-Literal(type=INT64, value=1)
-    |   +-group_by_list=
-    |     +-int64_col#24 := ColumnRef(type=INT64, column=$groupby.int64_col#22)
-    |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
-    +-order_by_item_list=
-      +-OrderByItem
-      | +-parse_location=126-127
-      | +-column_ref=
-      |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
-      +-OrderByItem
-        +-parse_location=129-130
-        +-column_ref=
-          +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
---
-ALTERNATION GROUP: 1,1,uint64_col
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.int64_col#24 AS int64_col [INT64]
-| +-$distinct.uint64_col#25 AS uint64_col [UINT64]
-+-query=
-  +-OrderByScan
-    +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20, $query.uint64_col#23]
-    |   |   +-expr_list=
-    |   |   | +-uint64_col#23 :=
-    |   |   |   +-Cast(INT64 -> UINT64)
-    |   |   |     +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
-    |   |   +-input_scan=
-    |   |     +-AggregateScan
-    |   |       +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20]
-    |   |       +-input_scan=
-    |   |       | +-ProjectScan
-    |   |       |   +-column_list=[SimpleTypes.int64#2, $pre_groupby.int64_col#21]
-    |   |       |   +-expr_list=
-    |   |       |   | +-int64_col#21 :=
-    |   |       |   |   +-FunctionCall(ZetaSQL:mod(INT64, INT64) -> INT64)
-    |   |       |   |     +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |       |   |     +-Literal(type=INT64, value=10)
-    |   |       |   +-input_scan=
-    |   |       |     +-TableScan(column_list=[SimpleTypes.int64#2], table=SimpleTypes, column_index_list=[1])
-    |   |       +-group_by_list=
-    |   |       | +-int64_col#22 := ColumnRef(type=INT64, column=$pre_groupby.int64_col#21)
-    |   |       +-aggregate_list=
-    |   |         +-$agg1#20 :=
-    |   |           +-AggregateFunctionCall(ZetaSQL:max(INT64) -> INT64)
-    |   |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |               +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |               +-Literal(type=INT64, value=1)
-    |   +-group_by_list=
-    |     +-int64_col#24 := ColumnRef(type=INT64, column=$groupby.int64_col#22)
-    |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
-    +-order_by_item_list=
-      +-OrderByItem
-      | +-parse_location=126-127
-      | +-column_ref=
-      |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
-      +-OrderByItem
-        +-parse_location=129-139
-        +-column_ref=
-          +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
---
-ALTERNATION GROUP: 1,int64_col,2
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.int64_col#24 AS int64_col [INT64]
-| +-$distinct.uint64_col#25 AS uint64_col [UINT64]
-+-query=
-  +-OrderByScan
-    +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20, $query.uint64_col#23]
-    |   |   +-expr_list=
-    |   |   | +-uint64_col#23 :=
-    |   |   |   +-Cast(INT64 -> UINT64)
-    |   |   |     +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
-    |   |   +-input_scan=
-    |   |     +-AggregateScan
-    |   |       +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20]
-    |   |       +-input_scan=
-    |   |       | +-ProjectScan
-    |   |       |   +-column_list=[SimpleTypes.int64#2, $pre_groupby.int64_col#21]
-    |   |       |   +-expr_list=
-    |   |       |   | +-int64_col#21 :=
-    |   |       |   |   +-FunctionCall(ZetaSQL:mod(INT64, INT64) -> INT64)
-    |   |       |   |     +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |       |   |     +-Literal(type=INT64, value=10)
-    |   |       |   +-input_scan=
-    |   |       |     +-TableScan(column_list=[SimpleTypes.int64#2], table=SimpleTypes, column_index_list=[1])
-    |   |       +-group_by_list=
-    |   |       | +-int64_col#22 := ColumnRef(type=INT64, column=$pre_groupby.int64_col#21)
-    |   |       +-aggregate_list=
-    |   |         +-$agg1#20 :=
-    |   |           +-AggregateFunctionCall(ZetaSQL:max(INT64) -> INT64)
-    |   |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |               +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |               +-Literal(type=INT64, value=1)
-    |   +-group_by_list=
-    |     +-int64_col#24 := ColumnRef(type=INT64, column=$groupby.int64_col#22)
-    |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
-    +-order_by_item_list=
-      +-OrderByItem
-      | +-parse_location=126-135
-      | +-column_ref=
-      |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
-      +-OrderByItem
-        +-parse_location=137-138
-        +-column_ref=
-          +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
+[language_features={{DISALLOW_GROUP_BY_FLOAT|}}]
+select count(distinct int64), count(distinct float) from SimpleTypes
 --
-ALTERNATION GROUP: 1,int64_col,uint64_col
+ALTERNATION GROUP: DISALLOW_GROUP_BY_FLOAT
 --
-QueryStmt
-+-output_column_list=
-| +-$distinct.int64_col#24 AS int64_col [INT64]
-| +-$distinct.uint64_col#25 AS uint64_col [UINT64]
-+-query=
-  +-OrderByScan
-    +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20, $query.uint64_col#23]
-    |   |   +-expr_list=
-    |   |   | +-uint64_col#23 :=
-    |   |   |   +-Cast(INT64 -> UINT64)
-    |   |   |     +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
-    |   |   +-input_scan=
-    |   |     +-AggregateScan
-    |   |       +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20]
-    |   |       +-input_scan=
-    |   |       | +-ProjectScan
-    |   |       |   +-column_list=[SimpleTypes.int64#2, $pre_groupby.int64_col#21]
-    |   |       |   +-expr_list=
-    |   |       |   | +-int64_col#21 :=
-    |   |       |   |   +-FunctionCall(ZetaSQL:mod(INT64, INT64) -> INT64)
-    |   |       |   |     +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |       |   |     +-Literal(type=INT64, value=10)
-    |   |       |   +-input_scan=
-    |   |       |     +-TableScan(column_list=[SimpleTypes.int64#2], table=SimpleTypes, column_index_list=[1])
-    |   |       +-group_by_list=
-    |   |       | +-int64_col#22 := ColumnRef(type=INT64, column=$pre_groupby.int64_col#21)
-    |   |       +-aggregate_list=
-    |   |         +-$agg1#20 :=
-    |   |           +-AggregateFunctionCall(ZetaSQL:max(INT64) -> INT64)
-    |   |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |               +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |               +-Literal(type=INT64, value=1)
-    |   +-group_by_list=
-    |     +-int64_col#24 := ColumnRef(type=INT64, column=$groupby.int64_col#22)
-    |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
-    +-order_by_item_list=
-      +-OrderByItem
-      | +-parse_location=126-135
-      | +-column_ref=
-      |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
-      +-OrderByItem
-        +-parse_location=137-147
-        +-column_ref=
-          +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
+ERROR: Aggregate functions with DISTINCT cannot be used with arguments of type FLOAT [at 1:31]
+select count(distinct int64), count(distinct float) from SimpleTypes
+                              ^
 --
-ALTERNATION GROUP: int64_col,1,2
+ALTERNATION GROUP: 
 --
 QueryStmt
 +-output_column_list=
-| +-$distinct.int64_col#24 AS int64_col [INT64]
-| +-$distinct.uint64_col#25 AS uint64_col [UINT64]
+| +-$aggregate.$agg1#20 AS `$col1` [INT64]
+| +-$aggregate.$agg2#21 AS `$col2` [INT64]
 +-query=
-  +-OrderByScan
-    +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    +-is_ordered=TRUE
+  +-ProjectScan
+    +-column_list=$aggregate.[$agg1#20, $agg2#21]
     +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20, $query.uint64_col#23]
-    |   |   +-expr_list=
-    |   |   | +-uint64_col#23 :=
-    |   |   |   +-Cast(INT64 -> UINT64)
-    |   |   |     +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
-    |   |   +-input_scan=
-    |   |     +-AggregateScan
-    |   |       +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20]
-    |   |       +-input_scan=
-    |   |       | +-ProjectScan
-    |   |       |   +-column_list=[SimpleTypes.int64#2, $pre_groupby.int64_col#21]
-    |   |       |   +-expr_list=
-    |   |       |   | +-int64_col#21 :=
-    |   |       |   |   +-FunctionCall(ZetaSQL:mod(INT64, INT64) -> INT64)
-    |   |       |   |     +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |       |   |     +-Literal(type=INT64, value=10)
-    |   |       |   +-input_scan=
-    |   |       |     +-TableScan(column_list=[SimpleTypes.int64#2], table=SimpleTypes, column_index_list=[1])
-    |   |       +-group_by_list=
-    |   |       | +-int64_col#22 := ColumnRef(type=INT64, column=$pre_groupby.int64_col#21)
-    |   |       +-aggregate_list=
-    |   |         +-$agg1#20 :=
-    |   |           +-AggregateFunctionCall(ZetaSQL:max(INT64) -> INT64)
-    |   |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |               +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |               +-Literal(type=INT64, value=1)
-    |   +-group_by_list=
-    |     +-int64_col#24 := ColumnRef(type=INT64, column=$groupby.int64_col#22)
-    |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
-    +-order_by_item_list=
-      +-OrderByItem
-      | +-parse_location=134-135
-      | +-column_ref=
-      |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
-      +-OrderByItem
-        +-parse_location=137-138
-        +-column_ref=
-          +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
+      +-AggregateScan
+        +-column_list=$aggregate.[$agg1#20, $agg2#21]
+        +-input_scan=
+        | +-TableScan(column_list=SimpleTypes.[int64#2, float#8], table=SimpleTypes, column_index_list=[1, 7])
+        +-aggregate_list=
+          +-$agg1#20 :=
+          | +-AggregateFunctionCall(ZetaSQL:count(INT64) -> INT64)
+          |   +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
+          |   +-distinct=TRUE
+          +-$agg2#21 :=
+            +-AggregateFunctionCall(ZetaSQL:count(FLOAT) -> INT64)
+              +-ColumnRef(type=FLOAT, column=SimpleTypes.float#8)
+              +-distinct=TRUE
+==
+
+[language_features={{DISALLOW_GROUP_BY_FLOAT|}}]
+select double, count(*) from SimpleTypes group by double
+--
+ALTERNATION GROUP: DISALLOW_GROUP_BY_FLOAT
+--
+ERROR: Grouping by expressions of type DOUBLE is not allowed [at 1:51]
+select double, count(*) from SimpleTypes group by double
+                                                  ^
 --
-ALTERNATION GROUP: int64_col,1,uint64_col
+ALTERNATION GROUP: 
 --
 QueryStmt
 +-output_column_list=
-| +-$distinct.int64_col#24 AS int64_col [INT64]
-| +-$distinct.uint64_col#25 AS uint64_col [UINT64]
+| +-$groupby.double#21 AS double [DOUBLE]
+| +-$aggregate.$agg1#20 AS `$col2` [INT64]
 +-query=
-  +-OrderByScan
-    +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    +-is_ordered=TRUE
+  +-ProjectScan
+    +-column_list=[$groupby.double#21, $aggregate.$agg1#20]
     +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20, $query.uint64_col#23]
-    |   |   +-expr_list=
-    |   |   | +-uint64_col#23 :=
-    |   |   |   +-Cast(INT64 -> UINT64)
-    |   |   |     +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
-    |   |   +-input_scan=
-    |   |     +-AggregateScan
-    |   |       +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20]
-    |   |       +-input_scan=
-    |   |       | +-ProjectScan
-    |   |       |   +-column_list=[SimpleTypes.int64#2, $pre_groupby.int64_col#21]
-    |   |       |   +-expr_list=
-    |   |       |   | +-int64_col#21 :=
-    |   |       |   |   +-FunctionCall(ZetaSQL:mod(INT64, INT64) -> INT64)
-    |   |       |   |     +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |       |   |     +-Literal(type=INT64, value=10)
-    |   |       |   +-input_scan=
-    |   |       |     +-TableScan(column_list=[SimpleTypes.int64#2], table=SimpleTypes, column_index_list=[1])
-    |   |       +-group_by_list=
-    |   |       | +-int64_col#22 := ColumnRef(type=INT64, column=$pre_groupby.int64_col#21)
-    |   |       +-aggregate_list=
-    |   |         +-$agg1#20 :=
-    |   |           +-AggregateFunctionCall(ZetaSQL:max(INT64) -> INT64)
-    |   |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |               +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |               +-Literal(type=INT64, value=1)
-    |   +-group_by_list=
-    |     +-int64_col#24 := ColumnRef(type=INT64, column=$groupby.int64_col#22)
-    |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
-    +-order_by_item_list=
-      +-OrderByItem
-      | +-parse_location=134-135
-      | +-column_ref=
-      |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
-      +-OrderByItem
-        +-parse_location=137-147
-        +-column_ref=
-          +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
---
-ALTERNATION GROUP: int64_col,int64_col,2
+      +-AggregateScan
+        +-column_list=[$groupby.double#21, $aggregate.$agg1#20]
+        +-input_scan=
+        | +-TableScan(column_list=[SimpleTypes.double#9], table=SimpleTypes, column_index_list=[8])
+        +-group_by_list=
+        | +-double#21 := ColumnRef(type=DOUBLE, column=SimpleTypes.double#9)
+        +-aggregate_list=
+          +-$agg1#20 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
+==
+
+select count(*), count(CAST(NULL AS STRUCT)) from KeyValue
 --
 QueryStmt
 +-output_column_list=
-| +-$distinct.int64_col#24 AS int64_col [INT64]
-| +-$distinct.uint64_col#25 AS uint64_col [UINT64]
+| +-$aggregate.$agg1#3 AS `$col1` [INT64]
+| +-$aggregate.$agg2#4 AS `$col2` [INT64]
 +-query=
-  +-OrderByScan
-    +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    +-is_ordered=TRUE
+  +-ProjectScan
+    +-column_list=$aggregate.[$agg1#3, $agg2#4]
     +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=$distinct.[int64_col#24, uint64_col#25]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20, $query.uint64_col#23]
-    |   |   +-expr_list=
-    |   |   | +-uint64_col#23 :=
-    |   |   |   +-Cast(INT64 -> UINT64)
-    |   |   |     +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
-    |   |   +-input_scan=
-    |   |     +-AggregateScan
-    |   |       +-column_list=[$groupby.int64_col#22, $aggregate.$agg1#20]
-    |   |       +-input_scan=
-    |   |       | +-ProjectScan
-    |   |       |   +-column_list=[SimpleTypes.int64#2, $pre_groupby.int64_col#21]
-    |   |       |   +-expr_list=
-    |   |       |   | +-int64_col#21 :=
-    |   |       |   |   +-FunctionCall(ZetaSQL:mod(INT64, INT64) -> INT64)
-    |   |       |   |     +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |       |   |     +-Literal(type=INT64, value=10)
-    |   |       |   +-input_scan=
-    |   |       |     +-TableScan(column_list=[SimpleTypes.int64#2], table=SimpleTypes, column_index_list=[1])
-    |   |       +-group_by_list=
-    |   |       | +-int64_col#22 := ColumnRef(type=INT64, column=$pre_groupby.int64_col#21)
-    |   |       +-aggregate_list=
-    |   |         +-$agg1#20 :=
-    |   |           +-AggregateFunctionCall(ZetaSQL:max(INT64) -> INT64)
-    |   |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |   |               +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
-    |   |               +-Literal(type=INT64, value=1)
-    |   +-group_by_list=
-    |     +-int64_col#24 := ColumnRef(type=INT64, column=$groupby.int64_col#22)
-    |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
-    +-order_by_item_list=
-      +-OrderByItem
-      | +-parse_location=134-143
-      | +-column_ref=
-      |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
-      +-OrderByItem
-        +-parse_location=145-146
-        +-column_ref=
-          +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
---
-ALTERNATION GROUP: int64_col,int64_col,uint64_col
+      +-AggregateScan
+        +-column_list=$aggregate.[$agg1#3, $agg2#4]
+        +-input_scan=
+        | +-TableScan(table=KeyValue)
+        +-aggregate_list=
+          +-$agg1#3 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
+          +-$agg2#4 :=
+            +-AggregateFunctionCall(ZetaSQL:count(STRUCT) -> INT64)
+              +-Literal(type=STRUCT, value=NULL, has_explicit_type=TRUE)
+==
+
+SELECT DISTINCT MOD(int64, 10) int64_col,
+       CAST(MAX(int64+1) AS UINT64) uint64_col
+FROM SimpleTypes
+GROUP BY {{1|int64_col}}
+ORDER BY {{1|int64_col}}, {{2|uint64_col}}
 --
 QueryStmt
 +-output_column_list=
@@ -5500,11 +4214,9 @@ QueryStmt
     |     +-uint64_col#25 := ColumnRef(type=UINT64, column=$query.uint64_col#23)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=134-143
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.int64_col#24)
       +-OrderByItem
-        +-parse_location=145-155
         +-column_ref=
           +-ColumnRef(type=UINT64, column=$distinct.uint64_col#25)
 ==
@@ -5919,11 +4631,9 @@ QueryStmt
     |             |     +-a#7 := ColumnRef(type=INT64, column=$groupby.a#4)
     |             +-order_by_item_list=
     |               +-OrderByItem
-    |               | +-parse_location=107-108
     |               | +-column_ref=
     |               |   +-ColumnRef(type=INT64, column=$distinct.c#6)
     |               +-OrderByItem
-    |                 +-parse_location=110-111
     |                 +-column_ref=
     |                   +-ColumnRef(type=INT64, column=$distinct.a#7)
     +-input_scan=
@@ -6859,7 +5569,6 @@ QueryStmt
               |     +-ColumnRef(type=STRING, column=KeyValue.Value#2)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=47-50
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=KeyValue.Key#1)
               +-limit=
@@ -7114,7 +5823,6 @@ QueryStmt
               |     +-ColumnRef(type=STRING, column=KeyValue.Value#2)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=102-105
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=KeyValue.Key#1)
               +-limit=
@@ -7146,7 +5854,6 @@ QueryStmt
               |     +-ColumnRef(type=STRING, column=KeyValue.Value#2)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=102-105
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=KeyValue.Key#1)
               +-limit=
@@ -7178,7 +5885,6 @@ QueryStmt
               |     +-ColumnRef(type=STRING, column=KeyValue.Value#2)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=103-106
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=KeyValue.Key#1)
               +-limit=
@@ -7210,7 +5916,6 @@ QueryStmt
               |     +-ColumnRef(type=STRING, column=KeyValue.Value#2)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=103-106
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=KeyValue.Key#1)
               +-limit=
@@ -7982,7 +6687,6 @@ QueryStmt
         |   |     +-$agg1#5 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=100-101
         |       +-column_ref=
         |         +-ColumnRef(type=INT64, column=$aggregate.$agg1#5)
         +-limit=
@@ -8044,7 +6748,6 @@ QueryStmt
     |     +-$agg1#7 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=71-72
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg1#7)
 ==
@@ -8196,6 +6899,5 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=91-135
                   +-column_ref=
-                    +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#4)
\ No newline at end of file
+                    +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#4)
diff --git a/zetasql/analyzer/testdata/alias_ambiguity.test b/zetasql/analyzer/testdata/alias_ambiguity.test
index a995e109c..592340c97 100644
--- a/zetasql/analyzer/testdata/alias_ambiguity.test
+++ b/zetasql/analyzer/testdata/alias_ambiguity.test
@@ -8,93 +8,6 @@ select {{key | keyvalue.key | key as key | keyvalue.key as key}}
 from keyvalue
 order by {{key | keyvalue.key}}
 --
-ALTERNATION GROUP: key ,key 
---
-QueryStmt
-+-output_column_list=
-| +-KeyValue.Key#1 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[KeyValue.Key#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=35-38
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=KeyValue.Key#1)
---
-ALTERNATION GROUP: key , keyvalue.key
---
-QueryStmt
-+-output_column_list=
-| +-KeyValue.Key#1 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[KeyValue.Key#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=36-48
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=KeyValue.Key#1)
---
-ALTERNATION GROUP:  keyvalue.key ,key 
---
-QueryStmt
-+-output_column_list=
-| +-KeyValue.Key#1 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[KeyValue.Key#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=45-48
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=KeyValue.Key#1)
---
-ALTERNATION GROUP:  keyvalue.key , keyvalue.key
---
-QueryStmt
-+-output_column_list=
-| +-KeyValue.Key#1 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[KeyValue.Key#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=46-58
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=KeyValue.Key#1)
---
-ALTERNATION GROUP:  key as key ,key 
---
-QueryStmt
-+-output_column_list=
-| +-KeyValue.Key#1 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[KeyValue.Key#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=43-46
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=KeyValue.Key#1)
---
-ALTERNATION GROUP:  key as key , keyvalue.key
---
 QueryStmt
 +-output_column_list=
 | +-KeyValue.Key#1 AS key [INT64]
@@ -106,41 +19,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=44-56
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=KeyValue.Key#1)
---
-ALTERNATION GROUP:  keyvalue.key as key,key 
---
-QueryStmt
-+-output_column_list=
-| +-KeyValue.Key#1 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[KeyValue.Key#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=51-54
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=KeyValue.Key#1)
---
-ALTERNATION GROUP:  keyvalue.key as key, keyvalue.key
---
-QueryStmt
-+-output_column_list=
-| +-KeyValue.Key#1 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[KeyValue.Key#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=52-64
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -160,7 +38,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=51-55
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -188,7 +65,6 @@ QueryStmt
     |     +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=43-46
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.key#3)
 ==
@@ -208,7 +84,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=43-48
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -248,7 +123,6 @@ QueryStmt
     | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-39
         +-column_ref=
           +-ColumnRef(type=STRING, column=KeyValue.Value#2)
 ==
@@ -268,7 +142,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=43-46
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -698,7 +571,6 @@ QueryStmt
     |     +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=47-50
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.key#3)
 ==
@@ -708,30 +580,6 @@ from keyvalue
 group by 1
 order by key
 --
-ALTERNATION GROUP: key 
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.key#3 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.key#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.key#3]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=46-49
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$groupby.key#3)
---
-ALTERNATION GROUP:  keyvalue.key 
---
 QueryStmt
 +-output_column_list=
 | +-$groupby.key#3 AS key [INT64]
@@ -748,51 +596,6 @@ QueryStmt
     |     +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=56-59
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$groupby.key#3)
---
-ALTERNATION GROUP:  key as key 
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.key#3 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.key#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.key#3]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=54-57
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$groupby.key#3)
---
-ALTERNATION GROUP:  keyvalue.key as key
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.key#3 AS key [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.key#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.key#3]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=62-65
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.key#3)
 ==
@@ -818,7 +621,6 @@ QueryStmt
     |     +-key1#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=53-57
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.key1#3)
 ==
@@ -844,7 +646,6 @@ QueryStmt
     |     +-value#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=54-59
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.value#3)
 ==
@@ -870,7 +671,6 @@ QueryStmt
     |     +-key1#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=53-56
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.key1#3)
 ==
@@ -900,7 +700,6 @@ QueryStmt
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=52-60
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg1#4)
 ==
@@ -930,11 +729,9 @@ QueryStmt
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=52-55
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.key#3)
       +-OrderByItem
-        +-parse_location=57-65
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg1#4)
 ==
@@ -1218,45 +1015,6 @@ select {{kv|kv as kv}}
 from KeyValue kv
 order by kv.key
 --
-ALTERNATION GROUP: kv
---
-QueryStmt
-+-output_column_list=
-| +-$query.kv#4 AS kv [STRUCT]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.kv#4]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[KeyValue.Key#1, KeyValue.Value#2, $query.kv#4, $orderby.$orderbycol1#5]
-    |   +-expr_list=
-    |   | +-$orderbycol1#5 :=
-    |   |   +-GetStructField
-    |   |     +-type=INT64
-    |   |     +-expr=
-    |   |     | +-ColumnRef(type=STRUCT, column=$query.kv#4)
-    |   |     +-field_idx=0
-    |   +-input_scan=
-    |     +-ProjectScan
-    |       +-column_list=[KeyValue.Key#1, KeyValue.Value#2, $query.kv#4]
-    |       +-expr_list=
-    |       | +-kv#4 :=
-    |       |   +-MakeStruct
-    |       |     +-type=STRUCT
-    |       |     +-field_list=
-    |       |       +-ColumnRef(type=INT64, column=KeyValue.Key#1)
-    |       |       +-ColumnRef(type=STRING, column=KeyValue.Value#2)
-    |       +-input_scan=
-    |         +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1], alias="kv")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=36-42
-        +-column_ref=
-          +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
---
-ALTERNATION GROUP: kv as kv
---
 QueryStmt
 +-output_column_list=
 | +-$query.kv#4 AS kv [STRUCT]
@@ -1288,7 +1046,6 @@ QueryStmt
     |         +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1], alias="kv")
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=42-48
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
 ==
@@ -1359,7 +1116,6 @@ QueryStmt
     |     +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0], alias="kv")
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=50-52
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.kv#3)
 ==
@@ -1402,7 +1158,6 @@ QueryStmt
     |             +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=69-72
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.key#4)
 ==
@@ -1447,7 +1202,6 @@ QueryStmt
     |             +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=78-81
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.key#4)
 ==
@@ -1490,7 +1244,6 @@ QueryStmt
     |             +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=74-80
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -1524,7 +1277,6 @@ QueryStmt
     |   +-element_column_list=[$array.v#4]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=71-72
         +-column_ref=
           +-ColumnRef(type=INT32, column=$array.v#4)
 ==
@@ -1576,7 +1328,6 @@ QueryStmt
     |   +-element_column_list=[$array.v#4]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=71-72
         +-column_ref=
           +-ColumnRef(type=INT32, column=$array.v#4)
 ==
@@ -1746,4 +1497,4 @@ QueryStmt
         |   +-input_scan=
         |     +-SingleRowScan
         +-group_by_list=
-          +-a#2 := ColumnRef(type=INT64, column=$subquery1.a#1)
\ No newline at end of file
+          +-a#2 := ColumnRef(type=INT64, column=$subquery1.a#1)
diff --git a/zetasql/analyzer/testdata/analytic_function_named_window.test b/zetasql/analyzer/testdata/analytic_function_named_window.test
index fbff299f2..638625668 100644
--- a/zetasql/analyzer/testdata/analytic_function_named_window.test
+++ b/zetasql/analyzer/testdata/analytic_function_named_window.test
@@ -755,11 +755,9 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=339-359
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$analytic.$analytic8#17)
       +-OrderByItem
-        +-parse_location=370-406
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic9#18)
 ==
diff --git a/zetasql/analyzer/testdata/analytic_function_partitionby_orderby.test b/zetasql/analyzer/testdata/analytic_function_partitionby_orderby.test
index 66d1eb8e8..ef7faffdc 100644
--- a/zetasql/analyzer/testdata/analytic_function_partitionby_orderby.test
+++ b/zetasql/analyzer/testdata/analytic_function_partitionby_orderby.test
@@ -67,7 +67,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=UNBOUNDED FOLLOWING)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=59-523
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#27)
 ==
@@ -255,7 +254,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=59-570
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#27)
 ==
@@ -414,7 +412,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=39-91
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#3)
 ==
@@ -533,6 +530,5 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=53-109
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#5)
diff --git a/zetasql/analyzer/testdata/analytic_functions.test b/zetasql/analyzer/testdata/analytic_functions.test
index e92e9a7a9..ad168a614 100644
--- a/zetasql/analyzer/testdata/analytic_functions.test
+++ b/zetasql/analyzer/testdata/analytic_functions.test
@@ -663,7 +663,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=47-117
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#8)
 ==
@@ -776,7 +775,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=47-138
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#8)
 ==
@@ -1097,16 +1095,13 @@ QueryStmt
     |                       +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=83-151
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$analytic.$analytic1#7)
       +-OrderByItem
-      | +-parse_location=162-212
       | +-column_ref=
       | | +-ColumnRef(type=INT64, column=$analytic.$analytic2#8)
       | +-is_descending=TRUE
       +-OrderByItem
-        +-parse_location=223-281
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol3#11)
 ==
@@ -1181,7 +1176,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=76-133
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#6)
 ==
@@ -1223,7 +1217,6 @@ QueryStmt
     |     +-analyticcol#6 := ColumnRef(type=INT64, column=$analytic.analyticcol#4)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=87-98
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.analyticcol#6)
 ==
@@ -1265,11 +1258,9 @@ QueryStmt
     |     +-analyticcol#6 := ColumnRef(type=INT64, column=$analytic.analyticcol#4)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=87-88
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.Key#5)
       +-OrderByItem
-        +-parse_location=90-91
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.analyticcol#6)
 ==
@@ -1822,15 +1813,12 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=UNBOUNDED FOLLOWING)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=46-72
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$analytic.$analytic1#20)
       +-OrderByItem
-      | +-parse_location=83-128
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$analytic.$analytic2#21)
       +-OrderByItem
-        +-parse_location=139-161
         +-column_ref=
           +-ColumnRef(type=INT32, column=$analytic.$analytic3#22)
 ==
@@ -1883,7 +1871,6 @@ QueryStmt
     |         +-$analytic1#22 := AnalyticFunctionCall(ZetaSQL:rank() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=94-120
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#22)
 ==
@@ -1922,7 +1909,6 @@ QueryStmt
     |         +-$analytic1#21 := AnalyticFunctionCall(ZetaSQL:rank() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=69-97
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#21)
 ==
@@ -2545,7 +2531,6 @@ QueryStmt
     |         +-$analytic1#4 := AnalyticFunctionCall(ZetaSQL:rank() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=47-132
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#4)
 ==
@@ -2628,7 +2613,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-102
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#5)
 ==
diff --git a/zetasql/analyzer/testdata/anonymization.test b/zetasql/analyzer/testdata/anonymization.test
index 13afff3b9..b49093959 100644
--- a/zetasql/analyzer/testdata/anonymization.test
+++ b/zetasql/analyzer/testdata/anonymization.test
@@ -2032,7 +2032,6 @@ QueryStmt
         |   |   |           +-Literal(type=INT64, value=0)
         |   |   +-order_by_item_list=
         |   |     +-OrderByItem
-        |   |       +-parse_location=265-272
         |   |       +-column_ref=
         |   |         +-ColumnRef(type=INT64, column=$aggregate.int64#13)
         |   +-limit=
@@ -2104,7 +2103,6 @@ QueryStmt
         |   |   |   |           +-Literal(type=INT64, value=0)
         |   |   |   +-order_by_item_list=
         |   |   |     +-OrderByItem
-        |   |   |       +-parse_location=265-272
         |   |   |       +-column_ref=
         |   |   |         +-ColumnRef(type=INT64, column=$aggregate.int64#13)
         |   |   +-limit=
@@ -8469,7 +8467,6 @@ QueryStmt
         |   | |   |     |           |       +-Literal(type=STRING, value="x")
         |   | |   |     |           +-order_by_item_list=
         |   | |   |     |             +-OrderByItem
-        |   | |   |     |               +-parse_location=231-234
         |   | |   |     |               +-column_ref=
         |   | |   |     |                 +-ColumnRef(type=INT64, column=$array_offset.off#28)
         |   | |   |     +-input_scan=
@@ -8618,7 +8615,6 @@ QueryStmt
     |         |           |       +-Literal(parse_location=65-68, type=STRING, value="x")
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#28)
     |         +-input_scan=
@@ -9783,7 +9779,6 @@ QueryStmt
         |     +-ColumnRef(type=INT64, column=SimpleTypesWithAnonymizationUid.int64#2)
         |     +-order_by_item_list=
         |       +-OrderByItem
-        |         +-parse_location=202-207
         |         +-column_ref=
         |           +-ColumnRef(type=INT64, column=SimpleTypesWithAnonymizationUid.int64#2)
         +-anonymization_option_list=
@@ -9812,7 +9807,6 @@ QueryStmt
         |     +-ColumnRef(type=INT64, column=SimpleTypesWithAnonymizationUid.int64#2)
         |     +-order_by_item_list=
         |       +-OrderByItem
-        |         +-parse_location=202-207
         |         +-column_ref=
         |           +-ColumnRef(type=INT64, column=SimpleTypesWithAnonymizationUid.int64#2)
         +-anonymization_option_list=
diff --git a/zetasql/analyzer/testdata/anonymization_subquery.test b/zetasql/analyzer/testdata/anonymization_subquery.test
index 88a423374..7300ec0a8 100644
--- a/zetasql/analyzer/testdata/anonymization_subquery.test
+++ b/zetasql/analyzer/testdata/anonymization_subquery.test
@@ -501,7 +501,6 @@ QueryStmt
     |     +-kappa := Literal(type=INT64, value=100)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=313-324
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$aggregate.weight#6)
         +-is_descending=TRUE
@@ -600,7 +599,6 @@ QueryStmt
     |     +-kappa := Literal(type=INT64, value=100)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=313-324
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$aggregate.weight#6)
         +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/array_aggregate.test b/zetasql/analyzer/testdata/array_aggregate.test
index ca8c39511..e27533b0f 100644
--- a/zetasql/analyzer/testdata/array_aggregate.test
+++ b/zetasql/analyzer/testdata/array_aggregate.test
@@ -72,7 +72,6 @@ QueryStmt
               |     +-Literal(type=INT64, value=3)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=250-255
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
               +-limit=
@@ -188,7 +187,6 @@ QueryStmt
         |             |         |   |     +-distinct.arg#12 := ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#9)
         |             |         |   +-order_by_item_list=
         |             |         |     +-OrderByItem
-        |             |         |       +-parse_location=250-255
         |             |         |       +-column_ref=
         |             |         |         +-ColumnRef(type=INT64, column=$agg_rewriter.distinct.arg#12)
         |             |         +-limit=
@@ -347,7 +345,6 @@ QueryStmt
           |   +-ColumnRef(type=INT64, column=$array.a#4)
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=28-29
           |       +-column_ref=
           |         +-ColumnRef(type=INT64, column=$array.a#4)
           +-$agg2#6 :=
@@ -355,7 +352,6 @@ QueryStmt
               +-ColumnRef(type=INT64, column=$array.a#4)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=53-54
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$array.a#4)
 
@@ -437,7 +433,6 @@ QueryStmt
         | |           |         |       +-element_column_list=[$agg_rewriter.$struct#9]
         | |           |         +-order_by_item_list=
         | |           |           +-OrderByItem
-        | |           |             +-parse_location=28-29
         | |           |             +-column_ref=
         | |           |               +-ColumnRef(type=INT64, column=$agg_rewriter.a#7)
         | |           +-input_scan=
@@ -492,7 +487,6 @@ QueryStmt
         |             |         |       +-element_column_list=[$agg_rewriter.$struct#13]
         |             |         +-order_by_item_list=
         |             |           +-OrderByItem
-        |             |             +-parse_location=53-54
         |             |             +-column_ref=
         |             |               +-ColumnRef(type=INT64, column=$agg_rewriter.a#11)
         |             +-input_scan=
@@ -628,7 +622,6 @@ QueryStmt
           |   +-ColumnRef(type=INT64, column=$array.a#4)
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=28-29
           |       +-column_ref=
           |         +-ColumnRef(type=UINT64, column=$array.b#5)
           +-$agg2#7 :=
@@ -636,7 +629,6 @@ QueryStmt
               +-ColumnRef(type=UINT64, column=$array.b#5)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=53-54
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$array.a#4)
 
@@ -732,7 +724,6 @@ QueryStmt
         | |           |         |       +-element_column_list=[$agg_rewriter.$struct#11]
         | |           |         +-order_by_item_list=
         | |           |           +-OrderByItem
-        | |           |             +-parse_location=28-29
         | |           |             +-column_ref=
         | |           |               +-ColumnRef(type=UINT64, column=$agg_rewriter.b#9)
         | |           +-input_scan=
@@ -793,7 +784,6 @@ QueryStmt
         |             |         |       +-element_column_list=[$agg_rewriter.$struct#16]
         |             |         +-order_by_item_list=
         |             |           +-OrderByItem
-        |             |             +-parse_location=53-54
         |             |             +-column_ref=
         |             |               +-ColumnRef(type=INT64, column=$agg_rewriter.a#13)
         |             +-input_scan=
@@ -963,7 +953,6 @@ QueryStmt
           |   |       +-ColumnRef(type=UINT64, column=$array.b#5)
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=132-133
           |       +-column_ref=
           |         +-ColumnRef(type=UINT64, column=$array.b#5)
           +-$agg2#7 :=
@@ -982,7 +971,6 @@ QueryStmt
               |     +-Literal(type=INT64, value=3)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=188-189
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$array.a#4)
 
@@ -1084,7 +1072,6 @@ QueryStmt
         | |           |         |       +-element_column_list=[$agg_rewriter.$struct#13]
         | |           |         +-order_by_item_list=
         | |           |           +-OrderByItem
-        | |           |             +-parse_location=132-133
         | |           |             +-column_ref=
         | |           |               +-ColumnRef(type=UINT64, column=$agg_rewriter.b#11)
         | |           +-input_scan=
@@ -1145,7 +1132,6 @@ QueryStmt
         |             |         |       +-element_column_list=[$agg_rewriter.$struct#18]
         |             |         +-order_by_item_list=
         |             |           +-OrderByItem
-        |             |             +-parse_location=188-189
         |             |             +-column_ref=
         |             |               +-ColumnRef(type=INT64, column=$agg_rewriter.a#15)
         |             +-input_scan=
@@ -1386,7 +1372,6 @@ QueryStmt
               +-ColumnRef(type=INT64, column=$array.a#4)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=74-77
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
               +-limit=
@@ -1544,7 +1529,6 @@ QueryStmt
         |             |         |   |       +-element_column_list=[$agg_rewriter.$struct#15]
         |             |         |   +-order_by_item_list=
         |             |         |     +-OrderByItem
-        |             |         |       +-parse_location=74-77
         |             |         |       +-column_ref=
         |             |         |         +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#13)
         |             |         +-limit=
@@ -1724,7 +1708,6 @@ QueryStmt
         |                   |     +-ColumnRef(type=INT64, column=t.correlated#2, is_correlated=TRUE)
         |                   +-order_by_item_list=
         |                   | +-OrderByItem
-        |                   |   +-parse_location=139-142
         |                   |   +-column_ref=
         |                   |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#7)
         |                   +-limit=
@@ -1859,7 +1842,6 @@ QueryStmt
         |             |             |         |   |       +-element_column_list=[$agg_rewriter.$struct#13]
         |             |             |         |   +-order_by_item_list=
         |             |             |         |     +-OrderByItem
-        |             |             |         |       +-parse_location=139-142
         |             |             |         |       +-column_ref=
         |             |             |         |         +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#11)
         |             |             |         +-limit=
diff --git a/zetasql/analyzer/testdata/array_concat_aggregate.test b/zetasql/analyzer/testdata/array_concat_aggregate.test
index 88a11cb96..9d869a37c 100644
--- a/zetasql/analyzer/testdata/array_concat_aggregate.test
+++ b/zetasql/analyzer/testdata/array_concat_aggregate.test
@@ -45,7 +45,6 @@ QueryStmt
               |     +-Literal(type=INT64, value=3)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=272-314
               |   +-column_ref=
               |     +-ColumnRef(type=ARRAY, column=$orderby.$orderbycol1#4)
               +-limit=
@@ -188,7 +187,6 @@ QueryStmt
         |             |             |             |   |         +-ColumnRef(type=ARRAY, column=$agg_rewriter.$orderbycol1#6)
         |             |             |             |   +-order_by_item_list=
         |             |             |             |     +-OrderByItem
-        |             |             |             |       +-parse_location=272-314
         |             |             |             |       +-column_ref=
         |             |             |             |         +-ColumnRef(type=ARRAY, column=$agg_rewriter.$orderbycol1#6)
         |             |             |             +-limit=
@@ -279,7 +277,6 @@ QueryStmt
           |     +-default_value=[]
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=65-110
           |       +-column_ref=
           |         +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           +-$agg2#7 :=
@@ -292,7 +289,6 @@ QueryStmt
                 +-default_value=[]
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=177-222
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
 
@@ -447,7 +443,6 @@ QueryStmt
         | |           |             |             |         +-ColumnRef(type=ARRAY, column=$agg_rewriter.$arg#8)
         | |           |             |             +-order_by_item_list=
         | |           |             |               +-OrderByItem
-        | |           |             |                 +-parse_location=65-110
         | |           |             |                 +-column_ref=
         | |           |             |                   +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#10)
         | |           |             +-input_scan=
@@ -593,7 +588,6 @@ QueryStmt
         |             |             |             |         +-ColumnRef(type=ARRAY, column=$agg_rewriter.$arg#15)
         |             |             |             +-order_by_item_list=
         |             |             |               +-OrderByItem
-        |             |             |                 +-parse_location=177-222
         |             |             |                 +-column_ref=
         |             |             |                   +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#17)
         |             |             +-input_scan=
@@ -697,7 +691,6 @@ QueryStmt
           |     +-default_value=[]
           |   +-order_by_item_list=
           |   | +-OrderByItem
-          |   |   +-parse_location=128-173
           |   |   +-column_ref=
           |   |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           |   +-limit=
@@ -712,7 +705,6 @@ QueryStmt
                 +-default_value=[]
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=248-305
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
 
@@ -871,7 +863,6 @@ QueryStmt
         | |           |             |             |   |         +-ColumnRef(type=ARRAY, column=$agg_rewriter.$arg#8)
         | |           |             |             |   +-order_by_item_list=
         | |           |             |             |     +-OrderByItem
-        | |           |             |             |       +-parse_location=128-173
         | |           |             |             |       +-column_ref=
         | |           |             |             |         +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#10)
         | |           |             |             +-limit=
@@ -1021,7 +1012,6 @@ QueryStmt
         |             |             |             |         +-ColumnRef(type=ARRAY, column=$agg_rewriter.$arg#15)
         |             |             |             +-order_by_item_list=
         |             |             |               +-OrderByItem
-        |             |             |                 +-parse_location=248-305
         |             |             |                 +-column_ref=
         |             |             |                   +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#17)
         |             |             +-input_scan=
@@ -1128,7 +1118,6 @@ QueryStmt
                 +-default_value=[]
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=162-209
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
               +-limit=
@@ -1429,7 +1418,6 @@ QueryStmt
         |             |             |             |   |         +-ColumnRef(type=ARRAY, column=$agg_rewriter.$arg#13)
         |             |             |             |   +-order_by_item_list=
         |             |             |             |     +-OrderByItem
-        |             |             |             |       +-parse_location=162-209
         |             |             |             |       +-column_ref=
         |             |             |             |         +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#15)
         |             |             |             +-limit=
diff --git a/zetasql/analyzer/testdata/array_element.test b/zetasql/analyzer/testdata/array_element.test
index d7dc2d148..8b4308edd 100644
--- a/zetasql/analyzer/testdata/array_element.test
+++ b/zetasql/analyzer/testdata/array_element.test
@@ -839,7 +839,6 @@ QueryStmt
     |         |           |   |           +-ColumnRef(type=STRING, column=$subquery1.k#4, is_correlated=TRUE)
     |         |           |   +-order_by_item_list=
     |         |           |     +-OrderByItem
-    |         |           |       +-parse_location=192-203
     |         |           |       +-column_ref=
     |         |           |       | +-ColumnRef(type=INT64, column=$array_offset.offset#7)
     |         |           |       +-is_descending=TRUE
@@ -962,7 +961,6 @@ QueryStmt
     |         |       |     |   |           +-ColumnRef(type=INT64, column=$subquery1.k#4, is_correlated=TRUE)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=328-339
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#7)
     |         |       |     |       +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/array_filter.test b/zetasql/analyzer/testdata/array_filter.test
index f953ba369..3009df7a0 100644
--- a/zetasql/analyzer/testdata/array_filter.test
+++ b/zetasql/analyzer/testdata/array_filter.test
@@ -73,7 +73,6 @@ QueryStmt
     |         |           |       +-Literal(type=INT64, value=0)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#5)
     |         +-input_scan=
@@ -162,7 +161,6 @@ QueryStmt
     |           |           |       +-Literal(type=INT64, value=0)
     |           |           +-order_by_item_list=
     |           |             +-OrderByItem
-    |           |               +-parse_location=231-234
     |           |               +-column_ref=
     |           |                 +-ColumnRef(type=INT64, column=$array_offset.off#5)
     |           +-input_scan=
@@ -249,7 +247,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=236-239
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |         +-input_scan=
@@ -341,7 +338,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=INT64, column=KeyValue.Key#1, is_correlated=TRUE)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#7)
     |         +-input_scan=
@@ -437,7 +433,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=INT64, column=KeyValue.Key#1, is_correlated=TRUE)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=236-239
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |         +-input_scan=
@@ -528,7 +523,6 @@ QueryStmt
     |         |           |       +-Literal(type=INT64, value=0)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#7)
     |         +-input_scan=
@@ -626,7 +620,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=INT64, column=KeyValue.Key#1, is_correlated=TRUE)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#7)
     |         +-input_scan=
@@ -733,7 +726,6 @@ QueryStmt
     |         |           |       +-Literal(type=INT64, value=1)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#12)
     |         +-input_scan=
@@ -781,7 +773,6 @@ QueryStmt
     |             |         |           |       +-Literal(type=INT64, value=0)
     |             |         |           +-order_by_item_list=
     |             |         |             +-OrderByItem
-    |             |         |               +-parse_location=231-234
     |             |         |               +-column_ref=
     |             |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |             |         +-input_scan=
@@ -912,7 +903,6 @@ QueryStmt
     |         |         |           |       +-Literal(type=INT64, value=1)
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=231-234
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#13)
     |         |         +-input_scan=
@@ -960,7 +950,6 @@ QueryStmt
     |         |             |         |           |       +-Literal(type=INT64, value=0)
     |         |             |         |           +-order_by_item_list=
     |         |             |         |             +-OrderByItem
-    |         |             |         |               +-parse_location=231-234
     |         |             |         |               +-column_ref=
     |         |             |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#9)
     |         |             |         +-input_scan=
@@ -1113,7 +1102,6 @@ QueryStmt
     |         |           |               |           |       +-Literal(type=INT64, value=0)
     |         |           |               |           +-order_by_item_list=
     |         |           |               |             +-OrderByItem
-    |         |           |               |               +-parse_location=231-234
     |         |           |               |               +-column_ref=
     |         |           |               |                 +-ColumnRef(type=INT64, column=$array_offset.off#17)
     |         |           |               +-input_scan=
@@ -1129,7 +1117,6 @@ QueryStmt
     |         |           |                     +-SingleRowScan
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#12)
     |         +-input_scan=
@@ -1296,7 +1283,6 @@ QueryStmt
     |         |         |           |               |           |       +-Literal(type=INT64, value=0)
     |         |         |           |               |           +-order_by_item_list=
     |         |         |           |               |             +-OrderByItem
-    |         |         |           |               |               +-parse_location=231-234
     |         |         |           |               |               +-column_ref=
     |         |         |           |               |                 +-ColumnRef(type=INT64, column=$array_offset.off#18)
     |         |         |           |               +-input_scan=
@@ -1312,7 +1298,6 @@ QueryStmt
     |         |         |           |                     +-SingleRowScan
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=231-234
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#13)
     |         |         +-input_scan=
@@ -1429,7 +1414,6 @@ QueryStmt
     |         |         |           |       +-Literal(type=INT64, value=1)
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=231-234
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |         |         +-input_scan=
@@ -1553,7 +1537,6 @@ QueryStmt
     |         |         |           |       +-ColumnRef(type=INT64, column=$array.element#7)
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=231-234
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |         |         +-input_scan=
@@ -1657,7 +1640,6 @@ QueryStmt
     |         |           |       +-Literal(type=INT64, value=0)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=231-234
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#11)
     |         +-input_scan=
@@ -1704,7 +1686,6 @@ QueryStmt
     |             |         |           |         +-ColumnHolder(column=$array_offset.off#6)
     |             |         |           +-order_by_item_list=
     |             |         |             +-OrderByItem
-    |             |         |               +-parse_location=216-219
     |             |         |               +-column_ref=
     |             |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |             |         +-input_scan=
@@ -1834,7 +1815,6 @@ QueryStmt
     |         |           |   |           |           |       +-Literal(type=INT64, value=1)
     |         |           |   |           |           +-order_by_item_list=
     |         |           |   |           |             +-OrderByItem
-    |         |           |   |           |               +-parse_location=231-234
     |         |           |   |           |               +-column_ref=
     |         |           |   |           |                 +-ColumnRef(type=INT64, column=$array_offset.off#16)
     |         |           |   |           +-input_scan=
@@ -1857,7 +1837,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#10)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=216-219
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#10)
     |         +-input_scan=
@@ -2026,7 +2005,6 @@ QueryStmt
     |   |         |           |       +-Literal(type=INT64, value=1)
     |   |         |           +-order_by_item_list=
     |   |         |             +-OrderByItem
-    |   |         |               +-parse_location=231-234
     |   |         |               +-column_ref=
     |   |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#10)
     |   |         +-input_scan=
@@ -2127,7 +2105,6 @@ QueryStmt
     |         |     |       |       +-Literal(type=INT64, value=0)
     |         |     |       +-order_by_item_list=
     |         |     |         +-OrderByItem
-    |         |     |           +-parse_location=243-246
     |         |     |           +-column_ref=
     |         |     |             +-ColumnRef(type=INT64, column=$array_offset.off#5)
     |         |     +-Literal(type=ARRAY, value=NULL, has_explicit_type=TRUE)
diff --git a/zetasql/analyzer/testdata/array_functions.test b/zetasql/analyzer/testdata/array_functions.test
index 01af851c3..94d3f8a9b 100644
--- a/zetasql/analyzer/testdata/array_functions.test
+++ b/zetasql/analyzer/testdata/array_functions.test
@@ -350,7 +350,6 @@ QueryStmt
     |         |       |     |   |       +-Literal(type=INT64, value=4)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=289-295
     |         |       |     |       +-column_ref=
     |         |       |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |       |     +-limit=
@@ -385,7 +384,6 @@ QueryStmt
     |         |       |     |   |       +-Literal(type=INT64, value=4)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=499-510
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#8)
     |         |       |     |       +-is_descending=TRUE
@@ -504,7 +502,6 @@ QueryStmt
     |         |       |     |   |         +-Literal(type=INT64, value=5)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=289-295
     |         |       |     |       +-column_ref=
     |         |       |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |       |     +-limit=
@@ -543,7 +540,6 @@ QueryStmt
     |         |       |     |   |         +-Literal(type=INT64, value=5)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=499-510
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#8)
     |         |       |     |       +-is_descending=TRUE
@@ -666,7 +662,6 @@ QueryStmt
     |         |       |     |   |       +-Literal(type=INT64, value=7)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=284-290
     |         |       |     |       +-column_ref=
     |         |       |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |       |     +-limit=
@@ -701,7 +696,6 @@ QueryStmt
     |         |       |     |   |       +-Literal(type=INT64, value=7)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=489-500
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#8)
     |         |       |     |       +-is_descending=TRUE
@@ -808,7 +802,6 @@ QueryStmt
     |         |           |       +-Literal(type=INT64, value=2)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=188-194
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.offset#5)
     |         +-input_scan=
@@ -922,7 +915,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=INT64, column=$subquery1.n#3, is_correlated=TRUE)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=351-357
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.offset#5)
     |         +-input_scan=
@@ -1032,7 +1024,6 @@ QueryStmt
     |         |           |         |       +-ColumnRef(type=INT64, column=$with_expr.start_offset#4, is_correlated=TRUE)
     |         |           |         +-order_by_item_list=
     |         |           |           +-OrderByItem
-    |         |           |             +-parse_location=436-442
     |         |           |             +-column_ref=
     |         |           |               +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |           +-input_scan=
@@ -1142,7 +1133,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=INT64, column=$subquery1.n#3, is_correlated=TRUE)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=360-366
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.offset#5)
     |         +-input_scan=
@@ -1252,7 +1242,6 @@ QueryStmt
     |         |           |         |       +-ColumnRef(type=INT64, column=$with_expr.end_offset#4, is_correlated=TRUE)
     |         |           |         +-order_by_item_list=
     |         |           |           +-OrderByItem
-    |         |           |             +-parse_location=451-457
     |         |           |             +-column_ref=
     |         |           |               +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |           +-input_scan=
@@ -1385,7 +1374,6 @@ QueryStmt
     |         |       |     |   |             +-SingleRowScan
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=284-290
     |         |       |     |       +-column_ref=
     |         |       |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#7)
     |         |       |     +-limit=
@@ -1427,7 +1415,6 @@ QueryStmt
     |         |       |     |   |             +-SingleRowScan
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=489-500
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#9)
     |         |       |     |       +-is_descending=TRUE
@@ -1562,7 +1549,6 @@ QueryStmt
     |         |       |     |   |             +-SingleRowScan
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=284-290
     |         |       |     |       +-column_ref=
     |         |       |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#9)
     |         |       |     +-limit=
@@ -1607,7 +1593,6 @@ QueryStmt
     |         |       |     |   |             +-SingleRowScan
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=489-500
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#11)
     |         |       |     |       +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/array_path.test b/zetasql/analyzer/testdata/array_path.test
index 3559ab472..57620c9f0 100644
--- a/zetasql/analyzer/testdata/array_path.test
+++ b/zetasql/analyzer/testdata/array_path.test
@@ -2333,7 +2333,6 @@ QueryStmt
     |     +-ColumnHolder(column=$array_offset.o#22)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=93-94
         +-column_ref=
           +-ColumnRef(type=INT64, column=$array_offset.o#22)
 
@@ -2390,7 +2389,6 @@ QueryStmt
     |     +-ColumnHolder(column=$array_offset.o#22)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=93-94
         +-column_ref=
           +-ColumnRef(type=INT64, column=$array_offset.o#22)
 ==
@@ -2630,7 +2628,6 @@ QueryStmt
     |     +-ColumnHolder(column=$array_offset.o#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=203-204
         +-column_ref=
           +-ColumnRef(type=INT64, column=$array_offset.o#3)
 
@@ -2746,7 +2743,6 @@ QueryStmt
     |     +-ColumnHolder(column=$array_offset.o#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=203-204
         +-column_ref=
           +-ColumnRef(type=INT64, column=$array_offset.o#3)
 ==
@@ -4379,7 +4375,6 @@ QueryStmt
     |             |     |     |     |     |           |       +-ColumnRef(type=INT64, column=KeyValue.Key#1, is_correlated=TRUE)
     |             |     |     |     |     |           +-order_by_item_list=
     |             |     |     |     |     |             +-OrderByItem
-    |             |     |     |     |     |               +-parse_location=231-234
     |             |     |     |     |     |               +-column_ref=
     |             |     |     |     |     |                 +-ColumnRef(type=INT64, column=$array_offset.off#13)
     |             |     |     |     |     +-input_scan=
diff --git a/zetasql/analyzer/testdata/array_transform.test b/zetasql/analyzer/testdata/array_transform.test
index 5aefbdd6a..11a1bcfc7 100644
--- a/zetasql/analyzer/testdata/array_transform.test
+++ b/zetasql/analyzer/testdata/array_transform.test
@@ -74,7 +74,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#5)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=216-219
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#5)
     |         +-input_scan=
@@ -162,7 +161,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#6)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=221-224
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |         +-input_scan=
@@ -255,7 +253,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#7)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=216-219
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#7)
     |         +-input_scan=
@@ -352,7 +349,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#8)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=221-224
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |         +-input_scan=
@@ -445,7 +441,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#7)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=216-219
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#7)
     |         +-input_scan=
@@ -544,7 +539,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#7)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=216-219
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#7)
     |         +-input_scan=
@@ -653,7 +647,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#13)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=216-219
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#13)
     |         +-input_scan=
@@ -702,7 +695,6 @@ QueryStmt
     |             |         |           |         +-ColumnHolder(column=$array_offset.off#8)
     |             |         |           +-order_by_item_list=
     |             |         |             +-OrderByItem
-    |             |         |               +-parse_location=216-219
     |             |         |               +-column_ref=
     |             |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |             |         +-input_scan=
@@ -835,7 +827,6 @@ QueryStmt
     |         |         |           |         +-ColumnHolder(column=$array_offset.off#14)
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=216-219
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#14)
     |         |         +-input_scan=
@@ -884,7 +875,6 @@ QueryStmt
     |         |             |         |           |         +-ColumnHolder(column=$array_offset.off#9)
     |         |             |         |           +-order_by_item_list=
     |         |             |         |             +-OrderByItem
-    |         |             |         |               +-parse_location=216-219
     |         |             |         |               +-column_ref=
     |         |             |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#9)
     |         |             |         +-input_scan=
@@ -1032,7 +1022,6 @@ QueryStmt
     |         |           |   |             |           |         +-ColumnHolder(column=$array_offset.off#20)
     |         |           |   |             |           +-order_by_item_list=
     |         |           |   |             |             +-OrderByItem
-    |         |           |   |             |               +-parse_location=216-219
     |         |           |   |             |               +-column_ref=
     |         |           |   |             |                 +-ColumnRef(type=INT64, column=$array_offset.off#20)
     |         |           |   |             +-input_scan=
@@ -1056,7 +1045,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#13)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=216-219
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#13)
     |         +-input_scan=
@@ -1217,7 +1205,6 @@ QueryStmt
     |         |         |           |   |             |           |         +-ColumnHolder(column=$array_offset.off#21)
     |         |         |           |   |             |           +-order_by_item_list=
     |         |         |           |   |             |             +-OrderByItem
-    |         |         |           |   |             |               +-parse_location=216-219
     |         |         |           |   |             |               +-column_ref=
     |         |         |           |   |             |                 +-ColumnRef(type=INT64, column=$array_offset.off#21)
     |         |         |           |   |             +-input_scan=
@@ -1241,7 +1228,6 @@ QueryStmt
     |         |         |           |         +-ColumnHolder(column=$array_offset.off#14)
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=216-219
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#14)
     |         |         +-input_scan=
@@ -1359,7 +1345,6 @@ QueryStmt
     |         |         |           |         +-ColumnHolder(column=$array_offset.off#8)
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=216-219
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |         |         +-input_scan=
@@ -1484,7 +1469,6 @@ QueryStmt
     |         |         |           |         +-ColumnHolder(column=$array_offset.off#8)
     |         |         |           +-order_by_item_list=
     |         |         |             +-OrderByItem
-    |         |         |               +-parse_location=216-219
     |         |         |               +-column_ref=
     |         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#8)
     |         |         +-input_scan=
diff --git a/zetasql/analyzer/testdata/binary_rewriter_functions.test b/zetasql/analyzer/testdata/binary_rewriter_functions.test
index 86b8118fc..58153b884 100644
--- a/zetasql/analyzer/testdata/binary_rewriter_functions.test
+++ b/zetasql/analyzer/testdata/binary_rewriter_functions.test
@@ -69,7 +69,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=STRING, column=$subquery1.target_element#3, is_correlated=TRUE)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=225-231
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.offset#5)
     |         +-input_scan=
@@ -156,7 +155,6 @@ QueryStmt
     |         |     |       |       +-ColumnRef(type=STRING, column=$subquery1.target_element#3, is_correlated=TRUE)
     |         |     |       +-order_by_item_list=
     |         |     |         +-OrderByItem
-    |         |     |           +-parse_location=237-243
     |         |     |           +-column_ref=
     |         |     |             +-ColumnRef(type=INT64, column=$array_offset.offset#5)
     |         |     +-Literal(type=ARRAY, value=NULL, has_explicit_type=TRUE)
@@ -263,7 +261,6 @@ QueryStmt
     |         |           |       +-ColumnRef(type=STRING, column=$subquery1.target_element#3, is_correlated=TRUE)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=220-226
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.offset#5)
     |         +-input_scan=
@@ -345,7 +342,6 @@ QueryStmt
     |         |     |       |       +-ColumnRef(type=STRING, column=$subquery1.target_element#3, is_correlated=TRUE)
     |         |     |       +-order_by_item_list=
     |         |     |         +-OrderByItem
-    |         |     |           +-parse_location=232-238
     |         |     |           +-column_ref=
     |         |     |             +-ColumnRef(type=INT64, column=$array_offset.offset#5)
     |         |     +-Literal(type=ARRAY, value=NULL, has_explicit_type=TRUE)
diff --git a/zetasql/analyzer/testdata/collation.test b/zetasql/analyzer/testdata/collation.test
index 29df5bca7..320ac5e56 100644
--- a/zetasql/analyzer/testdata/collation.test
+++ b/zetasql/analyzer/testdata/collation.test
@@ -1147,7 +1147,6 @@ QueryStmt
     | |       | +-TableScan(column_list=[CollatedTable.string_ci#5{Collation:"und:ci"}], table=CollatedTable, column_index_list=[0])
     | |       +-order_by_item_list=
     | |         +-OrderByItem
-    | |           +-parse_location=150-159
     | |           +-column_ref=
     | |           | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#5{Collation:"und:ci"})
     | |           +-collation=und:ci
@@ -3372,17 +3371,14 @@ QueryStmt
     | +-TableScan(column_list=CollatedTable.[string_ci#1, string_binary#2, array_with_string_ci#4], table=CollatedTable, column_index_list=[0, 1, 3])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=60-69
       | +-column_ref=
       | | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
       | +-collation=und:ci
       +-OrderByItem
-      | +-parse_location=71-84
       | +-column_ref=
       | | +-ColumnRef(type=STRING, type_annotation_map={Collation:"binary"}, column=CollatedTable.string_binary#2{Collation:"binary"})
       | +-collation=binary
       +-OrderByItem
-        +-parse_location=86-106
         +-column_ref=
         | +-ColumnRef(type=ARRAY, type_annotation_map=[{Collation:"und:ci"}], column=CollatedTable.array_with_string_ci#4[{Collation:"und:ci"}])
         +-collation=[und:ci]
@@ -3420,12 +3416,10 @@ QueryStmt
     |     +-TableScan(column_list=CollatedTable.[string_ci#1, struct_with_string_ci#3], table=CollatedTable, column_index_list=[0, 2])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=57-81
       | +-column_ref=
       | | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=$orderby.$orderbycol1#5{Collation:"und:ci"})
       | +-collation=und:ci
       +-OrderByItem
-        +-parse_location=83-106
         +-column_ref=
         | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=$orderby.$orderbycol2#6{Collation:"und:ci"})
         +-collation=und:ci
@@ -3449,14 +3443,12 @@ QueryStmt
     | +-TableScan(column_list=CollatedTable.[string_ci#1, string_binary#2], table=CollatedTable, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=60-86
       | +-column_ref=
       | | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
       | +-collation_name=
       | | +-Literal(type=STRING, value="binary")
       | +-collation=binary
       +-OrderByItem
-        +-parse_location=97-137
         +-column_ref=
         | +-ColumnRef(type=STRING, type_annotation_map={Collation:"binary"}, column=CollatedTable.string_binary#2{Collation:"binary"})
         +-collation_name=
@@ -3490,7 +3482,6 @@ QueryStmt
           |   +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=37-46
           |       +-column_ref=
           |       | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
           |       +-collation=und:ci
@@ -3500,7 +3491,6 @@ QueryStmt
           |   +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=148-173
           |       +-column_ref=
           |       | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
           |       +-collation_name=
@@ -3512,7 +3502,6 @@ QueryStmt
               +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=213-249
                   +-column_ref=
                   | +-ColumnRef(type=STRING, type_annotation_map={Collation:"und:ci"}, column=CollatedTable.string_ci#1{Collation:"und:ci"})
                   +-collation_name=
@@ -7830,7 +7819,6 @@ QueryStmt
     |           |           |         |       +-ColumnRef(type=INT64, column=$with_expr.end_offset#6, is_correlated=TRUE)
     |           |           |         +-order_by_item_list=
     |           |           |           +-OrderByItem
-    |           |           |             +-parse_location=770-773
     |           |           |             +-column_ref=
     |           |           |               +-ColumnRef(type=INT64, column=$array_offset.idx#8)
     |           |           +-input_scan=
@@ -8031,7 +8019,6 @@ QueryStmt
     |     |       |           |         |       +-ColumnRef(type=INT64, column=$with_expr.end_offset#10, is_correlated=TRUE)
     |     |       |           |         +-order_by_item_list=
     |     |       |           |           +-OrderByItem
-    |     |       |           |             +-parse_location=770-773
     |     |       |           |             +-column_ref=
     |     |       |           |               +-ColumnRef(type=INT64, column=$array_offset.idx#12)
     |     |       |           +-input_scan=
diff --git a/zetasql/analyzer/testdata/correlated_aggr_subquery.test b/zetasql/analyzer/testdata/correlated_aggr_subquery.test
index cb87860a4..c3be696eb 100644
--- a/zetasql/analyzer/testdata/correlated_aggr_subquery.test
+++ b/zetasql/analyzer/testdata/correlated_aggr_subquery.test
@@ -3600,7 +3600,6 @@ QueryStmt
     |         +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=54-149
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#6)
 
@@ -3764,6 +3763,5 @@ QueryStmt
     |         +-TableScan(column_list=SimpleTypesWithStruct.[key#1, TestEnum#2, TestStruct#3], table=SimpleTypesWithStruct, column_index_list=[0, 1, 2], alias="st")
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=52-99
         +-column_ref=
           +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#9)
diff --git a/zetasql/analyzer/testdata/correlated_expr_subquery.test b/zetasql/analyzer/testdata/correlated_expr_subquery.test
index 283235dd6..6c21fabc4 100644
--- a/zetasql/analyzer/testdata/correlated_expr_subquery.test
+++ b/zetasql/analyzer/testdata/correlated_expr_subquery.test
@@ -512,7 +512,6 @@ QueryStmt
     |     +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=36-52
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -1461,7 +1460,6 @@ QueryStmt
     |         |     +-v#5 := ColumnRef(type=INT32, column=$array.v#4)
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=110-111
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$distinct.v#5)
     +-input_scan=
@@ -1517,11 +1515,9 @@ QueryStmt
     |         |         +-v#5 := ColumnRef(type=INT32, column=$array.v#4)
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |           | +-parse_location=110-111
     |           | +-column_ref=
     |           |   +-ColumnRef(type=INT32, column=$distinct.v#5)
     |           +-OrderByItem
-    |             +-parse_location=113-119
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol2#6)
     +-input_scan=
@@ -1578,7 +1574,6 @@ QueryStmt
     |         |         +-key#6 := ColumnRef(type=INT32, column=$expr_subquery.key#5)
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=115-121
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#7)
     +-input_scan=
@@ -1638,11 +1633,9 @@ QueryStmt
     |         |         +-key#7 := ColumnRef(type=INT32, column=$expr_subquery.key#5)
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |           | +-parse_location=118-119
     |           | +-column_ref=
     |           |   +-ColumnRef(type=INT32, column=$distinct.v#6)
     |           +-OrderByItem
-    |             +-parse_location=121-127
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol2#8)
     +-input_scan=
@@ -1696,11 +1689,9 @@ QueryStmt
     |         |     +-key#7 := ColumnRef(type=INT32, column=$expr_subquery.key#5)
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |           | +-parse_location=118-119
     |           | +-column_ref=
     |           |   +-ColumnRef(type=INT32, column=$distinct.v#6)
     |           +-OrderByItem
-    |             +-parse_location=121-122
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$distinct.key#7)
     +-input_scan=
@@ -1768,7 +1759,6 @@ QueryStmt
         |       |     |     +-ex#4 := ColumnRef(type=STRING, column=$array.ex#1, is_correlated=TRUE)
         |       |     +-order_by_item_list=
         |       |       +-OrderByItem
-        |       |         +-parse_location=100-102
         |       |         +-column_ref=
         |       |           +-ColumnRef(type=STRING, column=$groupby.ex#4)
         |       +-Literal(type=STRING, value="a")
@@ -2095,7 +2085,6 @@ QueryStmt
     |             |             +-m#5 := ColumnRef(type=INT32, column=Int32ValueTable.value#1, is_correlated=TRUE)
     |             +-order_by_item_list=
     |               +-OrderByItem
-    |                 +-parse_location=121-126
     |                 +-column_ref=
     |                   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#7)
     +-input_scan=
@@ -2289,7 +2278,6 @@ QueryStmt
     |             |             +-ColumnRef(type=INT64, column=KeyValue.Key#1, is_correlated=TRUE)
     |             +-order_by_item_list=
     |               +-OrderByItem
-    |                 +-parse_location=136-139
     |                 +-column_ref=
     |                   +-ColumnRef(type=INT64, column=$expr_subquery.key#7)
     +-input_scan=
diff --git a/zetasql/analyzer/testdata/correlated_subquery_outer_aggr.test b/zetasql/analyzer/testdata/correlated_subquery_outer_aggr.test
index 0b995def1..4db3c6573 100644
--- a/zetasql/analyzer/testdata/correlated_subquery_outer_aggr.test
+++ b/zetasql/analyzer/testdata/correlated_subquery_outer_aggr.test
@@ -1114,7 +1114,6 @@ QueryStmt
     |         +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=50-62
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
 ==
@@ -1161,7 +1160,6 @@ QueryStmt
     |         +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=56-73
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
 ==
@@ -1929,7 +1927,6 @@ QueryStmt
     |         +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=50-71
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
 ==
@@ -1987,7 +1984,6 @@ QueryStmt
     |         +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=56-82
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
 ==
@@ -2354,7 +2350,6 @@ QueryStmt
     |   |         |         +-TestEnum#7 := ColumnRef(type=ENUM, column=TestTable.TestEnum#5)
     |   |         +-order_by_item_list=
     |   |           +-OrderByItem
-    |   |             +-parse_location=145-212
     |   |             +-column_ref=
     |   |               +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#9)
     |   +-input_scan=
@@ -2366,7 +2361,6 @@ QueryStmt
     |         +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=47-213
         +-column_ref=
           +-ColumnRef(type=ENUM, column=$orderby.$orderbycol1#10)
 ==
@@ -2619,11 +2613,9 @@ QueryStmt
     |   |       +-Literal(type=DOUBLE, value=14)
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |     | +-parse_location=608-617
     |     | +-column_ref=
     |     |   +-ColumnRef(type=INT64, column=$groupby.field#10)
     |     +-OrderByItem
-    |       +-parse_location=619-628
     |       +-column_ref=
     |         +-ColumnRef(type=DOUBLE, column=$query.value#11)
     +-limit=
@@ -2944,7 +2936,6 @@ QueryStmt
     |         +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=50-69
         +-column_ref=
           +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#5)
 ==
@@ -3241,7 +3232,6 @@ QueryStmt
     |         |             +-ColumnRef(type=INT64, column=KeyValue.Key#9)
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=116-122
     |             +-column_ref=
     |               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#13)
     +-input_scan=
@@ -3560,7 +3550,6 @@ QueryStmt
         |           |             +-ColumnRef(type=INT64, column=KeyValue.Key#3)
         |           +-order_by_item_list=
         |             +-OrderByItem
-        |               +-parse_location=140-146
         |               +-column_ref=
         |                 +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#7)
         +-group_by_list=
@@ -3931,7 +3920,6 @@ QueryStmt
         |         |             +-ColumnRef(type=INT64, column=KeyValue.Key#4)
         |         +-order_by_item_list=
         |           +-OrderByItem
-        |             +-parse_location=157-163
         |             +-column_ref=
         |               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
         +-aggregate_list=
@@ -4272,7 +4260,6 @@ QueryStmt
             |     |             +-ColumnRef(type=INT64, column=KeyValue.Key#5)
             |     +-order_by_item_list=
             |       +-OrderByItem
-            |         +-parse_location=132-138
             |         +-column_ref=
             |           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#9)
             +-Literal(type=INT64, value=0)
@@ -4332,7 +4319,6 @@ QueryStmt
     |             +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-124
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#9)
 ==
@@ -4385,7 +4371,6 @@ QueryStmt
     |             +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-119
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
 ==
@@ -4450,7 +4435,6 @@ QueryStmt
     |             +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-150
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#9)
 ==
@@ -4508,7 +4492,6 @@ QueryStmt
     |             +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-129
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#10)
 ==
@@ -4573,7 +4556,6 @@ QueryStmt
     |             +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-149
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#9)
 ==
@@ -4625,7 +4607,6 @@ QueryStmt
     |   |         |             +-ColumnRef(type=INT64, column=KeyValue.Key#5)
     |   |         +-order_by_item_list=
     |   |           +-OrderByItem
-    |   |             +-parse_location=140-146
     |   |             +-column_ref=
     |   |               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#9)
     |   +-input_scan=
@@ -4641,7 +4622,6 @@ QueryStmt
     |             +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-147
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#10)
 ==
@@ -4854,58 +4834,6 @@ from KitchenSinkValueTable t0
 group by 1
 order by (select {{t0.|}}has_int32_val)
 --
-ALTERNATION GROUP: t0.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.has_int32_val#3 AS has_int32_val [BOOL]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.has_int32_val#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.has_int32_val#3, $orderby.$orderbycol1#5]
-    |   +-expr_list=
-    |   | +-$orderbycol1#5 :=
-    |   |   +-SubqueryExpr
-    |   |     +-type=BOOL
-    |   |     +-subquery_type=SCALAR
-    |   |     +-parameter_list=
-    |   |     | +-ColumnRef(type=BOOL, column=$groupby.has_int32_val#3)
-    |   |     +-subquery=
-    |   |       +-ProjectScan
-    |   |         +-column_list=[$expr_subquery.has_int32_val#4]
-    |   |         +-expr_list=
-    |   |         | +-has_int32_val#4 := ColumnRef(type=BOOL, column=$groupby.has_int32_val#3, is_correlated=TRUE)
-    |   |         +-input_scan=
-    |   |           +-SingleRowScan
-    |   +-input_scan=
-    |     +-AggregateScan
-    |       +-column_list=[$groupby.has_int32_val#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[KitchenSinkValueTable.value#1, $pre_groupby.has_int32_val#2]
-    |       |   +-expr_list=
-    |       |   | +-has_int32_val#2 :=
-    |       |   |   +-GetProtoField
-    |       |   |     +-type=BOOL
-    |       |   |     +-expr=
-    |       |   |     | +-ColumnRef(type=PROTO, column=KitchenSinkValueTable.value#1)
-    |       |   |     +-field_descriptor=int32_val
-    |       |   |     +-get_has_bit=TRUE
-    |       |   +-input_scan=
-    |       |     +-TableScan(column_list=[KitchenSinkValueTable.value#1], table=KitchenSinkValueTable, column_index_list=[0], alias="t0")
-    |       +-group_by_list=
-    |         +-has_int32_val#3 := ColumnRef(type=BOOL, column=$pre_groupby.has_int32_val#2)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=74-99
-        +-column_ref=
-          +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#5)
---
-ALTERNATION GROUP: 
---
 QueryStmt
 +-output_column_list=
 | +-$groupby.has_int32_val#3 AS has_int32_val [BOOL]
@@ -4950,7 +4878,6 @@ QueryStmt
     |         +-has_int32_val#3 := ColumnRef(type=BOOL, column=$pre_groupby.has_int32_val#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=74-96
         +-column_ref=
           +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#5)
 ==
@@ -4961,58 +4888,6 @@ select distinct t0.has_int32_val
 from KitchenSinkValueTable t0
 order by (select {{t0.|}}has_int32_val)
 --
-ALTERNATION GROUP: t0.
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.has_int32_val#3 AS has_int32_val [BOOL]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.has_int32_val#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$distinct.has_int32_val#3, $orderby.$orderbycol1#5]
-    |   +-expr_list=
-    |   | +-$orderbycol1#5 :=
-    |   |   +-SubqueryExpr
-    |   |     +-type=BOOL
-    |   |     +-subquery_type=SCALAR
-    |   |     +-parameter_list=
-    |   |     | +-ColumnRef(type=BOOL, column=$distinct.has_int32_val#3)
-    |   |     +-subquery=
-    |   |       +-ProjectScan
-    |   |         +-column_list=[$expr_subquery.has_int32_val#4]
-    |   |         +-expr_list=
-    |   |         | +-has_int32_val#4 := ColumnRef(type=BOOL, column=$distinct.has_int32_val#3, is_correlated=TRUE)
-    |   |         +-input_scan=
-    |   |           +-SingleRowScan
-    |   +-input_scan=
-    |     +-AggregateScan
-    |       +-column_list=[$distinct.has_int32_val#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[KitchenSinkValueTable.value#1, $query.has_int32_val#2]
-    |       |   +-expr_list=
-    |       |   | +-has_int32_val#2 :=
-    |       |   |   +-GetProtoField
-    |       |   |     +-type=BOOL
-    |       |   |     +-expr=
-    |       |   |     | +-ColumnRef(type=PROTO, column=KitchenSinkValueTable.value#1)
-    |       |   |     +-field_descriptor=int32_val
-    |       |   |     +-get_has_bit=TRUE
-    |       |   +-input_scan=
-    |       |     +-TableScan(column_list=[KitchenSinkValueTable.value#1], table=KitchenSinkValueTable, column_index_list=[0], alias="t0")
-    |       +-group_by_list=
-    |         +-has_int32_val#3 := ColumnRef(type=BOOL, column=$query.has_int32_val#2)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=72-97
-        +-column_ref=
-          +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#5)
---
-ALTERNATION GROUP: 
---
 QueryStmt
 +-output_column_list=
 | +-$distinct.has_int32_val#3 AS has_int32_val [BOOL]
@@ -5057,7 +4932,6 @@ QueryStmt
     |         +-has_int32_val#3 := ColumnRef(type=BOOL, column=$query.has_int32_val#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=72-94
         +-column_ref=
           +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#5)
 ==
diff --git a/zetasql/analyzer/testdata/correlated_subquery_outer_aggr_struct.test b/zetasql/analyzer/testdata/correlated_subquery_outer_aggr_struct.test
index 67ca269c8..ed30ed6db 100644
--- a/zetasql/analyzer/testdata/correlated_subquery_outer_aggr_struct.test
+++ b/zetasql/analyzer/testdata/correlated_subquery_outer_aggr_struct.test
@@ -945,8 +945,6 @@ FROM ComplexTypes ct
 GROUP BY TestStruct.d
 ORDER BY (SELECT {{ct.|}}TestStruct.d.a);
 --
-ALTERNATION GROUP: ct.
---
 QueryStmt
 +-output_column_list=
 | +-$groupby.d#8 AS d [STRUCT]
@@ -995,61 +993,6 @@ QueryStmt
     |         +-d#8 := ColumnRef(type=STRUCT, column=$pre_groupby.d#7)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=72-98
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#10)
---
-ALTERNATION GROUP: 
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.d#8 AS d [STRUCT]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.d#8]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.d#8, $orderby.$orderbycol1#10]
-    |   +-expr_list=
-    |   | +-$orderbycol1#10 :=
-    |   |   +-SubqueryExpr
-    |   |     +-type=INT32
-    |   |     +-subquery_type=SCALAR
-    |   |     +-parameter_list=
-    |   |     | +-ColumnRef(type=STRUCT, column=$groupby.d#8)
-    |   |     +-subquery=
-    |   |       +-ProjectScan
-    |   |         +-column_list=[$expr_subquery.a#9]
-    |   |         +-expr_list=
-    |   |         | +-a#9 :=
-    |   |         |   +-GetStructField
-    |   |         |     +-type=INT32
-    |   |         |     +-expr=
-    |   |         |     | +-ColumnRef(type=STRUCT, column=$groupby.d#8, is_correlated=TRUE)
-    |   |         |     +-field_idx=0
-    |   |         +-input_scan=
-    |   |           +-SingleRowScan
-    |   +-input_scan=
-    |     +-AggregateScan
-    |       +-column_list=[$groupby.d#8]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[ComplexTypes.TestStruct#5, $pre_groupby.d#7]
-    |       |   +-expr_list=
-    |       |   | +-d#7 :=
-    |       |   |   +-GetStructField
-    |       |   |     +-type=STRUCT
-    |       |   |     +-expr=
-    |       |   |     | +-ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
-    |       |   |     +-field_idx=1
-    |       |   +-input_scan=
-    |       |     +-TableScan(column_list=[ComplexTypes.TestStruct#5], table=ComplexTypes, column_index_list=[4], alias="ct")
-    |       +-group_by_list=
-    |         +-d#8 := ColumnRef(type=STRUCT, column=$pre_groupby.d#7)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=72-95
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#10)
 ==
@@ -1101,7 +1044,6 @@ QueryStmt
     |         +-TestStruct#7 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=74-97
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#9)
 ==
@@ -1818,7 +1760,6 @@ QueryStmt
     |         +-TestStruct#7 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=65-112
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#10)
 ==
@@ -2325,7 +2266,6 @@ QueryStmt
     |   |         |                 +-field_idx=1
     |   |         +-order_by_item_list=
     |   |           +-OrderByItem
-    |   |             +-parse_location=164-205
     |   |             +-column_ref=
     |   |               +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#18)
     |   +-input_scan=
@@ -2337,7 +2277,6 @@ QueryStmt
     |         +-TestStruct#7 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=68-206
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#19)
 ==
@@ -2431,7 +2370,6 @@ QueryStmt
     |   |         |                 +-field_idx=1
     |   |         +-order_by_item_list=
     |   |           +-OrderByItem
-    |   |             +-parse_location=155-181
     |   |             +-column_ref=
     |   |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#20)
     |   +-input_scan=
@@ -2463,7 +2401,6 @@ QueryStmt
     |             +-TestStruct#8 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=70-182
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#21)
 ==
@@ -2877,7 +2814,6 @@ QueryStmt
     |             +-TestStruct#8 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=69-108
         +-column_ref=
           +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#11)
 ==
@@ -3213,7 +3149,6 @@ QueryStmt
     |             +-TestStruct#8 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=72-119
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#17)
 ==
@@ -3483,8 +3418,6 @@ FROM TestStructValueTable
 GROUP BY TestStructValueTable
 ORDER BY (SELECT TestStructValueTable.a)
 --
-ALTERNATION GROUP: TestStructValueTable.
---
 QueryStmt
 +-output_column_list=
 | +-$groupby.TestStructValueTable#3 AS TestStructValueTable [STRUCT]
@@ -3544,72 +3477,6 @@ QueryStmt
     |             +-TestStructValueTable#3 := ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=117-148
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#6)
---
-ALTERNATION GROUP: 
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.TestStructValueTable#3 AS TestStructValueTable [STRUCT]
-| +-$query.a#4 AS a [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.TestStructValueTable#3, $query.a#4]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.TestStructValueTable#3, $query.a#4, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-SubqueryExpr
-    |   |     +-type=INT32
-    |   |     +-subquery_type=SCALAR
-    |   |     +-parameter_list=
-    |   |     | +-ColumnRef(type=STRUCT, column=$groupby.TestStructValueTable#3)
-    |   |     +-subquery=
-    |   |       +-ProjectScan
-    |   |         +-column_list=[$expr_subquery.a#5]
-    |   |         +-expr_list=
-    |   |         | +-a#5 :=
-    |   |         |   +-GetStructField
-    |   |         |     +-type=INT32
-    |   |         |     +-expr=
-    |   |         |     | +-ColumnRef(type=STRUCT, column=$groupby.TestStructValueTable#3, is_correlated=TRUE)
-    |   |         |     +-field_idx=0
-    |   |         +-input_scan=
-    |   |           +-SingleRowScan
-    |   +-input_scan=
-    |     +-ProjectScan
-    |       +-column_list=[$groupby.TestStructValueTable#3, $query.a#4]
-    |       +-expr_list=
-    |       | +-a#4 :=
-    |       |   +-GetStructField
-    |       |     +-type=INT32
-    |       |     +-expr=
-    |       |     | +-ColumnRef(type=STRUCT, column=$groupby.TestStructValueTable#3)
-    |       |     +-field_idx=0
-    |       +-input_scan=
-    |         +-AggregateScan
-    |           +-column_list=[$groupby.TestStructValueTable#3]
-    |           +-input_scan=
-    |           | +-ProjectScan
-    |           |   +-column_list=[TestStructValueTable.value#1, $pre_groupby.a#2]
-    |           |   +-expr_list=
-    |           |   | +-a#2 :=
-    |           |   |   +-GetStructField
-    |           |   |     +-type=INT32
-    |           |   |     +-expr=
-    |           |   |     | +-ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
-    |           |   |     +-field_idx=0
-    |           |   +-input_scan=
-    |           |     +-TableScan(column_list=[TestStructValueTable.value#1], table=TestStructValueTable, column_index_list=[0])
-    |           +-group_by_list=
-    |             +-TestStructValueTable#3 := ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=96-127
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#6)
 ==
@@ -3620,73 +3487,6 @@ FROM TestStructValueTable vt
 GROUP BY vt
 ORDER BY (SELECT vt.a)
 --
-ALTERNATION GROUP: vt.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.vt#3 AS vt [STRUCT]
-| +-$query.a#4 AS a [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.vt#3, $query.a#4]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.vt#3, $query.a#4, $orderby.$orderbycol1#6]
-    |   +-expr_list=
-    |   | +-$orderbycol1#6 :=
-    |   |   +-SubqueryExpr
-    |   |     +-type=INT32
-    |   |     +-subquery_type=SCALAR
-    |   |     +-parameter_list=
-    |   |     | +-ColumnRef(type=STRUCT, column=$groupby.vt#3)
-    |   |     +-subquery=
-    |   |       +-ProjectScan
-    |   |         +-column_list=[$expr_subquery.a#5]
-    |   |         +-expr_list=
-    |   |         | +-a#5 :=
-    |   |         |   +-GetStructField
-    |   |         |     +-type=INT32
-    |   |         |     +-expr=
-    |   |         |     | +-ColumnRef(type=STRUCT, column=$groupby.vt#3, is_correlated=TRUE)
-    |   |         |     +-field_idx=0
-    |   |         +-input_scan=
-    |   |           +-SingleRowScan
-    |   +-input_scan=
-    |     +-ProjectScan
-    |       +-column_list=[$groupby.vt#3, $query.a#4]
-    |       +-expr_list=
-    |       | +-a#4 :=
-    |       |   +-GetStructField
-    |       |     +-type=INT32
-    |       |     +-expr=
-    |       |     | +-ColumnRef(type=STRUCT, column=$groupby.vt#3)
-    |       |     +-field_idx=0
-    |       +-input_scan=
-    |         +-AggregateScan
-    |           +-column_list=[$groupby.vt#3]
-    |           +-input_scan=
-    |           | +-ProjectScan
-    |           |   +-column_list=[TestStructValueTable.value#1, $pre_groupby.a#2]
-    |           |   +-expr_list=
-    |           |   | +-a#2 :=
-    |           |   |   +-GetStructField
-    |           |   |     +-type=INT32
-    |           |   |     +-expr=
-    |           |   |     | +-ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
-    |           |   |     +-field_idx=0
-    |           |   +-input_scan=
-    |           |     +-TableScan(column_list=[TestStructValueTable.value#1], table=TestStructValueTable, column_index_list=[0], alias="vt")
-    |           +-group_by_list=
-    |             +-vt#3 := ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=66-79
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#6)
---
-ALTERNATION GROUP: 
---
 QueryStmt
 +-output_column_list=
 | +-$groupby.vt#3 AS vt [STRUCT]
@@ -3746,7 +3546,6 @@ QueryStmt
     |             +-vt#3 := ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=63-76
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#6)
 ==
diff --git a/zetasql/analyzer/testdata/differential_privacy.test b/zetasql/analyzer/testdata/differential_privacy.test
index e37f2c9a1..f6627b706 100644
--- a/zetasql/analyzer/testdata/differential_privacy.test
+++ b/zetasql/analyzer/testdata/differential_privacy.test
@@ -1962,7 +1962,6 @@ QueryStmt
         |   |   |           +-Literal(type=INT64, value=0)
         |   |   +-order_by_item_list=
         |   |     +-OrderByItem
-        |   |       +-parse_location=283-290
         |   |       +-column_ref=
         |   |         +-ColumnRef(type=INT64, column=$aggregate.int64#13)
         |   +-limit=
@@ -2033,7 +2032,6 @@ QueryStmt
         |   |   |   |           +-Literal(type=INT64, value=0)
         |   |   |   +-order_by_item_list=
         |   |   |     +-OrderByItem
-        |   |   |       +-parse_location=283-290
         |   |   |       +-column_ref=
         |   |   |         +-ColumnRef(type=INT64, column=$aggregate.int64#13)
         |   |   +-limit=
diff --git a/zetasql/analyzer/testdata/differential_privacy_subquery.test b/zetasql/analyzer/testdata/differential_privacy_subquery.test
index 6d07e96eb..13d697e52 100644
--- a/zetasql/analyzer/testdata/differential_privacy_subquery.test
+++ b/zetasql/analyzer/testdata/differential_privacy_subquery.test
@@ -514,7 +514,6 @@ QueryStmt
     |     +-max_groups_contributed := Literal(type=INT64, value=100)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=349-360
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$aggregate.weight#6)
         +-is_descending=TRUE
@@ -605,7 +604,6 @@ QueryStmt
     |     +-max_groups_contributed := Literal(type=INT64, value=100)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=349-360
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$aggregate.weight#6)
         +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/dml_insert.test b/zetasql/analyzer/testdata/dml_insert.test
index 90fbbe52b..784dbbf4c 100644
--- a/zetasql/analyzer/testdata/dml_insert.test
+++ b/zetasql/analyzer/testdata/dml_insert.test
@@ -683,7 +683,6 @@ InsertStmt
 |   |   |       +-output_column_list=KeyValue.[Key#5, Value#6]
 |   |   +-order_by_item_list=
 |   |     +-OrderByItem
-|   |       +-parse_location=98-99
 |   |       +-column_ref=
 |   |         +-ColumnRef(type=INT64, column=$union_all.Key#7)
 |   +-limit=
diff --git a/zetasql/analyzer/testdata/expr_subquery.test b/zetasql/analyzer/testdata/expr_subquery.test
index 4ed4cfc64..1ddf0541c 100644
--- a/zetasql/analyzer/testdata/expr_subquery.test
+++ b/zetasql/analyzer/testdata/expr_subquery.test
@@ -1143,11 +1143,9 @@ QueryStmt
     |         |     +-TableScan(column_list=TestTable.[key#1, KitchenSink#3], table=TestTable, column_index_list=[0, 2])
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |           | +-parse_location=51-58
     |           | +-column_ref=
     |           |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
     |           +-OrderByItem
-    |             +-parse_location=60-81
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol2#5)
     +-input_scan=
@@ -1215,7 +1213,6 @@ QueryStmt
     |         |           +-output_column_list=[$union_all2_cast.$col1#6]
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=104-125
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#7)
     +-input_scan=
@@ -1263,11 +1260,9 @@ QueryStmt
     |         |     +-TableScan(column_list=TestTable.[key#1, KitchenSink#3], table=TestTable, column_index_list=[0, 2])
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |           | +-parse_location=84-91
     |           | +-column_ref=
     |           |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
     |           +-OrderByItem
-    |             +-parse_location=93-114
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol2#5)
     +-input_scan=
@@ -1331,7 +1326,6 @@ QueryStmt
     |         |           +-output_column_list=[$union_all2.$col1#6]
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=80-103
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#8)
     +-input_scan=
@@ -1407,7 +1401,6 @@ QueryStmt
     |         |           +-output_column_list=[$union_all2.$col1#7, $union_all2_cast.$col2#11]
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=130-153
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#12)
     +-input_scan=
diff --git a/zetasql/analyzer/testdata/hints.test b/zetasql/analyzer/testdata/hints.test
index e17409bee..1ea6e0f6a 100644
--- a/zetasql/analyzer/testdata/hints.test
+++ b/zetasql/analyzer/testdata/hints.test
@@ -966,7 +966,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=43-44
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -987,7 +986,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=39-40
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -1009,7 +1007,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=46-47
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -1058,7 +1055,6 @@ QueryStmt
     |       +-output_column_list=[KeyValue.Key#3]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=78-79
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_all.key#5)
 ==
diff --git a/zetasql/analyzer/testdata/json.test b/zetasql/analyzer/testdata/json.test
index d377ecb08..cffb14368 100644
--- a/zetasql/analyzer/testdata/json.test
+++ b/zetasql/analyzer/testdata/json.test
@@ -328,20 +328,17 @@ QueryStmt
     +-column_list=$query.[$col1#4, $col2#5, $col3#6]
     +-expr_list=
     | +-$col1#4 :=
-    | | +-FunctionCall(ZetaSQL:to_json(INT32, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    | | +-FunctionCall(ZetaSQL:to_json(INT32, optional(1) BOOL stringify_wide_numbers) -> JSON)
     | |   +-ColumnRef(type=INT32, column=TestTable.key#1)
     | |   +-Literal(type=BOOL, value=false)
-    | |   +-Literal(type=ENUM, value=FAIL)
     | +-$col2#5 :=
-    | | +-FunctionCall(ZetaSQL:to_json(ENUM, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    | | +-FunctionCall(ZetaSQL:to_json(ENUM, optional(1) BOOL stringify_wide_numbers) -> JSON)
     | |   +-ColumnRef(type=ENUM, column=TestTable.TestEnum#2)
     | |   +-Literal(type=BOOL, value=true)
-    | |   +-Literal(type=ENUM, value=FAIL)
     | +-$col3#6 :=
-    |   +-FunctionCall(ZetaSQL:to_json(PROTO, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    |   +-FunctionCall(ZetaSQL:to_json(PROTO, optional(1) BOOL stringify_wide_numbers) -> JSON)
     |     +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
     |     +-Literal(type=BOOL, value=false)
-    |     +-Literal(type=ENUM, value=FAIL)
     +-input_scan=
       +-TableScan(column_list=TestTable.[key#1, TestEnum#2, KitchenSink#3], table=TestTable, column_index_list=[0, 1, 2])
 ==
@@ -349,9 +346,9 @@ QueryStmt
 [language_features=JSON_TYPE,NAMED_ARGUMENTS,TO_JSON_UNSUPPORTED_FIELDS]
 SELECT TO_JSON(1, false);
 --
-ERROR: Positional argument is invalid because this function restricts that this argument is referred to by name "stringify_wide_numbers" only [at 1:19]
+ERROR: No matching signature for function TO_JSON for argument types: INT64, BOOL. Supported signatures: TO_JSON(ANY, [stringify_wide_numbers => BOOL]); TO_JSON(ANY, [stringify_wide_numbers => BOOL], [unsupported_fields => UNSUPPORTED_FIELDS]) [at 1:8]
 SELECT TO_JSON(1, false);
-                  ^
+       ^
 ==
 
 [language_features=JSON_TYPE,NAMED_ARGUMENTS]
@@ -599,18 +596,17 @@ QueryStmt
     +-column_list=[$query.$col1#1]
     +-expr_list=
     | +-$col1#1 :=
-    |   +-FunctionCall(ZetaSQL:to_json(RANGE, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    |   +-FunctionCall(ZetaSQL:to_json(RANGE, optional(1) BOOL stringify_wide_numbers) -> JSON)
     |     +-FunctionCall(ZetaSQL:range(DATE, DATE) -> RANGE)
     |     | +-Literal(type=DATE, value=2022-10-01, has_explicit_type=TRUE)
     |     | +-Literal(type=DATE, value=NULL)
     |     +-Literal(type=BOOL, value=false)
-    |     +-Literal(type=ENUM, value=FAIL)
     +-input_scan=
       +-SingleRowScan
 
 [UNPARSED_SQL]
 SELECT
-  TO_JSON(`RANGE`(DATE "2022-10-01", CAST(NULL AS DATE)), stringify_wide_numbers  => false, unsupported_fields  => "FAIL") AS a_1;
+  TO_JSON(`RANGE`(DATE "2022-10-01", CAST(NULL AS DATE)), stringify_wide_numbers  => false) AS a_1;
 ==
 
 [language_features=JSON_TYPE,NAMED_ARGUMENTS,RANGE_TYPE,TO_JSON_UNSUPPORTED_FIELDS]
@@ -632,18 +628,18 @@ QueryStmt
     +-column_list=[$query.$col1#1]
     +-expr_list=
     | +-$col1#1 :=
-    |   +-FunctionCall(ZetaSQL:to_json(RANGE, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    |   +-FunctionCall(ZetaSQL:to_json(RANGE, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
     |     +-FunctionCall(ZetaSQL:range(DATE, DATE) -> RANGE)
     |     | +-Literal(type=DATE, value=2022-10-01, has_explicit_type=TRUE)
     |     | +-Literal(type=DATE, value=NULL)
     |     +-Literal(type=BOOL, value=false)
-    |     +-Literal(type=ENUM, value=IGNORE)
+    |     +-Literal(type=ENUM, value=IGNORE)
     +-input_scan=
       +-SingleRowScan
 
 [UNPARSED_SQL]
 SELECT
-  TO_JSON(`RANGE`(DATE "2022-10-01", CAST(NULL AS DATE)), stringify_wide_numbers  => false, unsupported_fields  => "IGNORE") AS a_1;
+  TO_JSON(`RANGE`(DATE "2022-10-01", CAST(NULL AS DATE)), stringify_wide_numbers  => false, unsupported_fields  => CAST("IGNORE" AS UNSUPPORTED_FIELDS)) AS a_1;
 --
 ALTERNATION GROUP: 'PLACEHOLDER'
 --
@@ -655,18 +651,18 @@ QueryStmt
     +-column_list=[$query.$col1#1]
     +-expr_list=
     | +-$col1#1 :=
-    |   +-FunctionCall(ZetaSQL:to_json(RANGE, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    |   +-FunctionCall(ZetaSQL:to_json(RANGE, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
     |     +-FunctionCall(ZetaSQL:range(DATE, DATE) -> RANGE)
     |     | +-Literal(type=DATE, value=2022-10-01, has_explicit_type=TRUE)
     |     | +-Literal(type=DATE, value=NULL)
     |     +-Literal(type=BOOL, value=false)
-    |     +-Literal(type=ENUM, value=PLACEHOLDER)
+    |     +-Literal(type=ENUM, value=PLACEHOLDER)
     +-input_scan=
       +-SingleRowScan
 
 [UNPARSED_SQL]
 SELECT
-  TO_JSON(`RANGE`(DATE "2022-10-01", CAST(NULL AS DATE)), stringify_wide_numbers  => false, unsupported_fields  => "PLACEHOLDER") AS a_1;
+  TO_JSON(`RANGE`(DATE "2022-10-01", CAST(NULL AS DATE)), stringify_wide_numbers  => false, unsupported_fields  => CAST("PLACEHOLDER" AS UNSUPPORTED_FIELDS)) AS a_1;
 ==
 
 [language_features=JSON_TYPE,NAMED_ARGUMENTS,RANGE_TYPE,TO_JSON_UNSUPPORTED_FIELDS]
@@ -677,7 +673,7 @@ SELECT
 SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'blah')
 --
 
-ERROR: Could not cast literal "blah" to type zetasql.functions.UnsupportedFields [at 2:48]
+ERROR: Could not cast literal "blah" to type UNSUPPORTED_FIELDS [at 2:48]
 SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'blah')
                                                ^
 ==
@@ -691,7 +687,7 @@ SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'blah')
 SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'iGnore')
 --
 
-ERROR: Could not cast literal "iGnore" to type zetasql.functions.UnsupportedFields; Did you mean 'IGNORE'? (Note: ENUM values are case sensitive) [at 1:48]
+ERROR: Could not cast literal "iGnore" to type UNSUPPORTED_FIELDS; Did you mean 'IGNORE'? (Note: ENUM values are case sensitive) [at 1:48]
 SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'iGnore')
                                                ^
 ==
@@ -705,7 +701,7 @@ SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'iGnore')
 SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'iGnore')
 --
 
-ERROR: Could not cast literal "iGnore" to type zetasql.functions.UnsupportedFields; Did you mean 'IGNORE'? (Note: ENUM values are case sensitive) [at 1:48]
+ERROR: Could not cast literal "iGnore" to type UNSUPPORTED_FIELDS; Did you mean 'IGNORE'? (Note: ENUM values are case sensitive) [at 1:48]
 SELECT TO_JSON(RANGE(DATE '2022-10-01', NULL), unsupported_fields=>'iGnore')
                                                ^
 ==
@@ -723,14 +719,14 @@ QueryStmt
     +-column_list=[$query.$col1#1]
     +-expr_list=
     | +-$col1#1 :=
-    |   +-FunctionCall(ZetaSQL:to_json(INT64, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    |   +-FunctionCall(ZetaSQL:to_json(INT64, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
     |     +-Literal(type=INT64, value=1)
     |     +-Literal(type=BOOL, value=false)
-    |     +-Parameter(parse_location=38-40, type=ENUM, name="p")
+    |     +-Parameter(parse_location=38-40, type=ENUM, name="p")
     +-input_scan=
       +-SingleRowScan
 [UNDECLARED_PARAMETERS]
-p: ENUM
+p: ENUM
 ==
 
 # Setting unsupported_fields to a positional parameter
@@ -747,11 +743,11 @@ QueryStmt
     +-column_list=[$query.$col1#1]
     +-expr_list=
     | +-$col1#1 :=
-    |   +-FunctionCall(ZetaSQL:to_json(INT64, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
+    |   +-FunctionCall(ZetaSQL:to_json(INT64, optional(1) BOOL stringify_wide_numbers, optional(1) ENUM unsupported_fields) -> JSON)
     |     +-Literal(type=INT64, value=1)
     |     +-Literal(type=BOOL, value=false)
-    |     +-Parameter(parse_location=38-39, type=ENUM, position=1)
+    |     +-Parameter(parse_location=38-39, type=ENUM, position=1)
     +-input_scan=
       +-SingleRowScan
 [UNDECLARED_PARAMETERS]
-ENUM
+ENUM
diff --git a/zetasql/analyzer/testdata/lambda.test b/zetasql/analyzer/testdata/lambda.test
index 5294e4a04..3e6fc9779 100644
--- a/zetasql/analyzer/testdata/lambda.test
+++ b/zetasql/analyzer/testdata/lambda.test
@@ -1338,7 +1338,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#6)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=221-224
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |         +-input_scan=
@@ -1437,7 +1436,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#6)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=221-224
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |         +-input_scan=
@@ -1537,7 +1535,6 @@ QueryStmt
     |         |           |         +-ColumnHolder(column=$array_offset.off#6)
     |         |           +-order_by_item_list=
     |         |             +-OrderByItem
-    |         |               +-parse_location=221-224
     |         |               +-column_ref=
     |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |         +-input_scan=
diff --git a/zetasql/analyzer/testdata/limit.test b/zetasql/analyzer/testdata/limit.test
index 9baa61fbc..eb300f596 100644
--- a/zetasql/analyzer/testdata/limit.test
+++ b/zetasql/analyzer/testdata/limit.test
@@ -777,7 +777,6 @@ QueryStmt
     |   | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=34-35
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-limit=
@@ -801,7 +800,6 @@ QueryStmt
     |   | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=34-35
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-limit=
diff --git a/zetasql/analyzer/testdata/literals.test b/zetasql/analyzer/testdata/literals.test
index 3d2e1476d..f5e8ea345 100644
--- a/zetasql/analyzer/testdata/literals.test
+++ b/zetasql/analyzer/testdata/literals.test
@@ -1223,7 +1223,6 @@ QueryStmt
     |     +-$agg2#21 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=74-77
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg1#20)
 ==
diff --git a/zetasql/analyzer/testdata/map_functions.test b/zetasql/analyzer/testdata/map_functions.test
index 86afe5142..038708797 100644
--- a/zetasql/analyzer/testdata/map_functions.test
+++ b/zetasql/analyzer/testdata/map_functions.test
@@ -2333,3 +2333,335 @@ QueryStmt
       +-SingleRowScan
 ==
 
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 2) as a,
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'b', 2) as b,
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), NULL, 2) as c,
+  MAP_REPLACE(MAP_FROM_ARRAY(CAST(NULL AS ARRAY>)), 'a', 2) as d,
+  MAP_REPLACE(MAP_FROM_ARRAY(CAST(NULL AS ARRAY>)), 'a', 2, 'b', 3) as e,
+--
+QueryStmt
++-output_column_list=
+| +-$query.a#1 AS a [MAP]
+| +-$query.b#2 AS b [MAP]
+| +-$query.c#3 AS c [MAP]
+| +-$query.d#4 AS d [MAP]
+| +-$query.e#5 AS e [MAP]
++-query=
+  +-ProjectScan
+    +-column_list=$query.[a#1, b#2, c#3, d#4, e#5]
+    +-expr_list=
+    | +-a#1 :=
+    | | +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, INT64, repeated(0) STRING, repeated(0) INT64) -> MAP)
+    | |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    | |   | +-Literal(type=ARRAY>, value=[{"a", 1}])
+    | |   +-Literal(type=STRING, value="a")
+    | |   +-Literal(type=INT64, value=2)
+    | +-b#2 :=
+    | | +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, INT64, repeated(0) STRING, repeated(0) INT64) -> MAP)
+    | |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    | |   | +-Literal(type=ARRAY>, value=[{"a", 1}])
+    | |   +-Literal(type=STRING, value="b")
+    | |   +-Literal(type=INT64, value=2)
+    | +-c#3 :=
+    | | +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, INT64, repeated(0) STRING, repeated(0) INT64) -> MAP)
+    | |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    | |   | +-Literal(type=ARRAY>, value=[{"a", 1}])
+    | |   +-Literal(type=STRING, value=NULL)
+    | |   +-Literal(type=INT64, value=2)
+    | +-d#4 :=
+    | | +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, INT64, repeated(0) STRING, repeated(0) INT64) -> MAP)
+    | |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    | |   | +-Literal(type=ARRAY>, value=NULL, has_explicit_type=TRUE)
+    | |   +-Literal(type=STRING, value="a")
+    | |   +-Literal(type=INT64, value=2)
+    | +-e#5 :=
+    |   +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, INT64, repeated(1) STRING, repeated(1) INT64) -> MAP)
+    |     +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    |     | +-Literal(type=ARRAY>, value=NULL, has_explicit_type=TRUE)
+    |     +-Literal(type=STRING, value="a")
+    |     +-Literal(type=INT64, value=2)
+    |     +-Literal(type=STRING, value="b")
+    |     +-Literal(type=INT64, value=3)
+    +-input_scan=
+      +-SingleRowScan
+==
+
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]));
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: MAP. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]));
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: MAP
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Signature requires at least 3 arguments, found 1 argument
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Signature requires at least 3 arguments, found 1 argument [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]));
+  ^
+==
+
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), {{'a'|'a', 'b'|1, 2|'a', 2, 'b'}});
+--
+ALTERNATION GROUP: 'a'
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: MAP, STRING. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a');
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: MAP, STRING
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Signature requires at least 3 arguments, found 2 arguments
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Signature requires at least 3 arguments, found 2 arguments [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a');
+  ^
+--
+ALTERNATION GROUP: 'a', 'b'
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: MAP, STRING, STRING. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b');
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: MAP, STRING, STRING
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Unable to find common supertype for templated argument 
+      Input types for : {INT64, STRING}
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Argument 3: expected FUNCTIONT2>, found STRING [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b');
+  ^
+--
+ALTERNATION GROUP: 1, 2
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: MAP, INT64, INT64. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 1, 2);
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: MAP, INT64, INT64
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Unable to find common supertype for templated argument 
+      Input types for : {INT64, STRING}
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Argument 3: expected FUNCTIONT2>, found INT64 [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 1, 2);
+  ^
+--
+ALTERNATION GROUP: 'a', 2, 'b'
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: MAP, STRING, INT64, STRING. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 2, 'b');
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: MAP, STRING, INT64, STRING
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Wrong number of repeated arguments provided. Expected a multiple of 2 but got 1 repeated argument
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Argument 4: expected FUNCTIONT2>, found STRING [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 2, 'b');
+  ^
+==
+
+[language_features=V_1_4_MAP_TYPE,V_1_3_INLINE_LAMBDA_ARGUMENT]
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), {{'a'|NULL|'a', 'b', 'c'}}, v -> v + 1);
+--
+ALTERNATION GROUP: 'a'
+--
+QueryStmt
++-output_column_list=
+| +-$query.$col1#2 AS `$col1` [MAP]
++-query=
+  +-ProjectScan
+    +-column_list=[$query.$col1#2]
+    +-expr_list=
+    | +-$col1#2 :=
+    |   +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, repeated(0) STRING, FUNCTIONINT64> value) -> MAP)
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    |     |     +-Literal(type=ARRAY>, value=[{"a", 1}])
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value="a")
+    |     +-FunctionArgument
+    |       +-inline_lambda=
+    |         +-InlineLambda
+    |           +-argument_list=[$lambda_arg.v#1]
+    |           +-body=
+    |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
+    |               +-ColumnRef(type=INT64, column=$lambda_arg.v#1)
+    |               +-Literal(type=INT64, value=1)
+    +-input_scan=
+      +-SingleRowScan
+--
+ALTERNATION GROUP: NULL
+--
+QueryStmt
++-output_column_list=
+| +-$query.$col1#2 AS `$col1` [MAP]
++-query=
+  +-ProjectScan
+    +-column_list=[$query.$col1#2]
+    +-expr_list=
+    | +-$col1#2 :=
+    |   +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, repeated(0) STRING, FUNCTIONINT64> value) -> MAP)
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    |     |     +-Literal(type=ARRAY>, value=[{"a", 1}])
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value=NULL)
+    |     +-FunctionArgument
+    |       +-inline_lambda=
+    |         +-InlineLambda
+    |           +-argument_list=[$lambda_arg.v#1]
+    |           +-body=
+    |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
+    |               +-ColumnRef(type=INT64, column=$lambda_arg.v#1)
+    |               +-Literal(type=INT64, value=1)
+    +-input_scan=
+      +-SingleRowScan
+--
+ALTERNATION GROUP: 'a', 'b', 'c'
+--
+QueryStmt
++-output_column_list=
+| +-$query.$col1#2 AS `$col1` [MAP]
++-query=
+  +-ProjectScan
+    +-column_list=[$query.$col1#2]
+    +-expr_list=
+    | +-$col1#2 :=
+    |   +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, repeated(2) STRING, FUNCTIONINT64> value) -> MAP)
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    |     |     +-Literal(type=ARRAY>, value=[{"a", 1}])
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value="a")
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value="b")
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value="c")
+    |     +-FunctionArgument
+    |       +-inline_lambda=
+    |         +-InlineLambda
+    |           +-argument_list=[$lambda_arg.v#1]
+    |           +-body=
+    |             +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
+    |               +-ColumnRef(type=INT64, column=$lambda_arg.v#1)
+    |               +-Literal(type=INT64, value=1)
+    +-input_scan=
+      +-SingleRowScan
+==
+
+[language_features=V_1_4_MAP_TYPE,V_1_3_INLINE_LAMBDA_ARGUMENT]
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b', 'c', v -> NULL);
+--
+QueryStmt
++-output_column_list=
+| +-$query.$col1#2 AS `$col1` [MAP]
++-query=
+  +-ProjectScan
+    +-column_list=[$query.$col1#2]
+    +-expr_list=
+    | +-$col1#2 :=
+    |   +-FunctionCall(ZetaSQL:map_replace(MAP input_map, STRING, repeated(2) STRING, FUNCTIONINT64> value) -> MAP)
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-FunctionCall(ZetaSQL:map_from_array(ARRAY>) -> MAP)
+    |     |     +-Literal(type=ARRAY>, value=[{"a", 1}])
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value="a")
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value="b")
+    |     +-FunctionArgument
+    |     | +-expr=
+    |     |   +-Literal(type=STRING, value="c")
+    |     +-FunctionArgument
+    |       +-inline_lambda=
+    |         +-InlineLambda
+    |           +-argument_list=[$lambda_arg.v#1]
+    |           +-body=
+    |             +-Literal(type=INT64, value=NULL)
+    +-input_scan=
+      +-SingleRowScan
+==
+
+[language_features=V_1_4_MAP_TYPE,V_1_3_INLINE_LAMBDA_ARGUMENT]
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b', 1, v -> v + 1);
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: MAP, STRING, STRING, INT64, LAMBDA. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b', 1, v -> v + 1);
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: MAP, STRING, STRING, INT64, LAMBDA
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Argument 5: expected T2, found LAMBDA
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Unable to find common supertype for templated argument 
+      Input types for : {INT64, STRING, STRING} [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b', 1, v -> v + 1);
+  ^
+==
+
+[language_features=V_1_4_MAP_TYPE,V_1_3_INLINE_LAMBDA_ARGUMENT]
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b', 1, v -> 'a');
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: MAP, STRING, STRING, INT64, LAMBDA. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b', 1, v -> 'a');
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: MAP, STRING, STRING, INT64, LAMBDA
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Argument 5: expected T2, found LAMBDA
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Argument 5: failed to resolve lambda body, error: Lambda should return type INT64, but returns STRING [at 2:3]
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 'b', 1, v -> 'a');
+  ^
+==
+
+[language_features=V_1_4_MAP_TYPE,V_1_3_INLINE_LAMBDA_ARGUMENT]
+SELECT
+  MAP_REPLACE(NULL, 'a', 'b', 1, v -> 'a');
+--
+ERROR: No matching signature for function MAP_REPLACE for argument types: NULL, STRING, STRING, INT64, LAMBDA. Supported signatures: MAP_REPLACE(MAP, ANY, ANY, [[ANY, ANY], ...]); MAP_REPLACE(MAP, ANY, [ANY, ...], FUNCTIONANY>) [at 2:3]
+  MAP_REPLACE(NULL, 'a', 'b', 1, v -> 'a');
+  ^
+--
+Signature Mismatch Details:
+ERROR: No matching signature for function MAP_REPLACE
+  Argument types: NULL, STRING, STRING, INT64, LAMBDA
+  Signature: MAP_REPLACE(MAP, T1, T2, [[T1, T2], ...])
+    Argument 5: expected T2, found LAMBDA
+  Signature: MAP_REPLACE(MAP, T1, [T1, ...], FUNCTIONT2>)
+    Failed to infer type  [at 2:3]
+  MAP_REPLACE(NULL, 'a', 'b', 1, v -> 'a');
+  ^
diff --git a/zetasql/analyzer/testdata/match_recognize.test b/zetasql/analyzer/testdata/match_recognize.test
new file mode 100644
index 000000000..ad256890d
--- /dev/null
+++ b/zetasql/analyzer/testdata/match_recognize.test
@@ -0,0 +1,110 @@
+[default language_features=TABLE_VALUED_FUNCTIONS,NAMED_ARGUMENTS]
+[default show_unparsed]
+
+select m from KeyValue MATCH_RECOGNIZE(
+  ORDER BY key
+  MEASURES min(value) AS m
+  PATTERN (a b)
+  DEFINE
+    A AS length(value) < 10,
+    B AS length(value) >= 10
+)
+--
+ERROR: MATCH_RECOGNIZE is not supported [at 1:24]
+select m from KeyValue MATCH_RECOGNIZE(
+                       ^
+==
+
+# subquery
+select m from (SELECT * FROM KeyValue) AS k MATCH_RECOGNIZE(
+  ORDER BY key
+  MEASURES min(value) AS m
+  PATTERN (a b)
+  DEFINE
+    A AS length(value) < 10,
+    B AS length(value) >= 10
+)
+--
+ERROR: MATCH_RECOGNIZE is not supported [at 1:45]
+select m from (SELECT * FROM KeyValue) AS k MATCH_RECOGNIZE(
+                                            ^
+==
+
+# UNNEST
+select m from UNNEST(['abc', 'def']) AS value MATCH_RECOGNIZE(
+  ORDER BY key
+  MEASURES min(value) AS m
+  PATTERN (a b)
+  DEFINE
+    A AS length(value) < 10,
+    B AS length(value) >= 10
+)
+--
+ERROR: MATCH_RECOGNIZE is not allowed with array scans [at 1:47]
+select m from UNNEST(['abc', 'def']) AS value MATCH_RECOGNIZE(
+                                              ^
+==
+
+# 2 UNNESTs
+select m from UNNEST([1, 2]) AS key, UNNEST(['abc', 'def']) AS value
+MATCH_RECOGNIZE(
+  ORDER BY key
+  MEASURES min(value) AS m
+  PATTERN (a b)
+  DEFINE
+    A AS length(value) < 10,
+    B AS length(value) >= 10
+)
+--
+ERROR: MATCH_RECOGNIZE is not allowed with array scans [at 2:1]
+MATCH_RECOGNIZE(
+^
+==
+
+# Table with 2 UNNESTs
+select m from UNNEST([1, 2]) AS key, UNNEST(['abc', 'def']) AS value
+MATCH_RECOGNIZE(
+  ORDER BY key
+  MEASURES min(value) AS m
+  PATTERN (a b)
+  DEFINE
+    A AS length(value) < 10,
+    B AS length(value) >= 10
+)
+--
+ERROR: MATCH_RECOGNIZE is not allowed with array scans [at 2:1]
+MATCH_RECOGNIZE(
+^
+==
+
+# TVF
+select * from tvf_no_args()
+MATCH_RECOGNIZE(
+  ORDER BY key
+  MEASURES min(value) AS m
+  PATTERN (a b)
+  DEFINE
+    A AS length(value) < 10,
+    B AS length(value) >= 10
+)
+--
+ERROR: MATCH_RECOGNIZE is not supported [at 2:1]
+MATCH_RECOGNIZE(
+^
+==
+
+# parenthesized join
+select m from (KeyValue t1 INNER JOIN KeyValue t2 ON t1.key = t2.key)
+MATCH_RECOGNIZE(
+  ORDER BY key
+  MEASURES min(value) AS m
+  PATTERN (a b)
+  DEFINE
+    A AS length(value) < 10,
+    B AS length(value) >= 10
+)
+--
+ERROR: MATCH_RECOGNIZE is not supported [at 2:1]
+MATCH_RECOGNIZE(
+^
+==
diff --git a/zetasql/analyzer/testdata/order_by_collate.test b/zetasql/analyzer/testdata/order_by_collate.test
index f10136095..78cd911b6 100644
--- a/zetasql/analyzer/testdata/order_by_collate.test
+++ b/zetasql/analyzer/testdata/order_by_collate.test
@@ -3,7 +3,9 @@
 select `string` col from SimpleTypes
 order by col COLLATE {{"en_US"|@test_param_string|r"en_US"}}
 --
-ALTERNATION GROUP: "en_US"
+ALTERNATION GROUPS:
+    "en_US"
+    r"en_US"
 --
 QueryStmt
 +-output_column_list=
@@ -16,7 +18,6 @@ QueryStmt
     | +-TableScan(column_list=[SimpleTypes.string#5], table=SimpleTypes, column_index_list=[4])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=46-65
         +-column_ref=
         | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
         +-collation_name=
@@ -35,30 +36,10 @@ QueryStmt
     | +-TableScan(column_list=[SimpleTypes.string#5], table=SimpleTypes, column_index_list=[4])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=46-76
         +-column_ref=
         | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
         +-collation_name=
           +-Parameter(type=STRING, name="test_param_string")
---
-ALTERNATION GROUP: r"en_US"
---
-QueryStmt
-+-output_column_list=
-| +-SimpleTypes.string#5 AS col [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[SimpleTypes.string#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[SimpleTypes.string#5], table=SimpleTypes, column_index_list=[4])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=46-66
-        +-column_ref=
-        | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
-        +-collation_name=
-          +-Literal(type=STRING, value="en_US")
 ==
 
 # COLLATE must be followed by a string literal or a string parameter.
@@ -132,7 +113,6 @@ QueryStmt
     |     +-TableScan(column_list=[KeyValue.Value#2], table=KeyValue, column_index_list=[1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=36-70
         +-column_ref=
         | +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#3)
         +-collation_name=
@@ -175,26 +155,22 @@ QueryStmt
     | +-TableScan(column_list=SimpleTypes.[int32#1, string#5], table=SimpleTypes, column_index_list=[0, 4])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=75-93
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
       | +-collation_name=
       |   +-Literal(type=STRING, value="s1")
       +-OrderByItem
-      | +-parse_location=104-123
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
       | +-collation_name=
       | | +-Literal(type=STRING, value="s2")
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=134-166
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
       | +-collation_name=
       |   +-Parameter(type=STRING, name="test_param_string")
       +-OrderByItem
-        +-parse_location=177-183
         +-column_ref=
         | +-ColumnRef(type=INT32, column=SimpleTypes.int32#1)
         +-is_descending=TRUE
@@ -218,19 +194,16 @@ QueryStmt
     | +-TableScan(column_list=[SimpleTypes.string#5], table=SimpleTypes, column_index_list=[4])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=44-56
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
       | +-collation_name=
       |   +-Literal(type=STRING, value="")
       +-OrderByItem
-      | +-parse_location=67-83
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
       | +-collation_name=
       |   +-Literal(type=STRING, value="NULL")
       +-OrderByItem
-        +-parse_location=94-117
         +-column_ref=
         | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
         +-collation_name=
@@ -461,7 +434,6 @@ QueryStmt
     | +-TableScan(column_list=[SimpleTypes.string#5], table=SimpleTypes, column_index_list=[4])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=42-59
         +-column_ref=
         | +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
         +-collation_name=
diff --git a/zetasql/analyzer/testdata/order_by_in_aggregate.test b/zetasql/analyzer/testdata/order_by_in_aggregate.test
index 176faef46..73a3f5eed 100644
--- a/zetasql/analyzer/testdata/order_by_in_aggregate.test
+++ b/zetasql/analyzer/testdata/order_by_in_aggregate.test
@@ -20,7 +20,6 @@ QueryStmt
               +-ColumnRef(type=INT32, column=TestTable.key#1)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=30-33
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=TestTable.key#1)
 
@@ -71,11 +70,9 @@ QueryStmt
               +-ColumnRef(type=INT32, column=TestTable.key#1)
               +-order_by_item_list=
                 +-OrderByItem
-                | +-parse_location=30-39
                 | +-column_ref=
                 |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
                 +-OrderByItem
-                  +-parse_location=41-50
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol2#5)
 
@@ -108,7 +105,6 @@ QueryStmt
                 +-Literal(type=INT64, value=1)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=52-55
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=TestTable.key#1)
 
@@ -145,12 +141,10 @@ QueryStmt
     |           +-default_value=0
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=53-56
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=TestTable.key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=95-98
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.Key#5)
 
@@ -190,7 +184,6 @@ QueryStmt
                 +-default_value="default_name"
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=50-57
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 
@@ -227,7 +220,6 @@ QueryStmt
     |     |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     |     |   +-order_by_item_list=
     |     |     +-OrderByItem
-    |     |       +-parse_location=45-50
     |     |       +-column_ref=
     |     |         +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     |     +-s2#4 :=
@@ -236,7 +228,6 @@ QueryStmt
     |     |   +-Literal(type=STRING, value=",")
     |     |   +-order_by_item_list=
     |     |     +-OrderByItem
-    |     |       +-parse_location=97-102
     |     |       +-column_ref=
     |     |         +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     |     +-s3#5 :=
@@ -246,24 +237,19 @@ QueryStmt
     |         +-Literal(type=BYTES, value=b"b")
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=165-170
     |             +-column_ref=
     |               +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=214-217
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.key#6)
       +-OrderByItem
-      | +-parse_location=219-221
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$aggregate.s1#3)
       +-OrderByItem
-      | +-parse_location=223-225
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$aggregate.s2#4)
       +-OrderByItem
-        +-parse_location=227-229
         +-column_ref=
           +-ColumnRef(type=BYTES, column=$aggregate.s3#5)
 
@@ -323,7 +309,6 @@ QueryStmt
           |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=60-81
           |       +-column_ref=
           |         +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           +-$agg2#7 :=
@@ -331,7 +316,6 @@ QueryStmt
               +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#6)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=134-156
                   +-column_ref=
                     +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#6)
 
@@ -379,7 +363,6 @@ QueryStmt
                     +-default_value=0
                   +-order_by_item_list=
                     +-OrderByItem
-                      +-parse_location=73-76
                       +-column_ref=
                         +-ColumnRef(type=INT32, column=TestTable.key#1)
 
@@ -434,7 +417,6 @@ QueryStmt
                 +-default_value=0
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=71-106
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#7)
 
@@ -477,7 +459,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=69-90
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 
@@ -541,12 +522,10 @@ QueryStmt
                 +-default_value=0
               +-order_by_item_list=
                 +-OrderByItem
-                | +-parse_location=71-79
                 | +-column_ref=
                 | | +-ColumnRef(type=INT32, column=TestTable.key#1)
                 | +-is_descending=TRUE
                 +-OrderByItem
-                  +-parse_location=81-107
                   +-column_ref=
                   | +-ColumnRef(type=INT32, column=$orderby.$orderbycol2#4)
                   +-is_descending=TRUE
@@ -590,7 +569,6 @@ QueryStmt
               +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=73-116
                   +-column_ref=
                   | +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
                   +-collation_name=
@@ -703,16 +681,13 @@ QueryStmt
           |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |     | +-parse_location=71-92
           |     | +-column_ref=
           |     |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           |     +-OrderByItem
-          |     | +-parse_location=114-144
           |     | +-column_ref=
           |     | | +-ColumnRef(type=INT64, column=$orderby.$orderbycol2#5)
           |     | +-is_descending=TRUE
           |     +-OrderByItem
-          |       +-parse_location=166-188
           |       +-column_ref=
           |         +-ColumnRef(type=STRING, column=$orderby.$orderbycol3#6)
           +-b#9 :=
@@ -725,15 +700,12 @@ QueryStmt
           |     +-default_value=77
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |     | +-parse_location=255-276
           |     | +-column_ref=
           |     |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           |     +-OrderByItem
-          |     | +-parse_location=298-320
           |     | +-column_ref=
           |     |   +-ColumnRef(type=STRING, column=$orderby.$orderbycol3#6)
           |     +-OrderByItem
-          |       +-parse_location=342-372
           |       +-column_ref=
           |       | +-ColumnRef(type=INT64, column=$orderby.$orderbycol3#8)
           |       +-is_descending=TRUE
@@ -742,16 +714,13 @@ QueryStmt
               +-ColumnRef(type=STRING, column=$orderby.$orderbycol3#6)
               +-order_by_item_list=
                 +-OrderByItem
-                | +-parse_location=441-462
                 | +-column_ref=
                 |   +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#10)
                 +-OrderByItem
-                | +-parse_location=484-510
                 | +-column_ref=
                 | | +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
                 | +-is_descending=TRUE
                 +-OrderByItem
-                  +-parse_location=532-575
                   +-column_ref=
                   | +-ColumnRef(type=STRING, column=$orderby.$orderbycol3#6)
                   +-collation_name=
@@ -825,7 +794,6 @@ QueryStmt
         |         +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
         |         +-order_by_item_list=
         |           +-OrderByItem
-        |             +-parse_location=60-81
         |             +-column_ref=
         |               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
         +-function_group_list=
@@ -901,7 +869,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=39-42
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=TestTable.key#1)
 
@@ -910,57 +877,6 @@ QueryStmt
 select array_agg(distinct {{|TestTable.}}Key order by {{|TestTable.}}Key)
 from TestTable
 --
-ALTERNATION GROUP: 
-
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#4 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#4]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#4]
-        +-input_scan=
-        | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#4 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=TestTable.key#1)
-              +-distinct=TRUE
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=39-42
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=TestTable.key#1)
---
-ALTERNATION GROUP: TestTable.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#4 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#4]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#4]
-        +-input_scan=
-        | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#4 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=TestTable.key#1)
-              +-distinct=TRUE
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=39-52
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=TestTable.key#1)
---
-ALTERNATION GROUP: TestTable.,
---
 QueryStmt
 +-output_column_list=
 | +-$aggregate.$agg1#4 AS `$col1` [ARRAY]
@@ -979,41 +895,14 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=49-52
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=TestTable.key#1)
---
-ALTERNATION GROUP: TestTable.,TestTable.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#4 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#4]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#4]
-        +-input_scan=
-        | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#4 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=TestTable.key#1)
-              +-distinct=TRUE
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=49-62
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=TestTable.key#1)
+
 ==
 
 select array_agg({{|KitchenSinkValueTable.}}int64_key_1
                  order by {{|KitchenSinkValueTable.}}int64_key_1)
 from KitchenSinkValueTable
---
-ALTERNATION GROUP: 
-
 --
 QueryStmt
 +-output_column_list=
@@ -1042,108 +931,9 @@ QueryStmt
               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=55-66
-                  +-column_ref=
-                    +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
---
-ALTERNATION GROUP: KitchenSinkValueTable.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#3 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#3]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#3]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[KitchenSinkValueTable.value#1, $orderby.$orderbycol1#2]
-        |   +-expr_list=
-        |   | +-$orderbycol1#2 :=
-        |   |   +-GetProtoField
-        |   |     +-type=INT64
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=KitchenSinkValueTable.value#1)
-        |   |     +-field_descriptor=int64_key_1
-        |   +-input_scan=
-        |     +-TableScan(column_list=[KitchenSinkValueTable.value#1], table=KitchenSinkValueTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#3 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT64) -> ARRAY)
-              +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=55-88
-                  +-column_ref=
-                    +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
---
-ALTERNATION GROUP: KitchenSinkValueTable.,
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#3 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#3]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#3]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[KitchenSinkValueTable.value#1, $orderby.$orderbycol1#2]
-        |   +-expr_list=
-        |   | +-$orderbycol1#2 :=
-        |   |   +-GetProtoField
-        |   |     +-type=INT64
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=KitchenSinkValueTable.value#1)
-        |   |     +-field_descriptor=int64_key_1
-        |   +-input_scan=
-        |     +-TableScan(column_list=[KitchenSinkValueTable.value#1], table=KitchenSinkValueTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#3 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT64) -> ARRAY)
-              +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=77-88
-                  +-column_ref=
-                    +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
---
-ALTERNATION GROUP: KitchenSinkValueTable.,KitchenSinkValueTable.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#3 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#3]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#3]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[KitchenSinkValueTable.value#1, $orderby.$orderbycol1#2]
-        |   +-expr_list=
-        |   | +-$orderbycol1#2 :=
-        |   |   +-GetProtoField
-        |   |     +-type=INT64
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=KitchenSinkValueTable.value#1)
-        |   |     +-field_descriptor=int64_key_1
-        |   +-input_scan=
-        |     +-TableScan(column_list=[KitchenSinkValueTable.value#1], table=KitchenSinkValueTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#3 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT64) -> ARRAY)
-              +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=77-110
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
+
 ==
 
 select array_agg(distinct KitchenSinkValueTable.int64_key_1
@@ -1178,7 +968,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=86-119
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#2)
 
@@ -1187,9 +976,6 @@ QueryStmt
 select array_agg({{|TestStructValueTable.}}a
                  order by {{|TestStructValueTable.}}a)
 from TestStructValueTable
---
-ALTERNATION GROUP: 
-
 --
 QueryStmt
 +-output_column_list=
@@ -1218,108 +1004,9 @@ QueryStmt
               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=45-46
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
---
-ALTERNATION GROUP: TestStructValueTable.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#3 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#3]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#3]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[TestStructValueTable.value#1, $orderby.$orderbycol1#2]
-        |   +-expr_list=
-        |   | +-$orderbycol1#2 :=
-        |   |   +-GetStructField
-        |   |     +-type=INT32
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
-        |   |     +-field_idx=0
-        |   +-input_scan=
-        |     +-TableScan(column_list=[TestStructValueTable.value#1], table=TestStructValueTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#3 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=45-67
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
---
-ALTERNATION GROUP: TestStructValueTable.,
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#3 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#3]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#3]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[TestStructValueTable.value#1, $orderby.$orderbycol1#2]
-        |   +-expr_list=
-        |   | +-$orderbycol1#2 :=
-        |   |   +-GetStructField
-        |   |     +-type=INT32
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
-        |   |     +-field_idx=0
-        |   +-input_scan=
-        |     +-TableScan(column_list=[TestStructValueTable.value#1], table=TestStructValueTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#3 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=66-67
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
---
-ALTERNATION GROUP: TestStructValueTable.,TestStructValueTable.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#3 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#3]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#3]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[TestStructValueTable.value#1, $orderby.$orderbycol1#2]
-        |   +-expr_list=
-        |   | +-$orderbycol1#2 :=
-        |   |   +-GetStructField
-        |   |     +-type=INT32
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=STRUCT, column=TestStructValueTable.value#1)
-        |   |     +-field_idx=0
-        |   +-input_scan=
-        |     +-TableScan(column_list=[TestStructValueTable.value#1], table=TestStructValueTable, column_index_list=[0])
-        +-aggregate_list=
-          +-$agg1#3 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=66-88
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
+
 ==
 
 select array_agg(distinct TestStructValueTable.a
@@ -1354,7 +1041,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=75-97
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#2)
 
@@ -1368,107 +1054,6 @@ from
             KitchenSink.string_val as string_val
      from TestTable) as T
 --
-ALTERNATION GROUP: 
-
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#7 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#7]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#7]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[TestTable.key#1, T.int64_val#4, T.int32_val#5, T.string_val#6]
-        |   +-expr_list=
-        |   | +-int64_val#4 :=
-        |   | | +-GetProtoField
-        |   | |   +-type=INT64
-        |   | |   +-expr=
-        |   | |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   | |   +-field_descriptor=int64_val
-        |   | |   +-default_value=0
-        |   | +-int32_val#5 :=
-        |   | | +-GetProtoField
-        |   | |   +-type=INT32
-        |   | |   +-expr=
-        |   | |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   | |   +-field_descriptor=int32_val
-        |   | |   +-default_value=77
-        |   | +-string_val#6 :=
-        |   |   +-GetProtoField
-        |   |     +-type=STRING
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   |     +-field_descriptor=string_val
-        |   |     +-default_value="default_name"
-        |   +-input_scan=
-        |     +-TableScan(column_list=TestTable.[key#1, KitchenSink#3], table=TestTable, column_index_list=[0, 2])
-        +-aggregate_list=
-          +-$agg1#7 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=TestTable.key#1)
-              +-distinct=TRUE
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=39-42
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=TestTable.key#1)
---
-ALTERNATION GROUP: T.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#7 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#7]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#7]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[TestTable.key#1, T.int64_val#4, T.int32_val#5, T.string_val#6]
-        |   +-expr_list=
-        |   | +-int64_val#4 :=
-        |   | | +-GetProtoField
-        |   | |   +-type=INT64
-        |   | |   +-expr=
-        |   | |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   | |   +-field_descriptor=int64_val
-        |   | |   +-default_value=0
-        |   | +-int32_val#5 :=
-        |   | | +-GetProtoField
-        |   | |   +-type=INT32
-        |   | |   +-expr=
-        |   | |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   | |   +-field_descriptor=int32_val
-        |   | |   +-default_value=77
-        |   | +-string_val#6 :=
-        |   |   +-GetProtoField
-        |   |     +-type=STRING
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   |     +-field_descriptor=string_val
-        |   |     +-default_value="default_name"
-        |   +-input_scan=
-        |     +-TableScan(column_list=TestTable.[key#1, KitchenSink#3], table=TestTable, column_index_list=[0, 2])
-        +-aggregate_list=
-          +-$agg1#7 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=TestTable.key#1)
-              +-distinct=TRUE
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=39-44
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=TestTable.key#1)
---
-ALTERNATION GROUP: T.,
---
 QueryStmt
 +-output_column_list=
 | +-$aggregate.$agg1#7 AS `$col1` [ARRAY]
@@ -1512,58 +1097,9 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=41-44
-                  +-column_ref=
-                    +-ColumnRef(type=INT32, column=TestTable.key#1)
---
-ALTERNATION GROUP: T.,T.
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#7 AS `$col1` [ARRAY]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#7]
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$aggregate.$agg1#7]
-        +-input_scan=
-        | +-ProjectScan
-        |   +-column_list=[TestTable.key#1, T.int64_val#4, T.int32_val#5, T.string_val#6]
-        |   +-expr_list=
-        |   | +-int64_val#4 :=
-        |   | | +-GetProtoField
-        |   | |   +-type=INT64
-        |   | |   +-expr=
-        |   | |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   | |   +-field_descriptor=int64_val
-        |   | |   +-default_value=0
-        |   | +-int32_val#5 :=
-        |   | | +-GetProtoField
-        |   | |   +-type=INT32
-        |   | |   +-expr=
-        |   | |   | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   | |   +-field_descriptor=int32_val
-        |   | |   +-default_value=77
-        |   | +-string_val#6 :=
-        |   |   +-GetProtoField
-        |   |     +-type=STRING
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=TestTable.KitchenSink#3)
-        |   |     +-field_descriptor=string_val
-        |   |     +-default_value="default_name"
-        |   +-input_scan=
-        |     +-TableScan(column_list=TestTable.[key#1, KitchenSink#3], table=TestTable, column_index_list=[0, 2])
-        +-aggregate_list=
-          +-$agg1#7 :=
-            +-AggregateFunctionCall(ZetaSQL:array_agg(INT32) -> ARRAY)
-              +-ColumnRef(type=INT32, column=TestTable.key#1)
-              +-distinct=TRUE
-              +-order_by_item_list=
-                +-OrderByItem
-                  +-parse_location=41-46
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=TestTable.key#1)
+
 ==
 
 select Key,
@@ -1631,7 +1167,6 @@ QueryStmt
           |   +-distinct=TRUE
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=111-120
           |       +-column_ref=
           |         +-ColumnRef(type=INT32, column=$subquery1.int32_val#5)
           +-b#8 :=
@@ -1642,11 +1177,9 @@ QueryStmt
           |   +-distinct=TRUE
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |     | +-parse_location=227-236
           |     | +-column_ref=
           |     |   +-ColumnRef(type=INT32, column=$subquery1.int32_val#5)
           |     +-OrderByItem
-          |       +-parse_location=238-247
           |       +-column_ref=
           |         +-ColumnRef(type=INT64, column=$subquery1.int64_val#4)
           +-c#9 :=
@@ -1657,15 +1190,12 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                | +-parse_location=354-363
                 | +-column_ref=
                 |   +-ColumnRef(type=INT32, column=$subquery1.int32_val#5)
                 +-OrderByItem
-                | +-parse_location=365-374
                 | +-column_ref=
                 |   +-ColumnRef(type=INT64, column=$subquery1.int64_val#4)
                 +-OrderByItem
-                  +-parse_location=376-386
                   +-column_ref=
                     +-ColumnRef(type=STRING, column=$subquery1.string_val#6)
 
@@ -1732,16 +1262,13 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                | +-parse_location=111-125
                 | +-column_ref=
                 | | +-ColumnRef(type=INT32, column=$subquery1.int32_val#5)
                 | +-is_descending=TRUE
                 +-OrderByItem
-                | +-parse_location=160-173
                 | +-column_ref=
                 |   +-ColumnRef(type=INT64, column=$subquery1.int64_val#4)
                 +-OrderByItem
-                  +-parse_location=208-223
                   +-column_ref=
                   | +-ColumnRef(type=STRING, column=$subquery1.string_val#6)
                   +-is_descending=TRUE
@@ -1807,15 +1334,12 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                | +-parse_location=111-120
                 | +-column_ref=
                 |   +-ColumnRef(type=INT32, column=$subquery1.int32_val#5)
                 +-OrderByItem
-                | +-parse_location=122-131
                 | +-column_ref=
                 |   +-ColumnRef(type=INT32, column=$subquery1.int32_val#5)
                 +-OrderByItem
-                  +-parse_location=133-142
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$subquery1.int64_val#4)
 
@@ -1909,7 +1433,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=40-44
                   +-column_ref=
                     +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#4)
 ==
@@ -1968,7 +1491,6 @@ QueryStmt
               +-ColumnRef(type=ARRAY, column=$union_all.x#8)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=35-36
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$union_all.w#7)
 ==
@@ -2027,7 +1549,6 @@ QueryStmt
               +-ColumnRef(type=ARRAY, column=$union_all.x#8)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=35-41
                   +-column_ref=
                   | +-ColumnRef(type=INT64, column=$union_all.w#7)
                   +-is_descending=TRUE
@@ -2078,7 +1599,6 @@ QueryStmt
           |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           |   +-order_by_item_list=
           |   | +-OrderByItem
-          |   |   +-parse_location=60-81
           |   |   +-column_ref=
           |   |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
           |   +-limit=
@@ -2088,7 +1608,6 @@ QueryStmt
               +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#6)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=143-165
               |   +-column_ref=
               |     +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#6)
               +-limit=
@@ -2160,7 +1679,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=39-42
               |   +-column_ref=
               |     +-ColumnRef(type=INT32, column=TestTable.key#1)
               +-limit=
@@ -2209,12 +1727,10 @@ QueryStmt
                 +-default_value=0
               +-order_by_item_list=
               | +-OrderByItem
-              | | +-parse_location=71-79
               | | +-column_ref=
               | | | +-ColumnRef(type=INT32, column=TestTable.key#1)
               | | +-is_descending=TRUE
               | +-OrderByItem
-              |   +-parse_location=81-107
               |   +-column_ref=
               |   | +-ColumnRef(type=INT32, column=$orderby.$orderbycol2#4)
               |   +-is_descending=TRUE
@@ -2257,7 +1773,6 @@ QueryStmt
               +-ColumnRef(type=ARRAY, column=$subquery1.arr1#1)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=38-42
                   +-column_ref=
                     +-ColumnRef(type=ARRAY, column=$subquery1.arr2#2)
 ==
@@ -2311,7 +1826,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=47-51
                   +-column_ref=
                     +-ColumnRef(type=ARRAY, column=$subquery1.arr1#1)
 ==
@@ -2370,7 +1884,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=43-50
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -2415,7 +1928,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=73-98
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -2469,7 +1981,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=128-206
                   +-column_ref=
                     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -2509,7 +2020,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=84-106
                   +-column_ref=
                     +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
 --
@@ -2556,7 +2066,6 @@ QueryStmt
               +-distinct=TRUE
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=88-114
                   +-column_ref=
                     +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#4)
 ==
@@ -2604,7 +2113,6 @@ QueryStmt
               +-ColumnRef(type=INT32, column=TestTable.key#1)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=30-45
                   +-column_ref=
                   | +-ColumnRef(type=INT32, column=TestTable.key#1)
                   +-null_order=NULLS_FIRST
@@ -2628,7 +2136,6 @@ QueryStmt
               +-ColumnRef(type=INT32, column=TestTable.key#1)
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=30-44
                   +-column_ref=
                   | +-ColumnRef(type=INT32, column=TestTable.key#1)
                   +-null_order=NULLS_LAST
diff --git a/zetasql/analyzer/testdata/order_preservation.test b/zetasql/analyzer/testdata/order_preservation.test
index b2ae9b88d..428bb398b 100644
--- a/zetasql/analyzer/testdata/order_preservation.test
+++ b/zetasql/analyzer/testdata/order_preservation.test
@@ -27,7 +27,6 @@ QueryStmt
         | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=44-47
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -56,7 +55,6 @@ QueryStmt
         | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=68-71
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -86,7 +84,6 @@ QueryStmt
         |     +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=46-49
             +-column_ref=
               +-ColumnRef(type=INT32, column=TestTable.key#1)
 ==
@@ -109,7 +106,6 @@ QueryStmt
     |   | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=35-38
     |       +-column_ref=
     |         +-ColumnRef(type=INT32, column=TestTable.key#1)
     +-limit=
@@ -138,7 +134,6 @@ QueryStmt
         |   | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=50-53
         |       +-column_ref=
         |         +-ColumnRef(type=INT32, column=TestTable.key#1)
         +-limit=
@@ -183,7 +178,6 @@ QueryStmt
             |   | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
             |   +-order_by_item_list=
             |     +-OrderByItem
-            |       +-parse_location=60-63
             |       +-column_ref=
             |         +-ColumnRef(type=INT32, column=TestTable.key#1)
             +-limit=
@@ -228,7 +222,6 @@ QueryStmt
         |   |     +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=49-52
         |       +-column_ref=
         |         +-ColumnRef(type=INT32, column=TestTable.key#1)
         +-limit=
@@ -269,7 +262,6 @@ QueryStmt
         |   |     +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=56-59
         |       +-column_ref=
         |         +-ColumnRef(type=INT32, column=TestTable.key#1)
         +-limit=
@@ -304,7 +296,6 @@ QueryStmt
     | |       | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     | |       +-order_by_item_list=
     | |         +-OrderByItem
-    | |           +-parse_location=45-48
     | |           +-column_ref=
     | |             +-ColumnRef(type=INT32, column=TestTable.key#1)
     | +-$col2#14 :=
@@ -319,7 +310,6 @@ QueryStmt
     | |       | +-TableScan(column_list=[TestTable.key#4], table=TestTable, column_index_list=[0])
     | |       +-order_by_item_list=
     | |         +-OrderByItem
-    | |           +-parse_location=94-97
     | |           +-column_ref=
     | |             +-ColumnRef(type=INT32, column=TestTable.key#4)
     | +-$col3#15 :=
@@ -333,7 +323,6 @@ QueryStmt
     | |       | +-TableScan(column_list=[TestTable.key#7], table=TestTable, column_index_list=[0])
     | |       +-order_by_item_list=
     | |         +-OrderByItem
-    | |           +-parse_location=144-147
     | |           +-column_ref=
     | |             +-ColumnRef(type=INT32, column=TestTable.key#7)
     | +-$col4#16 :=
@@ -349,7 +338,6 @@ QueryStmt
     |         | +-TableScan(column_list=[TestTable.key#10], table=TestTable, column_index_list=[0])
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=193-196
     |             +-column_ref=
     |               +-ColumnRef(type=INT32, column=TestTable.key#10)
     +-input_scan=
@@ -377,7 +365,6 @@ QueryStmt
     |       | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     |       +-order_by_item_list=
     |         +-OrderByItem
-    |           +-parse_location=46-49
     |           +-column_ref=
     |             +-ColumnRef(type=INT32, column=TestTable.key#1)
     +-query=
@@ -410,7 +397,6 @@ QueryStmt
     | |     | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     | |     +-order_by_item_list=
     | |       +-OrderByItem
-    | |         +-parse_location=47-50
     | |         +-column_ref=
     | |           +-ColumnRef(type=INT32, column=TestTable.key#1)
     | +-WithEntry
@@ -428,7 +414,6 @@ QueryStmt
         | +-WithRefScan(column_list=[T2.key#5], with_query_name="T2")
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=109-115
             +-column_ref=
             | +-ColumnRef(type=INT32, column=T2.key#5)
             +-is_descending=TRUE
@@ -478,7 +463,6 @@ QueryStmt
         |   |     +-ColumnHolder(column=$array_offset.y#2)
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=81-82
         |       +-column_ref=
         |         +-ColumnRef(type=INT64, column=$array_offset.y#2)
         +-aggregate_list=
@@ -512,7 +496,6 @@ QueryStmt
         |       | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
         |       +-order_by_item_list=
         |         +-OrderByItem
-        |           +-parse_location=62-65
         |           +-column_ref=
         |             +-ColumnRef(type=INT32, column=TestTable.key#1)
         +-element_column_list=[$array.$unnest1#4]
diff --git a/zetasql/analyzer/testdata/orderby.test b/zetasql/analyzer/testdata/orderby.test
index ad89c3321..c40886053 100644
--- a/zetasql/analyzer/testdata/orderby.test
+++ b/zetasql/analyzer/testdata/orderby.test
@@ -20,16 +20,13 @@ QueryStmt
     |     +-TableScan(table=KeyValue)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=38-44
       | +-column_ref=
       | | +-ColumnRef(type=INT64, column=$query.$col2#4)
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=46-47
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$query.$col3#5)
       +-OrderByItem
-        +-parse_location=49-54
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.$col1#3)
 ==
@@ -47,7 +44,6 @@ QueryStmt
     | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=35-36
         +-column_ref=
           +-ColumnRef(type=INT32, column=TestTable.key#1)
 ==
@@ -67,7 +63,6 @@ QueryStmt
     | +-TableScan(column_list=TestTable.[key#1, TestEnum#2, KitchenSink#3], table=TestTable, column_index_list=[0, 1, 2])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=33-34
         +-column_ref=
           +-ColumnRef(type=INT32, column=TestTable.key#1)
 ==
@@ -90,7 +85,6 @@ QueryStmt
     |     +-key#4 := ColumnRef(type=INT32, column=TestTable.key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=48-49
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.key#4)
 ==
@@ -125,7 +119,6 @@ QueryStmt
     |       +-Literal(type=INT64, value=5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=74-75
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.key#4)
 ==
@@ -160,7 +153,6 @@ QueryStmt
     |       +-Literal(type=INT64, value=5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=74-75
         +-column_ref=
           +-ColumnRef(type=STRING, column=$aggregate.$agg1#3)
 ==
@@ -191,7 +183,6 @@ QueryStmt
         | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=51-52
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -214,7 +205,6 @@ QueryStmt
         | +-TableScan(column_list=TestTable.[key#1, KitchenSink#3], table=TestTable, column_index_list=[0, 2])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=63-64
             +-column_ref=
               +-ColumnRef(type=INT32, column=TestTable.key#1)
 ==
@@ -234,11 +224,9 @@ QueryStmt
     | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=41-42
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
       +-OrderByItem
-        +-parse_location=44-45
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -258,27 +246,21 @@ QueryStmt
     | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=41-42
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
       +-OrderByItem
-      | +-parse_location=44-45
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-      | +-parse_location=47-48
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
       +-OrderByItem
-      | +-parse_location=50-51
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-      | +-parse_location=53-54
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-        +-parse_location=56-57
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -316,60 +298,46 @@ QueryStmt
     | +-TableScan(column_list=SimpleTypes.[int32#1, int64#2, uint32#3, uint64#4, string#5, bytes#6, bool#7, float#8, double#9, date#10, timestamp_seconds#11, timestamp_millis#12, timestamp_micros#13, timestamp_nanos#14, timestamp#15, numeric#16, bignumeric#17, json#18, uuid#19], table=SimpleTypes, column_index_list=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=35-36
       | +-column_ref=
       |   +-ColumnRef(type=INT32, column=SimpleTypes.int32#1)
       +-OrderByItem
-      | +-parse_location=38-39
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
       +-OrderByItem
-      | +-parse_location=41-46
       | +-column_ref=
       |   +-ColumnRef(type=UINT32, column=SimpleTypes.uint32#3)
       +-OrderByItem
-      | +-parse_location=48-54
       | +-column_ref=
       | | +-ColumnRef(type=UINT64, column=SimpleTypes.uint64#4)
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=56-57
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=SimpleTypes.string#5)
       +-OrderByItem
-      | +-parse_location=59-60
       | +-column_ref=
       |   +-ColumnRef(type=BYTES, column=SimpleTypes.bytes#6)
       +-OrderByItem
-      | +-parse_location=62-63
       | +-column_ref=
       |   +-ColumnRef(type=BOOL, column=SimpleTypes.bool#7)
       +-OrderByItem
-      | +-parse_location=65-66
       | +-column_ref=
       |   +-ColumnRef(type=FLOAT, column=SimpleTypes.float#8)
       +-OrderByItem
-      | +-parse_location=68-69
       | +-column_ref=
       |   +-ColumnRef(type=DOUBLE, column=SimpleTypes.double#9)
       +-OrderByItem
-      | +-parse_location=71-73
       | +-column_ref=
       |   +-ColumnRef(type=DATE, column=SimpleTypes.date#10)
       +-OrderByItem
-      | +-parse_location=75-77
       | +-column_ref=
       |   +-ColumnRef(type=TIMESTAMP, column=SimpleTypes.timestamp_seconds#11)
       +-OrderByItem
-      | +-parse_location=79-81
       | +-column_ref=
       |   +-ColumnRef(type=TIMESTAMP, column=SimpleTypes.timestamp_millis#12)
       +-OrderByItem
-      | +-parse_location=83-85
       | +-column_ref=
       |   +-ColumnRef(type=TIMESTAMP, column=SimpleTypes.timestamp_micros#13)
       +-OrderByItem
-        +-parse_location=87-89
         +-column_ref=
           +-ColumnRef(type=TIMESTAMP, column=SimpleTypes.timestamp_nanos#14)
 ==
@@ -389,7 +357,6 @@ QueryStmt
     | +-TableScan(column_list=[ComplexTypes.TestEnum#2], table=ComplexTypes, column_index_list=[1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=43-44
         +-column_ref=
           +-ColumnRef(type=ENUM, column=ComplexTypes.TestEnum#2)
 ==
@@ -429,7 +396,6 @@ QueryStmt
     |       +-output_column_list=KeyValue.[Key#3, Value#4]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=83-84
         +-column_ref=
           +-ColumnRef(type=STRING, column=$union_all.value#6)
 ==
@@ -476,7 +442,6 @@ QueryStmt
     |     | |   |     +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     |     | |   +-order_by_item_list=
     |     | |     +-OrderByItem
-    |     | |       +-parse_location=50-51
     |     | |       +-column_ref=
     |     | |         +-ColumnRef(type=INT32, column=TestTable.key#1)
     |     | +-output_column_list=[TestTable.key#1, $union_all1.$col2#4, $union_all1.$col3#5]
@@ -507,7 +472,6 @@ QueryStmt
     |       |   |   |     +-TableScan(column_list=[TestTable.key#6], table=TestTable, column_index_list=[0])
     |       |   |   +-order_by_item_list=
     |       |   |     +-OrderByItem
-    |       |   |       +-parse_location=113-114
     |       |   |       +-column_ref=
     |       |   |         +-ColumnRef(type=INT64, column=$union_all2.$col2#9)
     |       |   +-limit=
@@ -517,7 +481,6 @@ QueryStmt
     |       +-output_column_list=[TestTable.key#6, $union_all2.$col2#9, $union_all2.$col3#10]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=142-143
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_all.$col3#13)
 ==
@@ -606,7 +569,6 @@ QueryStmt
     | +-TableScan(column_list=[ComplexTypes.Int32Array#4], table=ComplexTypes, column_index_list=[3])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=45-46
         +-column_ref=
           +-ColumnRef(type=ARRAY, column=ComplexTypes.Int32Array#4)
 ==
@@ -722,7 +684,6 @@ QueryStmt
     |     +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=32-36
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#3)
 ==
@@ -742,7 +703,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-37
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -782,7 +742,6 @@ QueryStmt
     |         +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=38-45
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -802,7 +761,6 @@ QueryStmt
     | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-39
         +-column_ref=
           +-ColumnRef(type=STRING, column=KeyValue.Value#2)
 ==
@@ -837,47 +795,36 @@ QueryStmt
     |     +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=32-35
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-      | +-parse_location=37-42
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
       +-OrderByItem
-      | +-parse_location=44-45
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-      | +-parse_location=47-48
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
       +-OrderByItem
-      | +-parse_location=50-55
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
       +-OrderByItem
-      | +-parse_location=57-58
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-      | +-parse_location=60-63
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-      | +-parse_location=65-66
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
       +-OrderByItem
-      | +-parse_location=68-75
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol9#3)
       +-OrderByItem
-      | +-parse_location=77-78
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-        +-parse_location=80-87
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol11#4)
 ==
@@ -933,47 +880,36 @@ QueryStmt
     |           +-output_column_list=KeyValue.[Key#3, Value#4]
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=119-123
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$union_all.key1#5)
       +-OrderByItem
-      | +-parse_location=125-131
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$union_all.value1#6)
       +-OrderByItem
-      | +-parse_location=133-134
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$union_all.key1#5)
       +-OrderByItem
-      | +-parse_location=136-137
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$union_all.value1#6)
       +-OrderByItem
-      | +-parse_location=139-145
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$union_all.value1#6)
       +-OrderByItem
-      | +-parse_location=147-148
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$union_all.key1#5)
       +-OrderByItem
-      | +-parse_location=150-154
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$union_all.key1#5)
       +-OrderByItem
-      | +-parse_location=156-157
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$union_all.value1#6)
       +-OrderByItem
-      | +-parse_location=159-167
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol9#7)
       +-OrderByItem
-      | +-parse_location=169-170
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$union_all.key1#5)
       +-OrderByItem
-        +-parse_location=172-180
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol11#8)
 ==
@@ -1002,7 +938,6 @@ QueryStmt
     |     +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=32-39
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#3)
 ==
@@ -1028,27 +963,21 @@ QueryStmt
     | +-TableScan(column_list=SimpleTypes.[int32#1, int64#2, uint32#3, uint64#4, float#8, double#9], table=SimpleTypes, column_index_list=[0, 1, 2, 3, 7, 8])
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=77-82
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
       +-OrderByItem
-      | +-parse_location=84-90
       | +-column_ref=
       |   +-ColumnRef(type=UINT64, column=SimpleTypes.uint64#4)
       +-OrderByItem
-      | +-parse_location=92-98
       | +-column_ref=
       |   +-ColumnRef(type=DOUBLE, column=SimpleTypes.double#9)
       +-OrderByItem
-      | +-parse_location=100-101
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=SimpleTypes.int64#2)
       +-OrderByItem
-      | +-parse_location=103-104
       | +-column_ref=
       |   +-ColumnRef(type=UINT64, column=SimpleTypes.uint64#4)
       +-OrderByItem
-        +-parse_location=106-107
         +-column_ref=
           +-ColumnRef(type=DOUBLE, column=SimpleTypes.double#9)
 ==
@@ -1067,7 +996,6 @@ QueryStmt
     | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=42-45
         +-column_ref=
           +-ColumnRef(type=INT32, column=TestTable.key#1)
 ==
@@ -1087,7 +1015,6 @@ QueryStmt
     | +-TableScan(column_list=[TestTable.key#1], table=TestTable, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=42-45
         +-column_ref=
           +-ColumnRef(type=INT32, column=TestTable.key#1)
 ==
@@ -1114,7 +1041,6 @@ QueryStmt
     |     +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=41-48
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#3)
 ==
@@ -1149,7 +1075,6 @@ QueryStmt
     |         +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=45-54
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -1174,7 +1099,6 @@ QueryStmt
     |     +-key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=47-50
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.key#3)
 ==
@@ -1207,7 +1131,6 @@ QueryStmt
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=61-69
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg2#5)
 ==
@@ -1237,7 +1160,6 @@ QueryStmt
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=51-59
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg1#4)
 ==
@@ -1272,47 +1194,36 @@ QueryStmt
     |     +-value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=73-76
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.foo#3)
       +-OrderByItem
-      | +-parse_location=78-81
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.foo#3)
       +-OrderByItem
-      | +-parse_location=83-84
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.foo#3)
       +-OrderByItem
-      | +-parse_location=86-87
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$groupby.value#4)
       +-OrderByItem
-      | +-parse_location=89-94
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$groupby.value#4)
       +-OrderByItem
-      | +-parse_location=96-99
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.foo#3)
       +-OrderByItem
-      | +-parse_location=101-104
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.foo#3)
       +-OrderByItem
-      | +-parse_location=106-109
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.foo#3)
       +-OrderByItem
-      | +-parse_location=111-112
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$groupby.value#4)
       +-OrderByItem
-      | +-parse_location=114-126
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.foo#3)
       +-OrderByItem
-        +-parse_location=128-142
         +-column_ref=
           +-ColumnRef(type=STRING, column=$groupby.value#4)
 ==
@@ -1350,7 +1261,6 @@ QueryStmt
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=54-62
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg1#4)
 ==
@@ -1384,32 +1294,25 @@ QueryStmt
     |   |     +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |     | +-parse_location=62-65
     |     | +-column_ref=
     |     |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     |     +-OrderByItem
-    |     | +-parse_location=67-68
     |     | +-column_ref=
     |     |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     |     +-OrderByItem
-    |     | +-parse_location=70-73
     |     | +-column_ref=
     |     |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     |     +-OrderByItem
-    |     | +-parse_location=75-76
     |     | +-column_ref=
     |     |   +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     |     +-OrderByItem
-    |       +-parse_location=78-87
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$orderby.$orderbycol5#3)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=99-100
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=KeyValue.Key#1)
       +-OrderByItem
-        +-parse_location=102-103
         +-column_ref=
           +-ColumnRef(type=STRING, column=KeyValue.Value#2)
 ==
@@ -1434,7 +1337,6 @@ QueryStmt
     |     +-Key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=43-44
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.Key#3)
 ==
@@ -1458,7 +1360,6 @@ QueryStmt
     |     +-Key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=43-46
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.Key#3)
 ==
@@ -1488,7 +1389,6 @@ QueryStmt
     |     +-key#4 := ColumnRef(type=INT64, column=$groupby.key#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=56-59
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.key#4)
 ==
@@ -1524,11 +1424,9 @@ QueryStmt
     |     +-maxvalue#6 := ColumnRef(type=STRING, column=$aggregate.maxvalue#3)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=80-83
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.key#5)
       +-OrderByItem
-        +-parse_location=85-86
         +-column_ref=
           +-ColumnRef(type=STRING, column=$distinct.maxvalue#6)
 ==
@@ -1564,15 +1462,12 @@ QueryStmt
     |     +-maxvalue#6 := ColumnRef(type=STRING, column=$aggregate.maxvalue#3)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=80-83
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.key#5)
       +-OrderByItem
-      | +-parse_location=85-86
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$distinct.maxvalue#6)
       +-OrderByItem
-        +-parse_location=88-96
         +-column_ref=
           +-ColumnRef(type=STRING, column=$distinct.maxvalue#6)
 ==
@@ -1612,11 +1507,9 @@ QueryStmt
     |     +-rankvalue#6 := ColumnRef(type=INT64, column=$analytic.rankvalue#4)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=86-91
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$distinct.Value#5)
       +-OrderByItem
-        +-parse_location=93-94
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.rankvalue#6)
 ==
@@ -1656,11 +1549,9 @@ QueryStmt
     |     +-rankvalue#6 := ColumnRef(type=INT64, column=$analytic.rankvalue#4)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=86-91
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$distinct.Value#5)
       +-OrderByItem
-        +-parse_location=93-102
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.rankvalue#6)
 ==
@@ -1741,7 +1632,6 @@ QueryStmt
     |         +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=48-58
         +-column_ref=
           +-ColumnRef(type=STRING, column=$aggregate.$agg1#4)
 --
@@ -1779,7 +1669,6 @@ QueryStmt
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=48-56
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg1#4)
 --
@@ -1830,7 +1719,6 @@ QueryStmt
     |       +-output_column_list=[$distinct.Key#6]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=87-90
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_all.key#7)
 ==
@@ -1870,7 +1758,6 @@ QueryStmt
     |     | |   |     +-Key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     |     | |   +-order_by_item_list=
     |     | |     +-OrderByItem
-    |     | |       +-parse_location=46-49
     |     | |       +-column_ref=
     |     | |         +-ColumnRef(type=INT64, column=$distinct.Key#3)
     |     | +-output_column_list=[$distinct.Key#3]
@@ -1888,13 +1775,11 @@ QueryStmt
     |       |   |     +-Key#6 := ColumnRef(type=INT64, column=KeyValue.Key#4)
     |       |   +-order_by_item_list=
     |       |     +-OrderByItem
-    |       |       +-parse_location=107-110
     |       |       +-column_ref=
     |       |         +-ColumnRef(type=INT64, column=$distinct.Key#6)
     |       +-output_column_list=[$distinct.Key#6]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=121-124
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_all.key#7)
 ==
@@ -1938,7 +1823,6 @@ QueryStmt
     |     | |   |     +-Key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     |     | |   +-order_by_item_list=
     |     | |     +-OrderByItem
-    |     | |       +-parse_location=46-49
     |     | |       +-column_ref=
     |     | |         +-ColumnRef(type=INT64, column=$distinct.Key#3)
     |     | +-output_column_list=[$distinct.Key#3]
@@ -1951,7 +1835,6 @@ QueryStmt
     |     | |   | +-TableScan(column_list=[KeyValue.Key#4], table=KeyValue, column_index_list=[0])
     |     | |   +-order_by_item_list=
     |     | |     +-OrderByItem
-    |     | |       +-parse_location=103-106
     |     | |       +-column_ref=
     |     | |         +-ColumnRef(type=INT64, column=KeyValue.Key#4)
     |     | +-output_column_list=[KeyValue.Key#4]
@@ -1969,13 +1852,11 @@ QueryStmt
     |       |   |     +-Key#8 := ColumnRef(type=INT64, column=KeyValue.Key#6)
     |       |   +-order_by_item_list=
     |       |     +-OrderByItem
-    |       |       +-parse_location=169-172
     |       |       +-column_ref=
     |       |         +-ColumnRef(type=INT64, column=$distinct.Key#8)
     |       +-output_column_list=[$distinct.Key#8]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=183-186
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_distinct.key#9)
 ==
@@ -2027,11 +1908,9 @@ QueryStmt
     |         +-Key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=43-48
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
       +-OrderByItem
-        +-parse_location=50-51
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.Key#3)
 ==
@@ -2069,7 +1948,6 @@ QueryStmt
     |         +-key#4 := ColumnRef(type=INT64, column=$groupby.key#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=59-64
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
 ==
@@ -2107,7 +1985,6 @@ QueryStmt
     |         +-key#4 := ColumnRef(type=INT64, column=$groupby.key#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=56-63
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
 ==
@@ -2137,11 +2014,9 @@ QueryStmt
         |     +-Value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
         +-order_by_item_list=
           +-OrderByItem
-          | +-parse_location=77-80
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
           +-OrderByItem
-            +-parse_location=82-87
             +-column_ref=
               +-ColumnRef(type=STRING, column=$distinct.Value#4)
 ==
@@ -2168,19 +2043,15 @@ QueryStmt
     |     +-Value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=41-42
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
       +-OrderByItem
-      | +-parse_location=44-45
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$distinct.Value#4)
       +-OrderByItem
-      | +-parse_location=47-50
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
       +-OrderByItem
-        +-parse_location=52-57
         +-column_ref=
           +-ColumnRef(type=STRING, column=$distinct.Value#4)
 ==
@@ -2218,15 +2089,12 @@ QueryStmt
         |     +-Value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
         +-order_by_item_list=
           +-OrderByItem
-          | +-parse_location=60-61
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
           +-OrderByItem
-          | +-parse_location=63-66
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
           +-OrderByItem
-            +-parse_location=68-73
             +-column_ref=
               +-ColumnRef(type=STRING, column=$distinct.Value#4)
 ==
@@ -2252,15 +2120,12 @@ QueryStmt
     |     +-Key#3 := ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=52-53
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
       +-OrderByItem
-      | +-parse_location=55-58
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
       +-OrderByItem
-        +-parse_location=60-72
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.Key#3)
 ==
@@ -2302,23 +2167,18 @@ QueryStmt
         |     +-Value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
         +-order_by_item_list=
           +-OrderByItem
-          | +-parse_location=85-86
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
           +-OrderByItem
-          | +-parse_location=88-91
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
           +-OrderByItem
-          | +-parse_location=93-98
           | +-column_ref=
           |   +-ColumnRef(type=STRING, column=$distinct.Value#4)
           +-OrderByItem
-          | +-parse_location=100-112
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$distinct.Key#3)
           +-OrderByItem
-            +-parse_location=114-128
             +-column_ref=
               +-ColumnRef(type=STRING, column=$distinct.Value#4)
 ==
@@ -2360,7 +2220,6 @@ QueryStmt
     |     +-nested_int64#3 := ColumnRef(type=INT64, column=$query.nested_int64#2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=78-103
         +-column_ref=
           +-ColumnRef(type=INT64, column=$distinct.nested_int64#3)
 ==
@@ -2416,7 +2275,6 @@ QueryStmt
     |         +-nested_int64#4 := ColumnRef(type=INT64, column=$groupby.nested_int64#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=113-142
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#5)
 ==
@@ -2456,7 +2314,6 @@ QueryStmt
     |       +-output_column_list=[KeyValue.Key#3]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=69-72
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_all.key#5)
 ==
@@ -2499,7 +2356,6 @@ QueryStmt
     |       +-output_column_list=[KeyValue.Key#4]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=78-81
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_all.key#6)
 ==
@@ -2537,7 +2393,6 @@ QueryStmt
     |       +-output_column_list=[KeyValue.Key#3]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=85-89
         +-column_ref=
           +-ColumnRef(type=INT64, column=$union_all.key1#5)
 ==
@@ -2605,7 +2460,6 @@ QueryStmt
     |           +-output_column_list=[KeyValue.Key#3]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=69-78
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
 ==
@@ -2661,7 +2515,6 @@ QueryStmt
     |           +-output_column_list=[KeyValue.Key#3]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=69-86
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#6)
 ==
@@ -2703,7 +2556,6 @@ QueryStmt
     |         +-TableScan(table=KeyValue)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-66
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
 ==
@@ -2738,7 +2590,6 @@ QueryStmt
     |     +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-51
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#3)
 ==
@@ -2762,7 +2613,6 @@ QueryStmt
     |     +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-50
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#3)
 ==
@@ -2811,7 +2661,6 @@ QueryStmt
     |         |         +-TableScan(column_list=[TestTable.KitchenSink#5], table=TestTable, column_index_list=[2])
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=74-86
     |             +-column_ref=
     |               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#7)
     +-input_scan=
@@ -2869,7 +2718,6 @@ QueryStmt
     |         |         +-TableScan(column_list=[TestTable.KitchenSink#5], table=TestTable, column_index_list=[2])
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=74-110
     |             +-column_ref=
     |               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#7)
     +-input_scan=
@@ -2937,7 +2785,6 @@ QueryStmt
     |         |           +-output_column_list=[$union_all2.$col1#7]
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=101-113
     |             +-column_ref=
     |               +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#9)
     +-input_scan=
@@ -2949,8 +2796,6 @@ select int32 as foo
 from SimpleTypes
 order by {{foo|1|int32}}
 --
-ALTERNATION GROUP: foo
---
 QueryStmt
 +-output_column_list=
 | +-SimpleTypes.int32#1 AS foo [INT32]
@@ -2962,41 +2807,6 @@ QueryStmt
     | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=46-49
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=SimpleTypes.int32#1)
---
-ALTERNATION GROUP: 1
---
-QueryStmt
-+-output_column_list=
-| +-SimpleTypes.int32#1 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[SimpleTypes.int32#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=46-47
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=SimpleTypes.int32#1)
---
-ALTERNATION GROUP: int32
---
-QueryStmt
-+-output_column_list=
-| +-SimpleTypes.int32#1 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[SimpleTypes.int32#1]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=46-51
         +-column_ref=
           +-ColumnRef(type=INT32, column=SimpleTypes.int32#1)
 ==
@@ -3009,95 +2819,13 @@ from SimpleTypes
 group by {{foo|1|int32}}
 order by {{foo|1|int32}}
 --
-ALTERNATION GROUP: foo,foo
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.foo#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.foo#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.foo#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=59-62
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.foo#20)
---
-ALTERNATION GROUP: foo,1
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.foo#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.foo#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.foo#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=59-60
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.foo#20)
---
-ALTERNATION GROUP: foo,int32
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.foo#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.foo#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.foo#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=59-64
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.foo#20)
---
-ALTERNATION GROUP: 1,foo
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.foo#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.foo#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.foo#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=57-60
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.foo#20)
---
-ALTERNATION GROUP: 1,1
+ALTERNATION GROUPS:
+    foo,foo
+    foo,1
+    foo,int32
+    1,foo
+    1,1
+    1,int32
 --
 QueryStmt
 +-output_column_list=
@@ -3115,33 +2843,13 @@ QueryStmt
     |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=57-58
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.foo#20)
 --
-ALTERNATION GROUP: 1,int32
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.foo#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.foo#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.foo#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=57-62
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.foo#20)
---
-ALTERNATION GROUP: int32,foo
+ALTERNATION GROUPS:
+    int32,foo
+    int32,1
+    int32,int32
 --
 QueryStmt
 +-output_column_list=
@@ -3159,51 +2867,6 @@ QueryStmt
     |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=61-64
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32#20)
---
-ALTERNATION GROUP: int32,1
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.int32#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.int32#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.int32#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=61-62
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32#20)
---
-ALTERNATION GROUP: int32,int32
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.int32#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.int32#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.int32#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=61-66
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.int32#20)
 ==
@@ -3213,30 +2876,6 @@ select distinct int32 as foo
 from SimpleTypes
 order by {{foo|1|int32}}
 --
-ALTERNATION GROUP: foo
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.int32#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.int32#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.int32#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=55-58
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.int32#20)
---
-ALTERNATION GROUP: 1
---
 QueryStmt
 +-output_column_list=
 | +-$distinct.int32#20 AS foo [INT32]
@@ -3253,29 +2892,6 @@ QueryStmt
     |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=55-56
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.int32#20)
---
-ALTERNATION GROUP: int32
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.int32#20 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.int32#20]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.int32#20]
-    |   +-input_scan=
-    |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   +-group_by_list=
-    |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=55-60
         +-column_ref=
           +-ColumnRef(type=INT32, column=$distinct.int32#20)
 ==
@@ -3288,7 +2904,13 @@ from SimpleTypes
 group by {{foo|1|int32}}
 order by {{foo|1|int32}}
 --
-ALTERNATION GROUP: foo,foo
+ALTERNATION GROUPS:
+    foo,foo
+    foo,1
+    foo,int32
+    1,foo
+    1,1
+    1,int32
 --
 QueryStmt
 +-output_column_list=
@@ -3311,200 +2933,13 @@ QueryStmt
     |     +-foo#21 := ColumnRef(type=INT32, column=$groupby.foo#20)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=68-71
         +-column_ref=
           +-ColumnRef(type=INT32, column=$distinct.foo#21)
 --
-ALTERNATION GROUP: foo,1
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.foo#21 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.foo#21]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.foo#21]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.foo#20]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   |   +-group_by_list=
-    |   |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    |   +-group_by_list=
-    |     +-foo#21 := ColumnRef(type=INT32, column=$groupby.foo#20)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=68-69
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.foo#21)
---
-ALTERNATION GROUP: foo,int32
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.foo#21 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.foo#21]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.foo#21]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.foo#20]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   |   +-group_by_list=
-    |   |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    |   +-group_by_list=
-    |     +-foo#21 := ColumnRef(type=INT32, column=$groupby.foo#20)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=68-73
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.foo#21)
---
-ALTERNATION GROUP: 1,foo
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.foo#21 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.foo#21]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.foo#21]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.foo#20]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   |   +-group_by_list=
-    |   |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    |   +-group_by_list=
-    |     +-foo#21 := ColumnRef(type=INT32, column=$groupby.foo#20)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=66-69
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.foo#21)
---
-ALTERNATION GROUP: 1,1
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.foo#21 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.foo#21]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.foo#21]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.foo#20]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   |   +-group_by_list=
-    |   |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    |   +-group_by_list=
-    |     +-foo#21 := ColumnRef(type=INT32, column=$groupby.foo#20)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=66-67
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.foo#21)
---
-ALTERNATION GROUP: 1,int32
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.foo#21 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.foo#21]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.foo#21]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.foo#20]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   |   +-group_by_list=
-    |   |     +-foo#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    |   +-group_by_list=
-    |     +-foo#21 := ColumnRef(type=INT32, column=$groupby.foo#20)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=66-71
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.foo#21)
---
-ALTERNATION GROUP: int32,foo
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.int32#21 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.int32#21]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.int32#21]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32#20]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   |   +-group_by_list=
-    |   |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    |   +-group_by_list=
-    |     +-int32#21 := ColumnRef(type=INT32, column=$groupby.int32#20)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=70-73
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.int32#21)
---
-ALTERNATION GROUP: int32,1
---
-QueryStmt
-+-output_column_list=
-| +-$distinct.int32#21 AS foo [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$distinct.int32#21]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$distinct.int32#21]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32#20]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[SimpleTypes.int32#1], table=SimpleTypes, column_index_list=[0])
-    |   |   +-group_by_list=
-    |   |     +-int32#20 := ColumnRef(type=INT32, column=SimpleTypes.int32#1)
-    |   +-group_by_list=
-    |     +-int32#21 := ColumnRef(type=INT32, column=$groupby.int32#20)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=70-71
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$distinct.int32#21)
---
-ALTERNATION GROUP: int32,int32
+ALTERNATION GROUPS:
+    int32,foo
+    int32,1
+    int32,int32
 --
 QueryStmt
 +-output_column_list=
@@ -3527,7 +2962,6 @@ QueryStmt
     |     +-int32#21 := ColumnRef(type=INT32, column=$groupby.int32#20)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=70-75
         +-column_ref=
           +-ColumnRef(type=INT32, column=$distinct.int32#21)
 ==
@@ -3608,7 +3042,6 @@ QueryStmt
     |           +-field_idx=0
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=109-113
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.b#3)
 ==
@@ -3678,7 +3111,6 @@ QueryStmt
     |         +-TestStruct#7 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=65-94
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#8)
 ==
@@ -3729,7 +3161,6 @@ QueryStmt
     |         +-TestStruct#9 := ColumnRef(type=STRUCT>, column=ComplexTypes.TestStruct#5)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=99-114
         +-column_ref=
           +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#10)
 --
@@ -3768,7 +3199,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-49
         +-column_ref=
         | +-ColumnRef(type=INT64, column=KeyValue.Key#1)
         +-null_order=NULLS_FIRST
@@ -3786,7 +3216,6 @@ QueryStmt
     | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=34-48
         +-column_ref=
         | +-ColumnRef(type=INT64, column=KeyValue.Key#1)
         +-null_order=NULLS_LAST
diff --git a/zetasql/analyzer/testdata/pipe_aggregate_with_order.test b/zetasql/analyzer/testdata/pipe_aggregate_with_order.test
index 377d6ed61..bda54866f 100644
--- a/zetasql/analyzer/testdata/pipe_aggregate_with_order.test
+++ b/zetasql/analyzer/testdata/pipe_aggregate_with_order.test
@@ -50,27 +50,22 @@ QueryStmt
     |     +-$agg1#3 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=162-167
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$groupby.value#5)
       +-OrderByItem
-      | +-parse_location=180-185
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.value#5)
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=199-204
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.value#5)
       | +-null_order=NULLS_FIRST
       +-OrderByItem
-      | +-parse_location=229-234
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.value#5)
       | +-is_descending=TRUE
       | +-null_order=NULLS_LAST
       +-OrderByItem
-        +-parse_location=259-264
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.$groupbycol3#6)
 --
@@ -111,39 +106,31 @@ QueryStmt
     |     +-$agg1#3 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=145-148
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.key#4)
       +-OrderByItem
-      | +-parse_location=157-162
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$groupby.value#5)
       +-OrderByItem
-      | +-parse_location=171-176
       | +-column_ref=
       |   +-ColumnRef(type=STRING, column=$groupby.value#5)
       +-OrderByItem
-      | +-parse_location=189-194
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.value#5)
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=208-213
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.value#5)
       | +-null_order=NULLS_FIRST
       +-OrderByItem
-      | +-parse_location=238-243
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.value#5)
       | +-is_descending=TRUE
       | +-null_order=NULLS_LAST
       +-OrderByItem
-      | +-parse_location=268-273
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.$groupbycol3#6)
       +-OrderByItem
-        +-parse_location=286-291
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.$groupbycol4#7)
 ==
@@ -204,16 +191,13 @@ QueryStmt
     |     +-$agg1#4 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=84-87
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.$groupbycol3#7)
       +-OrderByItem
-      | +-parse_location=93-96
       | +-column_ref=
       | | +-ColumnRef(type=INT64, column=$groupby.$groupbycol4#8)
       | +-is_descending=TRUE
       +-OrderByItem
-        +-parse_location=103-106
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$groupby.$groupbycol5#9)
         +-is_descending=TRUE
@@ -268,24 +252,19 @@ QueryStmt
     |     +-$agg1#4 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=85-86
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.x#5)
       +-OrderByItem
-      | +-parse_location=88-91
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.$groupbycol2#6)
       +-OrderByItem
-      | +-parse_location=93-96
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.$groupbycol3#7)
       +-OrderByItem
-      | +-parse_location=102-105
       | +-column_ref=
       | | +-ColumnRef(type=INT64, column=$groupby.$groupbycol4#8)
       | +-is_descending=TRUE
       +-OrderByItem
-        +-parse_location=112-115
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$groupby.$groupbycol5#9)
         +-is_descending=TRUE
@@ -330,7 +309,6 @@ QueryStmt
     |     +-$agg1#3 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=58-61
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$groupby.key#4)
         +-null_order=NULLS_FIRST
@@ -356,7 +334,6 @@ QueryStmt
     |     +-$agg1#3 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=58-61
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$groupby.key#4)
         +-null_order=NULLS_LAST
@@ -622,21 +599,17 @@ QueryStmt
     |             +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=27-37
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$aggregate.$agg1#3)
       +-OrderByItem
-      | +-parse_location=56-68
       | +-column_ref=
       | | +-ColumnRef(type=INT64, column=$aggregate.$agg2#4)
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=88-100
       | +-column_ref=
       | | +-ColumnRef(type=INT64, column=$aggregate.$col3#8)
       | +-null_order=NULLS_FIRST
       +-OrderByItem
-        +-parse_location=131-139
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$aggregate.$agg4#6)
         +-is_descending=TRUE
@@ -682,16 +655,13 @@ QueryStmt
     |     +-$agg3#9 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=103-111
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.string_b#11)
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=34-49
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$aggregate.$agg1#7)
       +-OrderByItem
-        +-parse_location=69-77
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$aggregate.$agg3#9)
         +-is_descending=TRUE
@@ -727,20 +697,16 @@ QueryStmt
     |     +-$agg3#9 := AggregateFunctionCall(ZetaSQL:$count_star() -> INT64)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=105-110
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.int_a#10)
       +-OrderByItem
-      | +-parse_location=112-120
       | +-column_ref=
       | | +-ColumnRef(type=STRING, column=$groupby.string_b#11)
       | +-is_descending=TRUE
       +-OrderByItem
-      | +-parse_location=34-49
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$aggregate.$agg1#7)
       +-OrderByItem
-        +-parse_location=69-77
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$aggregate.$agg3#9)
         +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/pipe_call_tvf.test b/zetasql/analyzer/testdata/pipe_call_tvf.test
index c1cec316b..6262d3852 100644
--- a/zetasql/analyzer/testdata/pipe_call_tvf.test
+++ b/zetasql/analyzer/testdata/pipe_call_tvf.test
@@ -1401,7 +1401,6 @@ QueryStmt
         |   |   +-has_using=TRUE
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=111-114
         |       +-column_ref=
         |         +-ColumnRef(type=INT64, column=w1.Key#5)
         +-limit=
diff --git a/zetasql/analyzer/testdata/pipe_order_by.test b/zetasql/analyzer/testdata/pipe_order_by.test
index 54f7f2d86..dae5e4452 100644
--- a/zetasql/analyzer/testdata/pipe_order_by.test
+++ b/zetasql/analyzer/testdata/pipe_order_by.test
@@ -35,21 +35,17 @@ QueryStmt
     |   |         +-SingleRowScan
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |     | +-parse_location=28-29
     |     | +-column_ref=
     |     |   +-ColumnRef(type=INT64, column=$query.x#1)
     |     +-OrderByItem
-    |     | +-parse_location=31-37
     |     | +-column_ref=
     |     | | +-ColumnRef(type=INT64, column=$query.y#2)
     |     | +-is_descending=TRUE
     |     +-OrderByItem
-    |       +-parse_location=39-42
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$orderby.$orderbycol3#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=65-66
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.x#1)
 ==
@@ -81,14 +77,12 @@ QueryStmt
     |   |     +-SingleRowScan
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=32-47
     |       +-column_ref=
     |       | +-ColumnRef(type=STRING, column=$query.x#1)
     |       +-collation_name=
     |         +-Literal(type=STRING, value="abc")
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=60-77
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$query.y#2)
         +-is_descending=TRUE
@@ -122,20 +116,16 @@ QueryStmt
     |   |     +-SingleRowScan
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |     | +-parse_location=28-29
     |     | +-column_ref=
     |     |   +-ColumnRef(type=INT64, column=$query.x#1)
     |     +-OrderByItem
-    |       +-parse_location=31-32
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$query.y#2)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=45-46
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$query.y#2)
       +-OrderByItem
-        +-parse_location=48-54
         +-column_ref=
         | +-ColumnRef(type=INT64, column=$query.x#1)
         +-is_descending=TRUE
@@ -210,7 +200,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=UNBOUNDED FOLLOWING)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=28-42
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#3)
 ==
@@ -312,23 +301,18 @@ QueryStmt
     |                       +-WindowFrameExpr(boundary_type=UNBOUNDED FOLLOWING)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=33-34
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$query.x#1)
       +-OrderByItem
-      | +-parse_location=48-51
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol2#8)
       +-OrderByItem
-      | +-parse_location=65-81
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol3#9)
       +-OrderByItem
-      | +-parse_location=95-121
       | +-column_ref=
       |   +-ColumnRef(type=DOUBLE, column=$analytic.$analytic2#5)
       +-OrderByItem
-        +-parse_location=135-165
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic3#6)
 ==
@@ -410,7 +394,6 @@ QueryStmt
     |       |                   +-WindowFrameExpr(boundary_type=CURRENT ROW)
     |       +-order_by_item_list=
     |         +-OrderByItem
-    |           +-parse_location=37-79
     |           +-column_ref=
     |             +-ColumnRef(type=INT64, column=$analytic.$analytic1#4)
     +-filter_expr=
diff --git a/zetasql/analyzer/testdata/pipe_order_preservation.test b/zetasql/analyzer/testdata/pipe_order_preservation.test
index 1de27f164..1c3fd21df 100644
--- a/zetasql/analyzer/testdata/pipe_order_preservation.test
+++ b/zetasql/analyzer/testdata/pipe_order_preservation.test
@@ -19,7 +19,6 @@ QueryStmt
     |     +-SingleRowScan
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=67-68
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.x#1)
 ==
@@ -49,7 +48,6 @@ QueryStmt
     |   |     +-SingleRowScan
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=23-24
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$query.x#1)
     +-limit=
@@ -79,7 +77,6 @@ QueryStmt
     |   | +-TableScan(column_list=TestTable.[key#1, TestEnum#2, KitchenSink#3], table=TestTable, column_index_list=[0, 1, 2])
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=33-36
     |       +-column_ref=
     |         +-ColumnRef(type=INT32, column=TestTable.key#1)
     +-limit=
@@ -114,7 +111,6 @@ QueryStmt
         | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=26-29
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 --
@@ -142,7 +138,6 @@ QueryStmt
         | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=26-29
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 --
@@ -169,7 +164,6 @@ QueryStmt
         | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=26-29
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 --
@@ -186,7 +180,6 @@ QueryStmt
     | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=26-29
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 --
@@ -220,7 +213,6 @@ QueryStmt
         | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=26-29
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -253,7 +245,6 @@ QueryStmt
     |   |     +-SingleRowScan
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=23-24
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$query.x#1)
     +-filter_expr=
@@ -283,7 +274,6 @@ QueryStmt
     |   |     +-SingleRowScan
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=23-24
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$query.x#1)
     +-function_group_list=
@@ -320,7 +310,6 @@ QueryStmt
     |     +-SingleRowScan
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=23-24
         +-column_ref=
           +-ColumnRef(type=INT64, column=$query.x#1)
 ==
@@ -365,7 +354,6 @@ QueryStmt
         |           | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
         |           +-order_by_item_list=
         |             +-OrderByItem
-        |               +-parse_location=26-27
         |               +-column_ref=
         |                 +-ColumnRef(type=INT64, column=KeyValue.Key#1)
         +-function_group_list=
@@ -411,7 +399,6 @@ QueryStmt
     |           | +-TableScan(column_list=[KeyValue.Key#1], table=KeyValue, column_index_list=[0])
     |           +-order_by_item_list=
     |             +-OrderByItem
-    |               +-parse_location=26-27
     |               +-column_ref=
     |                 +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-function_group_list=
@@ -463,7 +450,6 @@ QueryStmt
         |       | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
         |       +-order_by_item_list=
         |         +-OrderByItem
-        |           +-parse_location=26-27
         |           +-column_ref=
         |             +-ColumnRef(type=INT64, column=KeyValue.Key#1)
         +-function_group_list=
@@ -494,7 +480,6 @@ QueryStmt
     | +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=32-35
         +-column_ref=
           +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
@@ -527,7 +512,6 @@ QueryStmt
     |         |     +-TableScan(column_list=[KeyValue.Key#2], table=KeyValue, column_index_list=[0])
     |         +-order_by_item_list=
     |           +-OrderByItem
-    |             +-parse_location=65-68
     |             +-column_ref=
     |               +-ColumnRef(type=INT64, column=KeyValue.Key#2)
     +-input_scan=
@@ -579,7 +563,6 @@ QueryStmt
     |                 | +-TableScan(column_list=KeyValue.[Key#2, Value#3], table=KeyValue, column_index_list=[0, 1])
     |                 +-order_by_item_list=
     |                   +-OrderByItem
-    |                     +-parse_location=51-54
     |                     +-column_ref=
     |                       +-ColumnRef(type=INT64, column=KeyValue.Key#2)
     +-input_scan=
diff --git a/zetasql/analyzer/testdata/pipe_parenthesized_query_alias.test b/zetasql/analyzer/testdata/pipe_parenthesized_query_alias.test
index 4ab2edf17..c758c2c6e 100644
--- a/zetasql/analyzer/testdata/pipe_parenthesized_query_alias.test
+++ b/zetasql/analyzer/testdata/pipe_parenthesized_query_alias.test
@@ -169,7 +169,6 @@ QueryStmt
         |   |     +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=40-43
         |       +-column_ref=
         |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
         +-limit=
diff --git a/zetasql/analyzer/testdata/pipe_query.test b/zetasql/analyzer/testdata/pipe_query.test
index b0bc5d9e9..3b1e27efb 100644
--- a/zetasql/analyzer/testdata/pipe_query.test
+++ b/zetasql/analyzer/testdata/pipe_query.test
@@ -445,7 +445,6 @@ QueryStmt
     |   |     +-SingleRowScan
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=36-37
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$subquery1.x#1)
     +-filter_expr=
diff --git a/zetasql/analyzer/testdata/pipe_select.test b/zetasql/analyzer/testdata/pipe_select.test
index 8106a561e..c2455db2f 100644
--- a/zetasql/analyzer/testdata/pipe_select.test
+++ b/zetasql/analyzer/testdata/pipe_select.test
@@ -737,7 +737,7 @@ QueryStmt
 --
 ALTERNATION GROUP: bad_name
 --
-ERROR: Field 1 has name bad_name which is not a field in proto zetasql_test__.KitchenSinkPB.Nested [at 4:39]
+ERROR: Protocol buffer zetasql_test__.KitchenSinkPB.Nested does not have a field named bad_name [at 4:39]
   nested_value: (SELECT 1 |> SELECT { bad_name: 5 })
                                       ^
 ==
diff --git a/zetasql/analyzer/testdata/pipe_static_describe.test b/zetasql/analyzer/testdata/pipe_static_describe.test
index fb63b81bd..886baf23f 100644
--- a/zetasql/analyzer/testdata/pipe_static_describe.test
+++ b/zetasql/analyzer/testdata/pipe_static_describe.test
@@ -253,7 +253,6 @@ QueryStmt
         |       +-Literal(type=INT64, value=10)
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=47-50
             +-column_ref=
               +-ColumnRef(type=INT64, column=KeyValue.Key#1)
 ==
diff --git a/zetasql/analyzer/testdata/pivot.test b/zetasql/analyzer/testdata/pivot.test
index 7a52bf798..2b5b07d62 100644
--- a/zetasql/analyzer/testdata/pivot.test
+++ b/zetasql/analyzer/testdata/pivot.test
@@ -5229,11 +5229,9 @@ QueryStmt
         |   +-null_handling_modifier=IGNORE_NULLS
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |     | +-parse_location=88-101
         |     | +-column_ref=
         |     |   +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
         |     +-OrderByItem
-        |       +-parse_location=103-109
         |       +-column_ref=
         |       | +-ColumnRef(type=INT64, column=$subquery1.z#3)
         |       +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/positional_query_params.test b/zetasql/analyzer/testdata/positional_query_params.test
index 82f4330e3..b2e4056bb 100644
--- a/zetasql/analyzer/testdata/positional_query_params.test
+++ b/zetasql/analyzer/testdata/positional_query_params.test
@@ -326,7 +326,6 @@ QueryStmt
     |   |         +-TableScan(table=KeyValue)
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=32-33
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
     +-limit=
@@ -514,7 +513,6 @@ QueryStmt
     |   |     +-value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=99-100
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$groupby.key#3)
     +-limit=
@@ -673,7 +671,6 @@ QueryStmt
     |   |         +-ColumnRef(type=ARRAY, column=$subquery1.b#4)
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=140-141
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$subquery1.c#5)
     +-limit=
diff --git a/zetasql/analyzer/testdata/proto_braced_constructors.test b/zetasql/analyzer/testdata/proto_braced_constructors.test
index ca2265829..cd8d44717 100644
--- a/zetasql/analyzer/testdata/proto_braced_constructors.test
+++ b/zetasql/analyzer/testdata/proto_braced_constructors.test
@@ -139,6 +139,16 @@ QueryStmt
       +-SingleRowScan
 ==
 
+# Nested values cannot (yet) be set using field paths.
+SELECT NEW zetasql_test__.KitchenSinkPB {
+  nested_value.nested_int64: 10
+}
+--
+ERROR: Braced constructor supports only singular field paths, nested paths should use additional sets of braces [at 2:3]
+  nested_value.nested_int64: 10
+  ^
+==
+
 # Nested repeated sub-message example.
 SELECT NEW zetasql_test__.KitchenSinkPB {
   int64_key_1: 1
@@ -246,6 +256,18 @@ QueryStmt
       +-SingleRowScan
 ==
 
+# Extension without parenthesis is an error.
+SELECT NEW zetasql_test__.TestExtraPB {
+     zetasql_test__.TestExtraPBExtensionHolder.test_extra_proto_extension {
+         ext_value: [1]
+     }
+}
+--
+ERROR: Protocol buffer zetasql_test__.TestExtraPB does not have a field named zetasql_test__ [at 2:6]
+     zetasql_test__.TestExtraPBExtensionHolder.test_extra_proto_extension {
+     ^
+==
+
 # Another example with extensions, this time with a regular field too.
 SELECT NEW zetasql_test__.TestExtraPB {
     int32_val1: 5,
@@ -387,7 +409,7 @@ ERROR: Scalar subquery cannot have more than one column unless using SELECT AS S
 # Invalid field test.
 SELECT NEW zetasql_test__.KitchenSinkPB {xxxxx: 5}
 --
-ERROR: Field 1 has name xxxxx which is not a field in proto zetasql_test__.KitchenSinkPB [at 1:42]
+ERROR: Protocol buffer zetasql_test__.KitchenSinkPB does not have a field named xxxxx [at 1:42]
 SELECT NEW zetasql_test__.KitchenSinkPB {xxxxx: 5}
                                          ^
 ==
@@ -1180,7 +1202,7 @@ SELECT NEW zetasql_test__.KitchenSinkPB {
   nested_value: (SELECT { nested: 5 })
 }
 --
-ERROR: Field 1 has name nested which is not a field in proto zetasql_test__.KitchenSinkPB.Nested [at 4:27]
+ERROR: Protocol buffer zetasql_test__.KitchenSinkPB.Nested does not have a field named nested [at 4:27]
   nested_value: (SELECT { nested: 5 })
                           ^
 ==
@@ -1734,7 +1756,6 @@ QueryStmt
         |       +-column_index_list=[0]
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=156-176
             +-column_ref=
               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#5)
 
diff --git a/zetasql/analyzer/testdata/query_params.test b/zetasql/analyzer/testdata/query_params.test
index b7a06a44d..bead8b28c 100644
--- a/zetasql/analyzer/testdata/query_params.test
+++ b/zetasql/analyzer/testdata/query_params.test
@@ -249,7 +249,6 @@ QueryStmt
     |   |     +-value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=55-56
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$groupby.key#3)
     +-limit=
@@ -283,7 +282,6 @@ QueryStmt
     |   |     +-value#4 := ColumnRef(type=STRING, column=KeyValue.Value#2)
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=55-56
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=$groupby.key#3)
     +-limit=
diff --git a/zetasql/analyzer/testdata/safe_function.test b/zetasql/analyzer/testdata/safe_function.test
index f2c15bfa5..d564c3802 100644
--- a/zetasql/analyzer/testdata/safe_function.test
+++ b/zetasql/analyzer/testdata/safe_function.test
@@ -692,7 +692,6 @@ QueryStmt
     | |       |     |       |         +-ColumnHolder(column=$array_offset.off#12)
     | |       |     |       +-order_by_item_list=
     | |       |     |         +-OrderByItem
-    | |       |     |           +-parse_location=228-231
     | |       |     |           +-column_ref=
     | |       |     |             +-ColumnRef(type=INT64, column=$array_offset.off#12)
     | |       |     +-Literal(type=ARRAY, value=NULL, has_explicit_type=TRUE)
@@ -746,7 +745,6 @@ QueryStmt
     | |       |     |       |       +-Literal(type=DOUBLE, value=0)
     | |       |     |       +-order_by_item_list=
     | |       |     |         +-OrderByItem
-    | |       |     |           +-parse_location=243-246
     | |       |     |           +-column_ref=
     | |       |     |             +-ColumnRef(type=INT64, column=$array_offset.off#17)
     | |       |     +-Literal(type=ARRAY, value=NULL, has_explicit_type=TRUE)
diff --git a/zetasql/analyzer/testdata/select_dotstar.test b/zetasql/analyzer/testdata/select_dotstar.test
index 0be733f03..956642506 100644
--- a/zetasql/analyzer/testdata/select_dotstar.test
+++ b/zetasql/analyzer/testdata/select_dotstar.test
@@ -1302,7 +1302,6 @@ QueryStmt
                           +-ColumnRef(type=DATE, column=my_table.modified_date#6)
                       +-order_by_item_list=
                       | +-OrderByItem
-                      |   +-parse_location=130-148
                       |   +-column_ref=
                       |   | +-ColumnRef(type=DATE, column=my_table.modified_date#6)
                       |   +-is_descending=TRUE
@@ -1752,11 +1751,9 @@ QueryStmt
         |                 +-ColumnRef(type=INT64, column=my_table.key#4)
         +-order_by_item_list=
           +-OrderByItem
-          | +-parse_location=175-176
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$query.k#10)
           +-OrderByItem
-            +-parse_location=178-179
             +-column_ref=
               +-ColumnRef(type=STRING, column=$query.value#11)
 ==
@@ -1845,11 +1842,9 @@ QueryStmt
         |     +-value#13 := ColumnRef(type=STRING, column=$query.value#11)
         +-order_by_item_list=
           +-OrderByItem
-          | +-parse_location=184-185
           | +-column_ref=
           |   +-ColumnRef(type=INT64, column=$distinct.k#12)
           +-OrderByItem
-            +-parse_location=187-188
             +-column_ref=
               +-ColumnRef(type=STRING, column=$distinct.value#13)
 ==
@@ -2570,7 +2565,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=UNBOUNDED FOLLOWING)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=62-78
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#3)
 ==
@@ -2655,7 +2649,6 @@ QueryStmt
     |                   +-WindowFrameExpr(boundary_type=UNBOUNDED FOLLOWING)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=123-137
         +-column_ref=
           +-ColumnRef(type=INT64, column=$analytic.$analytic1#8)
 ==
@@ -2782,7 +2775,6 @@ QueryStmt
         |                         +-ColumnRef(type=INT64, column=Perf_Accounts_D.region#15)
         |                     +-order_by_item_list=
         |                       +-OrderByItem
-        |                         +-parse_location=352-362
         |                         +-column_ref=
         |                         | +-ColumnRef(type=INT64, column=Perf_Accounts_D.ratio#16)
         |                         +-is_descending=TRUE
@@ -3149,7 +3141,6 @@ QueryStmt
     |     +-x#4 := ColumnRef(type=INT64, column=$pre_groupby.x#3)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=86-87
         +-column_ref=
           +-ColumnRef(type=INT64, column=$groupby.x#4)
 ==
@@ -3319,11 +3310,9 @@ QueryStmt
     |     +-c#6 := ColumnRef(type=STRING, column=$pre_groupby.c#4)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=77-78
       | +-column_ref=
       |   +-ColumnRef(type=INT32, column=$groupby.b#5)
       +-OrderByItem
-        +-parse_location=80-81
         +-column_ref=
           +-ColumnRef(type=STRING, column=$groupby.c#6)
 ==
diff --git a/zetasql/analyzer/testdata/sql_function_inlining.test b/zetasql/analyzer/testdata/sql_function_inlining.test
index 282fdb49c..75b49fc04 100644
--- a/zetasql/analyzer/testdata/sql_function_inlining.test
+++ b/zetasql/analyzer/testdata/sql_function_inlining.test
@@ -1266,7 +1266,6 @@ QueryStmt
     |             |           |         +-ColumnHolder(column=$array_offset.off#6)
     |             |           +-order_by_item_list=
     |             |             +-OrderByItem
-    |             |               +-parse_location=216-219
     |             |               +-column_ref=
     |             |                 +-ColumnRef(type=INT64, column=$array_offset.off#6)
     |             +-input_scan=
@@ -1355,7 +1354,6 @@ QueryStmt
     |             |           |         +-ColumnHolder(column=$array_offset.off#7)
     |             |           +-order_by_item_list=
     |             |             +-OrderByItem
-    |             |               +-parse_location=216-219
     |             |               +-column_ref=
     |             |                 +-ColumnRef(type=INT64, column=$array_offset.off#7)
     |             +-input_scan=
diff --git a/zetasql/analyzer/testdata/sql_table_function_inlining.test b/zetasql/analyzer/testdata/sql_table_function_inlining.test
index c4a42209b..d88f4baac 100644
--- a/zetasql/analyzer/testdata/sql_table_function_inlining.test
+++ b/zetasql/analyzer/testdata/sql_table_function_inlining.test
@@ -297,7 +297,6 @@ QueryStmt
         |         |           |       +-Literal(type=INT64, value=1)
         |         |           +-order_by_item_list=
         |         |             +-OrderByItem
-        |         |               +-parse_location=231-234
         |         |               +-column_ref=
         |         |                 +-ColumnRef(type=INT64, column=$array_offset.off#5)
         |         +-input_scan=
@@ -3948,7 +3947,6 @@ QueryStmt
             |           |             |         |       +-element_column_list=[$agg_rewriter.$struct#8]
             |           |             |         +-order_by_item_list=
             |           |             |           +-OrderByItem
-            |           |             |             +-parse_location=155-156
             |           |             |             +-column_ref=
             |           |             |               +-ColumnRef(type=INT64, column=$agg_rewriter.a#6)
             |           |             +-input_scan=
diff --git a/zetasql/analyzer/testdata/sql_template_function_inlining.test b/zetasql/analyzer/testdata/sql_template_function_inlining.test
index 5db227d98..adbbfe9e4 100644
--- a/zetasql/analyzer/testdata/sql_template_function_inlining.test
+++ b/zetasql/analyzer/testdata/sql_template_function_inlining.test
@@ -72,7 +72,6 @@ FunctionCall(ZetaSQL:if(BOOL, ARRAY, ARRAY) -> ARRAY)
       |     +-ColumnHolder(column=$array_offset.offset#2)
       +-order_by_item_list=
         +-OrderByItem
-          +-parse_location=181-192
           +-column_ref=
           | +-ColumnRef(type=INT64, column=$array_offset.offset#2)
           +-is_descending=TRUE
@@ -114,7 +113,6 @@ QueryStmt
     |               |     +-ColumnHolder(column=$array_offset.offset#3)
     |               +-order_by_item_list=
     |                 +-OrderByItem
-    |                   +-parse_location=181-192
     |                   +-column_ref=
     |                   | +-ColumnRef(type=INT64, column=$array_offset.offset#3)
     |                   +-is_descending=TRUE
@@ -160,7 +158,6 @@ FunctionCall(ZetaSQL:if(BOOL, ARRAY, ARRAY) -> ARRAY)
       |     +-ColumnHolder(column=$array_offset.offset#2)
       +-order_by_item_list=
         +-OrderByItem
-          +-parse_location=181-192
           +-column_ref=
           | +-ColumnRef(type=INT64, column=$array_offset.offset#2)
           +-is_descending=TRUE
@@ -202,7 +199,6 @@ QueryStmt
     |               |     +-ColumnHolder(column=$array_offset.offset#3)
     |               +-order_by_item_list=
     |                 +-OrderByItem
-    |                   +-parse_location=181-192
     |                   +-column_ref=
     |                   | +-ColumnRef(type=INT64, column=$array_offset.offset#3)
     |                   +-is_descending=TRUE
@@ -248,7 +244,6 @@ FunctionCall(ZetaSQL:if(BOOL, ARRAY, ARRAY) -> ARRAY)
       |     +-ColumnHolder(column=$array_offset.offset#2)
       +-order_by_item_list=
         +-OrderByItem
-          +-parse_location=181-192
           +-column_ref=
           | +-ColumnRef(type=INT64, column=$array_offset.offset#2)
           +-is_descending=TRUE
@@ -290,7 +285,6 @@ QueryStmt
     |               |     +-ColumnHolder(column=$array_offset.offset#3)
     |               +-order_by_item_list=
     |                 +-OrderByItem
-    |                   +-parse_location=181-192
     |                   +-column_ref=
     |                   | +-ColumnRef(type=INT64, column=$array_offset.offset#3)
     |                   +-is_descending=TRUE
@@ -336,7 +330,6 @@ FunctionCall(ZetaSQL:if(BOOL, ARRAY, ARRAY) -> ARRAY)
       |     +-ColumnHolder(column=$array_offset.offset#2)
       +-order_by_item_list=
         +-OrderByItem
-          +-parse_location=181-192
           +-column_ref=
           | +-ColumnRef(type=INT64, column=$array_offset.offset#2)
           +-is_descending=TRUE
@@ -378,7 +371,6 @@ QueryStmt
     |               |     +-ColumnHolder(column=$array_offset.offset#3)
     |               +-order_by_item_list=
     |                 +-OrderByItem
-    |                   +-parse_location=181-192
     |                   +-column_ref=
     |                   | +-ColumnRef(type=INT64, column=$array_offset.offset#3)
     |                   +-is_descending=TRUE
@@ -567,7 +559,6 @@ QueryStmt
     |                 |   |       |           |         +-ColumnHolder(column=$array_offset.off#9)
     |                 |   |       |           +-order_by_item_list=
     |                 |   |       |             +-OrderByItem
-    |                 |   |       |               +-parse_location=216-219
     |                 |   |       |               +-column_ref=
     |                 |   |       |                 +-ColumnRef(type=INT64, column=$array_offset.off#9)
     |                 |   |       +-input_scan=
diff --git a/zetasql/analyzer/testdata/string_aggregate.test b/zetasql/analyzer/testdata/string_aggregate.test
index 3d79afa82..0dd72df09 100644
--- a/zetasql/analyzer/testdata/string_aggregate.test
+++ b/zetasql/analyzer/testdata/string_aggregate.test
@@ -72,7 +72,6 @@ QueryStmt
               |     +-Literal(type=INT64, value=3)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=251-263
               |   +-column_ref=
               |     +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#6)
               +-limit=
@@ -156,7 +155,6 @@ QueryStmt
         |     |         |         |   |     +-distinct.arg#12 := ColumnRef(type=STRING, column=$agg_rewriter.$orderbycol1#9)
         |     |         |         |   +-order_by_item_list=
         |     |         |         |     +-OrderByItem
-        |     |         |         |       +-parse_location=251-263
         |     |         |         |       +-column_ref=
         |     |         |         |         +-ColumnRef(type=STRING, column=$agg_rewriter.distinct.arg#12)
         |     |         |         +-limit=
@@ -256,7 +254,6 @@ QueryStmt
           |   +-Literal(type=STRING, value="x")
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=34-35
           |       +-column_ref=
           |         +-ColumnRef(type=STRING, column=$array.a#4)
           +-$agg2#6 :=
@@ -265,7 +262,6 @@ QueryStmt
               +-Literal(type=STRING, value=", ")
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=66-67
                   +-column_ref=
                     +-ColumnRef(type=STRING, column=$array.a#4)
 
@@ -339,7 +335,6 @@ QueryStmt
         | |   |         |         |         +-ColumnRef(type=STRING, column=$agg_rewriter.a#7)
         | |   |         |         +-order_by_item_list=
         | |   |         |           +-OrderByItem
-        | |   |         |             +-parse_location=34-35
         | |   |         |             +-column_ref=
         | |   |         |               +-ColumnRef(type=STRING, column=$agg_rewriter.a#7)
         | |   |         +-input_scan=
@@ -403,7 +398,6 @@ QueryStmt
         |     |         |         |         +-ColumnRef(type=STRING, column=$agg_rewriter.a#11)
         |     |         |         +-order_by_item_list=
         |     |         |           +-OrderByItem
-        |     |         |             +-parse_location=66-67
         |     |         |             +-column_ref=
         |     |         |               +-ColumnRef(type=STRING, column=$agg_rewriter.a#11)
         |     |         +-input_scan=
@@ -486,7 +480,6 @@ QueryStmt
           |   +-Literal(type=STRING, value=",")
           |   +-order_by_item_list=
           |     +-OrderByItem
-          |       +-parse_location=34-35
           |       +-column_ref=
           |         +-ColumnRef(type=BYTES, column=$array.b#5)
           +-$agg2#7 :=
@@ -495,7 +488,6 @@ QueryStmt
               +-Literal(type=BYTES, value=b", ")
               +-order_by_item_list=
                 +-OrderByItem
-                  +-parse_location=67-68
                   +-column_ref=
                     +-ColumnRef(type=STRING, column=$array.a#4)
 
@@ -575,7 +567,6 @@ QueryStmt
         | |   |         |         |         +-ColumnRef(type=STRING, column=$agg_rewriter.a#8)
         | |   |         |         +-order_by_item_list=
         | |   |         |           +-OrderByItem
-        | |   |         |             +-parse_location=34-35
         | |   |         |             +-column_ref=
         | |   |         |               +-ColumnRef(type=BYTES, column=$agg_rewriter.b#9)
         | |   |         +-input_scan=
@@ -645,7 +636,6 @@ QueryStmt
         |     |         |         |         +-ColumnRef(type=BYTES, column=$agg_rewriter.b#14)
         |     |         |         +-order_by_item_list=
         |     |         |           +-OrderByItem
-        |     |         |             +-parse_location=67-68
         |     |         |             +-column_ref=
         |     |         |               +-ColumnRef(type=STRING, column=$agg_rewriter.a#13)
         |     |         +-input_scan=
@@ -746,7 +736,6 @@ QueryStmt
               +-Literal(type=STRING, value=",")
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=88-104
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
               +-limit=
@@ -897,7 +886,6 @@ QueryStmt
         |     |         |         |   |         +-ColumnRef(type=STRING, column=$agg_rewriter.a#12)
         |     |         |         |   +-order_by_item_list=
         |     |         |         |     +-OrderByItem
-        |     |         |         |       +-parse_location=88-104
         |     |         |         |       +-column_ref=
         |     |         |         |         +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#13)
         |     |         |         +-limit=
@@ -998,7 +986,6 @@ QueryStmt
               +-ColumnRef(type=STRING, column=$array.a#4)
               +-order_by_item_list=
               | +-OrderByItem
-              |   +-parse_location=73-89
               |   +-column_ref=
               |     +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
               +-limit=
@@ -1149,7 +1136,6 @@ QueryStmt
         |     |         |         |   |         +-ColumnRef(type=STRING, column=$agg_rewriter.a#12)
         |     |         |         |   +-order_by_item_list=
         |     |         |         |     +-OrderByItem
-        |     |         |         |       +-parse_location=73-89
         |     |         |         |       +-column_ref=
         |     |         |         |         +-ColumnRef(type=INT64, column=$agg_rewriter.$orderbycol1#13)
         |     |         |         +-limit=
diff --git a/zetasql/analyzer/testdata/struct_braced_constructors.test b/zetasql/analyzer/testdata/struct_braced_constructors.test
index 96063b2fd..7812948c5 100644
--- a/zetasql/analyzer/testdata/struct_braced_constructors.test
+++ b/zetasql/analyzer/testdata/struct_braced_constructors.test
@@ -171,7 +171,7 @@ SET @@struct_system_variable = {c:1}
 # Error: STRUCT should not use proto extension.
 SELECT STRUCT {(a.b): 1}
 --
-ERROR: STRUCT Braced constructor is not allowed to use proto extension. [at 1:16]
+ERROR: Fields in STRUCT Braced constructor should always have a single identifier specified, not a path expression [at 1:16]
 SELECT STRUCT {(a.b): 1}
                ^
 ==
@@ -1645,7 +1645,6 @@ QueryStmt
         |       +-column_index_list=[0]
         +-order_by_item_list=
           +-OrderByItem
-            +-parse_location=157-177
             +-column_ref=
               +-ColumnRef(type=INT32, column=$orderby.$orderbycol1#5)
 
diff --git a/zetasql/analyzer/testdata/tablesample.test b/zetasql/analyzer/testdata/tablesample.test
index 51659bcf5..0ddf2c694 100644
--- a/zetasql/analyzer/testdata/tablesample.test
+++ b/zetasql/analyzer/testdata/tablesample.test
@@ -746,7 +746,6 @@ QueryStmt
     |   |               +-unit=ROWS
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=136-139
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=KeyValue.Key#1)
     +-limit=
@@ -1599,3 +1598,14 @@ ALTERNATION GROUP: TestEnum
 ERROR: Unrecognized name: TestEnum [at 5:51]
       TABLESAMPLE RESERVOIR (10 ROWS PARTITION BY TestEnum)
                                                   ^
+==
+
+# Cannot combine table operators
+select KeyValue.Value from KeyValue
+TABLESAMPLE RESERVOIR (1 ROWS)
+TABLESAMPLE RESERVOIR (2 ROWS)
+TABLESAMPLE RESERVOIR (3 ROWS)
+--
+ERROR: Unsupported combination of table operators. The only allowed combination is PIVOT/UNPIVOT followed by TABLESAMPLE.  [at 3:1]
+TABLESAMPLE RESERVOIR (2 ROWS)
+^
diff --git a/zetasql/analyzer/testdata/ternary_rewriter_functions.test b/zetasql/analyzer/testdata/ternary_rewriter_functions.test
index f87c5bbfb..985024137 100644
--- a/zetasql/analyzer/testdata/ternary_rewriter_functions.test
+++ b/zetasql/analyzer/testdata/ternary_rewriter_functions.test
@@ -105,7 +105,6 @@ QueryStmt
     |         |           |         |       +-ColumnRef(type=INT64, column=$with_expr.end_offset#6, is_correlated=TRUE)
     |         |           |         +-order_by_item_list=
     |         |           |           +-OrderByItem
-    |         |           |             +-parse_location=770-773
     |         |           |             +-column_ref=
     |         |           |               +-ColumnRef(type=INT64, column=$array_offset.idx#8)
     |         |           +-input_scan=
@@ -244,7 +243,6 @@ QueryStmt
     |         |     |       |         |       +-ColumnRef(type=INT64, column=$with_expr.end_offset#6, is_correlated=TRUE)
     |         |     |       |         +-order_by_item_list=
     |         |     |       |           +-OrderByItem
-    |         |     |       |             +-parse_location=782-785
     |         |     |       |             +-column_ref=
     |         |     |       |               +-ColumnRef(type=INT64, column=$array_offset.idx#8)
     |         |     |       +-input_scan=
@@ -394,7 +392,6 @@ QueryStmt
     |         |       |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=321-327
     |         |       |     |       +-column_ref=
     |         |       |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |       |     +-limit=
@@ -430,7 +427,6 @@ QueryStmt
     |         |       |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=537-548
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#8)
     |         |       |     |       +-is_descending=TRUE
@@ -531,7 +527,6 @@ QueryStmt
     |         |     |   |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |     |   |     |   +-order_by_item_list=
     |         |     |   |     |     +-OrderByItem
-    |         |     |   |     |       +-parse_location=333-339
     |         |     |   |     |       +-column_ref=
     |         |     |   |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |     |   |     +-limit=
@@ -567,7 +562,6 @@ QueryStmt
     |         |     |   |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |     |   |     |   +-order_by_item_list=
     |         |     |   |     |     +-OrderByItem
-    |         |     |   |     |       +-parse_location=549-560
     |         |     |   |     |       +-column_ref=
     |         |     |   |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#8)
     |         |     |   |     |       +-is_descending=TRUE
@@ -674,7 +668,6 @@ QueryStmt
     |         |       |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=316-322
     |         |       |     |       +-column_ref=
     |         |       |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |       |     +-limit=
@@ -710,7 +703,6 @@ QueryStmt
     |         |       |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |       |     |   +-order_by_item_list=
     |         |       |     |     +-OrderByItem
-    |         |       |     |       +-parse_location=527-538
     |         |       |     |       +-column_ref=
     |         |       |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#8)
     |         |       |     |       +-is_descending=TRUE
@@ -812,7 +804,6 @@ QueryStmt
     |         |     |   |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |     |   |     |   +-order_by_item_list=
     |         |     |   |     |     +-OrderByItem
-    |         |     |   |     |       +-parse_location=328-334
     |         |     |   |     |       +-column_ref=
     |         |     |   |     |         +-ColumnRef(type=INT64, column=$array_offset.offset#6)
     |         |     |   |     +-limit=
@@ -848,7 +839,6 @@ QueryStmt
     |         |     |   |     |   |       +-ColumnRef(type=STRING, column=$subquery1.target_element#4, is_correlated=TRUE)
     |         |     |   |     |   +-order_by_item_list=
     |         |     |   |     |     +-OrderByItem
-    |         |     |   |     |       +-parse_location=539-550
     |         |     |   |     |       +-column_ref=
     |         |     |   |     |       | +-ColumnRef(type=INT64, column=$array_offset.offset#8)
     |         |     |   |     |       +-is_descending=TRUE
diff --git a/zetasql/analyzer/testdata/tvf_relation_args.test b/zetasql/analyzer/testdata/tvf_relation_args.test
index 37c0c005e..ec45afa91 100644
--- a/zetasql/analyzer/testdata/tvf_relation_args.test
+++ b/zetasql/analyzer/testdata/tvf_relation_args.test
@@ -4061,7 +4061,6 @@ QueryStmt
         |   |   +-has_using=TRUE
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=111-114
         |       +-column_ref=
         |         +-ColumnRef(type=INT64, column=w1.key#5)
         +-limit=
diff --git a/zetasql/analyzer/testdata/tvf_scalar_args.test b/zetasql/analyzer/testdata/tvf_scalar_args.test
index d91b2a20e..6a351291d 100644
--- a/zetasql/analyzer/testdata/tvf_scalar_args.test
+++ b/zetasql/analyzer/testdata/tvf_scalar_args.test
@@ -1573,7 +1573,6 @@ QueryStmt
     |     +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1], alias="kv")
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=35-118
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#6)
 ==
diff --git a/zetasql/analyzer/testdata/unary_rewriter_functions.test b/zetasql/analyzer/testdata/unary_rewriter_functions.test
index b4695afba..bf19309b9 100644
--- a/zetasql/analyzer/testdata/unary_rewriter_functions.test
+++ b/zetasql/analyzer/testdata/unary_rewriter_functions.test
@@ -365,7 +365,6 @@ QueryStmt
     |           | +-TableScan(column_list=[KeyValue.Value#2], table=KeyValue, column_index_list=[1])
     |           +-order_by_item_list=
     |             +-OrderByItem
-    |               +-parse_location=61-66
     |               +-column_ref=
     |                 +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     +-input_scan=
@@ -418,7 +417,6 @@ QueryStmt
     |             |         | +-TableScan(column_list=[KeyValue.Value#2], table=KeyValue, column_index_list=[1])
     |             |         +-order_by_item_list=
     |             |           +-OrderByItem
-    |             |             +-parse_location=61-66
     |             |             +-column_ref=
     |             |               +-ColumnRef(type=STRING, column=KeyValue.Value#2)
     |             +-input_scan=
@@ -805,11 +803,9 @@ QueryStmt
     |         |           |   |         +-ColumnRef(type=STRING, column=$array.e#3)
     |         |           |   +-order_by_item_list=
     |         |           |     +-OrderByItem
-    |         |           |     | +-parse_location=216-221
     |         |           |     | +-column_ref=
     |         |           |     |   +-ColumnRef(type=STRING, column=$array.e#3)
     |         |           |     +-OrderByItem
-    |         |           |       +-parse_location=223-230
     |         |           |       +-column_ref=
     |         |           |         +-ColumnRef(type=INT64, column=$array_offset.idx#4)
     |         |           +-limit=
@@ -890,11 +886,9 @@ QueryStmt
     |         |     |       |   |         +-ColumnRef(type=STRING, column=$array.e#3)
     |         |     |       |   +-order_by_item_list=
     |         |     |       |     +-OrderByItem
-    |         |     |       |     | +-parse_location=228-233
     |         |     |       |     | +-column_ref=
     |         |     |       |     |   +-ColumnRef(type=STRING, column=$array.e#3)
     |         |     |       |     +-OrderByItem
-    |         |     |       |       +-parse_location=235-242
     |         |     |       |       +-column_ref=
     |         |     |       |         +-ColumnRef(type=INT64, column=$array_offset.idx#4)
     |         |     |       +-limit=
@@ -977,12 +971,10 @@ QueryStmt
     |         |           |   |         +-ColumnRef(type=INT64, column=$array.e#3)
     |         |           |   +-order_by_item_list=
     |         |           |     +-OrderByItem
-    |         |           |     | +-parse_location=216-222
     |         |           |     | +-column_ref=
     |         |           |     | | +-ColumnRef(type=INT64, column=$array.e#3)
     |         |           |     | +-is_descending=TRUE
     |         |           |     +-OrderByItem
-    |         |           |       +-parse_location=224-231
     |         |           |       +-column_ref=
     |         |           |         +-ColumnRef(type=INT64, column=$array_offset.idx#4)
     |         |           +-limit=
@@ -1071,17 +1063,14 @@ QueryStmt
     |         |           |   |             +-ColumnRef(type=DOUBLE, column=$array.e#3)
     |         |           |   +-order_by_item_list=
     |         |           |     +-OrderByItem
-    |         |           |     | +-parse_location=216-230
     |         |           |     | +-column_ref=
     |         |           |     | | +-ColumnRef(type=BOOL, column=$orderby.$orderbycol1#5)
     |         |           |     | +-is_descending=TRUE
     |         |           |     +-OrderByItem
-    |         |           |     | +-parse_location=232-238
     |         |           |     | +-column_ref=
     |         |           |     | | +-ColumnRef(type=DOUBLE, column=$array.e#3)
     |         |           |     | +-is_descending=TRUE
     |         |           |     +-OrderByItem
-    |         |           |       +-parse_location=240-247
     |         |           |       +-column_ref=
     |         |           |         +-ColumnRef(type=INT64, column=$array_offset.idx#4)
     |         |           +-limit=
diff --git a/zetasql/analyzer/testdata/unpivot.test b/zetasql/analyzer/testdata/unpivot.test
index 6a085cfa6..38707e158 100644
--- a/zetasql/analyzer/testdata/unpivot.test
+++ b/zetasql/analyzer/testdata/unpivot.test
@@ -1352,6 +1352,16 @@ QueryStmt
           +-Literal(type=INT64, value=10)
 ==
 
+# Unpivot combined with TABLESAMPLE, but error because a 3rd operator follows
+SELECT * FROM KeyValue UNPIVOT(a for b in (Key))
+    TABLESAMPLE SYSTEM (1 ROWS) REPEATABLE(10)
+    TABLESAMPLE SYSTEM (2 ROWS)
+--
+ERROR: Unsupported combination of table operators. The only allowed combination is PIVOT/UNPIVOT followed by TABLESAMPLE.  [at 3:5]
+    TABLESAMPLE SYSTEM (2 ROWS)
+    ^
+==
+
 # Unpivot with multiple output value columns, labels are auto-generated by concatenating column names
 SELECT * FROM MultipleColumns UNPIVOT((x,y) for z in ((int_a,int_b), (int_c,int_d)));
 --
diff --git a/zetasql/analyzer/testdata/value_tables.test b/zetasql/analyzer/testdata/value_tables.test
index 6e0c9a8b9..49591e3af 100644
--- a/zetasql/analyzer/testdata/value_tables.test
+++ b/zetasql/analyzer/testdata/value_tables.test
@@ -825,7 +825,6 @@ QueryStmt
     |     +-a#5 := ColumnRef(type=INT32, column=$pre_groupby.a#4)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=101-104
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.a#5)
 --
@@ -893,7 +892,6 @@ QueryStmt
     |     +-A#5 := ColumnRef(type=INT32, column=$pre_groupby.a#4)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=101-104
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.A#5)
 --
@@ -961,7 +959,6 @@ QueryStmt
     |     +-a#5 := ColumnRef(type=INT32, column=$pre_groupby.A#4)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=101-104
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.a#5)
 --
@@ -1029,7 +1026,6 @@ QueryStmt
     |     +-A#5 := ColumnRef(type=INT32, column=$pre_groupby.A#4)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=101-104
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.A#5)
 ==
@@ -1797,11 +1793,9 @@ QueryStmt
     |                   +-default_value=77
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=80-89
       | +-column_ref=
       |   +-ColumnRef(type=BYTES, column=$orderby.$orderbycol1#3)
       +-OrderByItem
-        +-parse_location=91-101
         +-column_ref=
           +-ColumnRef(type=BOOL, column=$orderby.$orderbycol2#4)
 ==
@@ -1830,7 +1824,6 @@ QueryStmt
     |       +-Literal(type=INT32, value=0)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=53-59
         +-column_ref=
         | +-ColumnRef(type=INT32, column=Int32ValueTable.value#1)
         +-is_descending=TRUE
@@ -1907,7 +1900,6 @@ QueryStmt
     |           +-output_column_list=[$make_proto.$proto#8]
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=146-149
         +-column_ref=
           +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#10)
 ==
@@ -1992,7 +1984,6 @@ QueryStmt
         |   |     +-value#7 := ColumnRef(type=STRING, column=$aggregate.value#3)
         |   +-order_by_item_list=
         |     +-OrderByItem
-        |       +-parse_location=187-188
         |       +-column_ref=
         |         +-ColumnRef(type=INT64, column=$distinct.key#6)
         +-limit=
@@ -2070,7 +2061,6 @@ QueryStmt
     |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=74-84
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
 ==
@@ -2083,8 +2073,6 @@ group by {{k.|}}int32_val1
 having {{k.|}}int32_val1 > 0
 order by {{k.|}}int32_val1
 --
-ALTERNATION GROUP: k.,k.,k.,k.
---
 QueryStmt
 +-output_column_list=
 | +-$groupby.int32_val1#5 AS int32_val1 [INT32]
@@ -2119,316 +2107,315 @@ QueryStmt
     |       +-Literal(type=INT32, value=0)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=102-114
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
+==
+
+# Tests for b/19181915
+select cast(int32_val1 as double) from TestExtraValueTable group by int32_val1
 --
-ALTERNATION GROUP: k.,k.,k.,
+QueryStmt
++-output_column_list=
+| +-$query.$col1#5 AS `$col1` [DOUBLE]
++-query=
+  +-ProjectScan
+    +-column_list=[$query.$col1#5]
+    +-expr_list=
+    | +-$col1#5 :=
+    |   +-Cast(INT32 -> DOUBLE)
+    |     +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
+    +-input_scan=
+      +-AggregateScan
+        +-column_list=[$groupby.int32_val1#4]
+        +-input_scan=
+        | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
+        +-group_by_list=
+          +-int32_val1#4 :=
+            +-GetProtoField
+              +-type=INT32
+              +-expr=
+              | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+              +-field_descriptor=int32_val1
+              +-default_value=0
+==
+
+select sum(int32_val1)
+from TestExtraValueTable
+group by int32_val2
+having int32_val2 > 1
 --
 QueryStmt
 +-output_column_list=
-| +-$groupby.int32_val1#5 AS int32_val1 [INT32]
+| +-$aggregate.$agg1#4 AS `$col1` [INT64]
 +-query=
-  +-OrderByScan
-    +-column_list=[$groupby.int32_val1#5]
-    +-is_ordered=TRUE
+  +-ProjectScan
+    +-column_list=[$aggregate.$agg1#4]
     +-input_scan=
-    | +-FilterScan
-    |   +-column_list=[$groupby.int32_val1#5]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32_val1#5]
-    |   |   +-input_scan=
-    |   |   | +-ProjectScan
-    |   |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.int32_val1#4]
-    |   |   |   +-expr_list=
-    |   |   |   | +-int32_val1#4 :=
-    |   |   |   |   +-GetProtoField
-    |   |   |   |     +-type=INT32
-    |   |   |   |     +-expr=
-    |   |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |   |     +-field_descriptor=int32_val1
-    |   |   |   |     +-default_value=0
-    |   |   |   +-input_scan=
-    |   |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0], alias="k")
-    |   |   +-group_by_list=
-    |   |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
-    |   +-filter_expr=
-    |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-    |       +-Literal(type=INT32, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=102-112
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
+      +-FilterScan
+        +-column_list=[$groupby.int32_val2#5, $aggregate.$agg1#4]
+        +-input_scan=
+        | +-AggregateScan
+        |   +-column_list=[$groupby.int32_val2#5, $aggregate.$agg1#4]
+        |   +-input_scan=
+        |   | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
+        |   +-group_by_list=
+        |   | +-int32_val2#5 :=
+        |   |   +-GetProtoField
+        |   |     +-type=INT32
+        |   |     +-expr=
+        |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+        |   |     +-field_descriptor=int32_val2
+        |   |     +-default_value=0
+        |   +-aggregate_list=
+        |     +-$agg1#4 :=
+        |       +-AggregateFunctionCall(ZetaSQL:sum(INT64) -> INT64)
+        |         +-Cast(INT32 -> INT64)
+        |           +-GetProtoField
+        |             +-type=INT32
+        |             +-expr=
+        |             | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+        |             +-field_descriptor=int32_val1
+        |             +-default_value=0
+        +-filter_expr=
+          +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
+            +-ColumnRef(type=INT32, column=$groupby.int32_val2#5)
+            +-Literal(type=INT32, value=1)
+==
+
+select sum(int32_val1)
+from TestExtraValueTable
+where int32_val1 > 5
+group by int32_val1
+having sum(int32_val1) > 5 and int32_val1 < 10;
 --
-ALTERNATION GROUPS:
-    k.,k.,,k.
-    k.,,k.,k.
-    k.,k.,k.
+QueryStmt
++-output_column_list=
+| +-$aggregate.$agg1#4 AS `$col1` [INT64]
++-query=
+  +-ProjectScan
+    +-column_list=[$aggregate.$agg1#4]
+    +-input_scan=
+      +-FilterScan
+        +-column_list=[$groupby.int32_val1#5, $aggregate.$agg1#4, $aggregate.$agg2#6]
+        +-input_scan=
+        | +-AggregateScan
+        |   +-column_list=[$groupby.int32_val1#5, $aggregate.$agg1#4, $aggregate.$agg2#6]
+        |   +-input_scan=
+        |   | +-FilterScan
+        |   |   +-column_list=[TestExtraValueTable.value#1]
+        |   |   +-input_scan=
+        |   |   | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
+        |   |   +-filter_expr=
+        |   |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
+        |   |       +-GetProtoField
+        |   |       | +-type=INT32
+        |   |       | +-expr=
+        |   |       | | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+        |   |       | +-field_descriptor=int32_val1
+        |   |       | +-default_value=0
+        |   |       +-Literal(type=INT32, value=5)
+        |   +-group_by_list=
+        |   | +-int32_val1#5 :=
+        |   |   +-GetProtoField
+        |   |     +-type=INT32
+        |   |     +-expr=
+        |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+        |   |     +-field_descriptor=int32_val1
+        |   |     +-default_value=0
+        |   +-aggregate_list=
+        |     +-$agg1#4 :=
+        |     | +-AggregateFunctionCall(ZetaSQL:sum(INT64) -> INT64)
+        |     |   +-Cast(INT32 -> INT64)
+        |     |     +-GetProtoField
+        |     |       +-type=INT32
+        |     |       +-expr=
+        |     |       | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+        |     |       +-field_descriptor=int32_val1
+        |     |       +-default_value=0
+        |     +-$agg2#6 :=
+        |       +-AggregateFunctionCall(ZetaSQL:sum(INT64) -> INT64)
+        |         +-Cast(INT32 -> INT64)
+        |           +-GetProtoField
+        |             +-type=INT32
+        |             +-expr=
+        |             | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+        |             +-field_descriptor=int32_val1
+        |             +-default_value=0
+        +-filter_expr=
+          +-FunctionCall(ZetaSQL:$and(BOOL, repeated(1) BOOL) -> BOOL)
+            +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
+            | +-ColumnRef(type=INT64, column=$aggregate.$agg2#6)
+            | +-Literal(type=INT64, value=5)
+            +-FunctionCall(ZetaSQL:$less(INT32, INT32) -> BOOL)
+              +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
+              +-Literal(type=INT32, value=10)
+==
+
+select int32_val1 as foo, count(int32_val2)
+from TestExtraValueTable
+GROUP BY foo
+order by int32_val1
 --
 QueryStmt
 +-output_column_list=
-| +-$groupby.int32_val1#5 AS int32_val1 [INT32]
+| +-$groupby.foo#6 AS foo [INT32]
+| +-$aggregate.$agg1#4 AS `$col2` [INT64]
 +-query=
   +-OrderByScan
-    +-column_list=[$groupby.int32_val1#5]
+    +-column_list=[$groupby.foo#6, $aggregate.$agg1#4]
     +-is_ordered=TRUE
     +-input_scan=
-    | +-FilterScan
-    |   +-column_list=[$groupby.int32_val1#5]
+    | +-AggregateScan
+    |   +-column_list=[$groupby.foo#6, $aggregate.$agg1#4]
     |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32_val1#5]
+    |   | +-ProjectScan
+    |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.foo#5]
+    |   |   +-expr_list=
+    |   |   | +-foo#5 :=
+    |   |   |   +-GetProtoField
+    |   |   |     +-type=INT32
+    |   |   |     +-expr=
+    |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+    |   |   |     +-field_descriptor=int32_val1
+    |   |   |     +-default_value=0
     |   |   +-input_scan=
-    |   |   | +-ProjectScan
-    |   |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.int32_val1#4]
-    |   |   |   +-expr_list=
-    |   |   |   | +-int32_val1#4 :=
-    |   |   |   |   +-GetProtoField
-    |   |   |   |     +-type=INT32
-    |   |   |   |     +-expr=
-    |   |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |   |     +-field_descriptor=int32_val1
-    |   |   |   |     +-default_value=0
-    |   |   |   +-input_scan=
-    |   |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0], alias="k")
-    |   |   +-group_by_list=
-    |   |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
-    |   +-filter_expr=
-    |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-    |       +-Literal(type=INT32, value=0)
+    |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
+    |   +-group_by_list=
+    |   | +-foo#6 := ColumnRef(type=INT32, column=$pre_groupby.foo#5)
+    |   +-aggregate_list=
+    |     +-$agg1#4 :=
+    |       +-AggregateFunctionCall(ZetaSQL:count(INT32) -> INT64)
+    |         +-GetProtoField
+    |           +-type=INT32
+    |           +-expr=
+    |           | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+    |           +-field_descriptor=int32_val2
+    |           +-default_value=0
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=100-112
         +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
---
-ALTERNATION GROUPS:
-    k.,k.,,
-    k.,,k.,
-    k.,k.,
+          +-ColumnRef(type=INT32, column=$groupby.foo#6)
+==
+
+select max(int32_val2)
+from TestExtraValueTable
+WHERE int32_val1 > 1000000
+  and int32_val1 < 1500000
+group by int32_val1
+order by int32_val1
 --
 QueryStmt
 +-output_column_list=
-| +-$groupby.int32_val1#5 AS int32_val1 [INT32]
+| +-$aggregate.$agg1#4 AS `$col1` [INT32]
 +-query=
   +-OrderByScan
-    +-column_list=[$groupby.int32_val1#5]
+    +-column_list=[$aggregate.$agg1#4]
     +-is_ordered=TRUE
     +-input_scan=
-    | +-FilterScan
-    |   +-column_list=[$groupby.int32_val1#5]
+    | +-AggregateScan
+    |   +-column_list=[$groupby.int32_val1#5, $aggregate.$agg1#4]
     |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32_val1#5]
+    |   | +-FilterScan
+    |   |   +-column_list=[TestExtraValueTable.value#1]
     |   |   +-input_scan=
-    |   |   | +-ProjectScan
-    |   |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.int32_val1#4]
-    |   |   |   +-expr_list=
-    |   |   |   | +-int32_val1#4 :=
-    |   |   |   |   +-GetProtoField
-    |   |   |   |     +-type=INT32
-    |   |   |   |     +-expr=
-    |   |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |   |     +-field_descriptor=int32_val1
-    |   |   |   |     +-default_value=0
-    |   |   |   +-input_scan=
-    |   |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0], alias="k")
-    |   |   +-group_by_list=
-    |   |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
-    |   +-filter_expr=
-    |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-    |       +-Literal(type=INT32, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=100-110
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
---
-ALTERNATION GROUPS:
-    k.,,,k.
-    k.,,k.
-    k.,k.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.int32_val1#5 AS int32_val1 [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.int32_val1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-FilterScan
-    |   +-column_list=[$groupby.int32_val1#5]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32_val1#5]
-    |   |   +-input_scan=
-    |   |   | +-ProjectScan
-    |   |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.int32_val1#4]
-    |   |   |   +-expr_list=
-    |   |   |   | +-int32_val1#4 :=
-    |   |   |   |   +-GetProtoField
-    |   |   |   |     +-type=INT32
-    |   |   |   |     +-expr=
-    |   |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |   |     +-field_descriptor=int32_val1
-    |   |   |   |     +-default_value=0
-    |   |   |   +-input_scan=
-    |   |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0], alias="k")
-    |   |   +-group_by_list=
-    |   |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
-    |   +-filter_expr=
-    |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-    |       +-Literal(type=INT32, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=98-110
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
---
-ALTERNATION GROUPS:
-    k.,,,
-    k.,,
-    k.,
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.int32_val1#5 AS int32_val1 [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.int32_val1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-FilterScan
-    |   +-column_list=[$groupby.int32_val1#5]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32_val1#5]
-    |   |   +-input_scan=
-    |   |   | +-ProjectScan
-    |   |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.int32_val1#4]
-    |   |   |   +-expr_list=
-    |   |   |   | +-int32_val1#4 :=
-    |   |   |   |   +-GetProtoField
-    |   |   |   |     +-type=INT32
-    |   |   |   |     +-expr=
-    |   |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |   |     +-field_descriptor=int32_val1
-    |   |   |   |     +-default_value=0
-    |   |   |   +-input_scan=
-    |   |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0], alias="k")
-    |   |   +-group_by_list=
-    |   |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
-    |   +-filter_expr=
-    |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-    |       +-Literal(type=INT32, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=98-108
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
---
-ALTERNATION GROUP: k.
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.int32_val1#5 AS int32_val1 [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.int32_val1#5]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-FilterScan
-    |   +-column_list=[$groupby.int32_val1#5]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32_val1#5]
-    |   |   +-input_scan=
-    |   |   | +-ProjectScan
-    |   |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.int32_val1#4]
-    |   |   |   +-expr_list=
-    |   |   |   | +-int32_val1#4 :=
-    |   |   |   |   +-GetProtoField
-    |   |   |   |     +-type=INT32
-    |   |   |   |     +-expr=
-    |   |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |   |     +-field_descriptor=int32_val1
-    |   |   |   |     +-default_value=0
-    |   |   |   +-input_scan=
-    |   |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0], alias="k")
-    |   |   +-group_by_list=
-    |   |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
-    |   +-filter_expr=
-    |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-    |       +-Literal(type=INT32, value=0)
+    |   |   | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
+    |   |   +-filter_expr=
+    |   |     +-FunctionCall(ZetaSQL:$and(BOOL, repeated(1) BOOL) -> BOOL)
+    |   |       +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
+    |   |       | +-GetProtoField
+    |   |       | | +-type=INT32
+    |   |       | | +-expr=
+    |   |       | | | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+    |   |       | | +-field_descriptor=int32_val1
+    |   |       | | +-default_value=0
+    |   |       | +-Literal(type=INT32, value=1000000)
+    |   |       +-FunctionCall(ZetaSQL:$less(INT32, INT32) -> BOOL)
+    |   |         +-GetProtoField
+    |   |         | +-type=INT32
+    |   |         | +-expr=
+    |   |         | | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+    |   |         | +-field_descriptor=int32_val1
+    |   |         | +-default_value=0
+    |   |         +-Literal(type=INT32, value=1500000)
+    |   +-group_by_list=
+    |   | +-int32_val1#5 :=
+    |   |   +-GetProtoField
+    |   |     +-type=INT32
+    |   |     +-expr=
+    |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+    |   |     +-field_descriptor=int32_val1
+    |   |     +-default_value=0
+    |   +-aggregate_list=
+    |     +-$agg1#4 :=
+    |       +-AggregateFunctionCall(ZetaSQL:max(INT32) -> INT32)
+    |         +-GetProtoField
+    |           +-type=INT32
+    |           +-expr=
+    |           | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+    |           +-field_descriptor=int32_val2
+    |           +-default_value=0
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=96-108
         +-column_ref=
           +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
---
-ALTERNATION GROUP: 
+==
+
+select int32_val2, int32_val1
+from TestExtraValueTable
+group by 1, 2
 --
 QueryStmt
 +-output_column_list=
+| +-$groupby.int32_val2#4 AS int32_val2 [INT32]
 | +-$groupby.int32_val1#5 AS int32_val1 [INT32]
 +-query=
-  +-OrderByScan
-    +-column_list=[$groupby.int32_val1#5]
-    +-is_ordered=TRUE
+  +-ProjectScan
+    +-column_list=$groupby.[int32_val2#4, int32_val1#5]
     +-input_scan=
-    | +-FilterScan
-    |   +-column_list=[$groupby.int32_val1#5]
-    |   +-input_scan=
-    |   | +-AggregateScan
-    |   |   +-column_list=[$groupby.int32_val1#5]
-    |   |   +-input_scan=
-    |   |   | +-ProjectScan
-    |   |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.int32_val1#4]
-    |   |   |   +-expr_list=
-    |   |   |   | +-int32_val1#4 :=
-    |   |   |   |   +-GetProtoField
-    |   |   |   |     +-type=INT32
-    |   |   |   |     +-expr=
-    |   |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |   |     +-field_descriptor=int32_val1
-    |   |   |   |     +-default_value=0
-    |   |   |   +-input_scan=
-    |   |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0], alias="k")
-    |   |   +-group_by_list=
-    |   |     +-int32_val1#5 := ColumnRef(type=INT32, column=$pre_groupby.int32_val1#4)
-    |   +-filter_expr=
-    |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-    |       +-Literal(type=INT32, value=0)
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=96-106
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
+      +-AggregateScan
+        +-column_list=$groupby.[int32_val2#4, int32_val1#5]
+        +-input_scan=
+        | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
+        +-group_by_list=
+          +-int32_val2#4 :=
+          | +-GetProtoField
+          |   +-type=INT32
+          |   +-expr=
+          |   | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+          |   +-field_descriptor=int32_val2
+          |   +-default_value=0
+          +-int32_val1#5 :=
+            +-GetProtoField
+              +-type=INT32
+              +-expr=
+              | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
+              +-field_descriptor=int32_val1
+              +-default_value=0
 ==
 
-# Tests for b/19181915
-select cast(int32_val1 as double) from TestExtraValueTable group by int32_val1
+select int32_val1+1, int32_val1+int32_val1
+from TestExtraValueTable
+group by int32_val1
 --
 QueryStmt
 +-output_column_list=
-| +-$query.$col1#5 AS `$col1` [DOUBLE]
+| +-$query.$col1#5 AS `$col1` [INT64]
+| +-$query.$col2#6 AS `$col2` [INT64]
 +-query=
   +-ProjectScan
-    +-column_list=[$query.$col1#5]
+    +-column_list=$query.[$col1#5, $col2#6]
     +-expr_list=
     | +-$col1#5 :=
-    |   +-Cast(INT32 -> DOUBLE)
-    |     +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
+    | | +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
+    | |   +-Cast(INT32 -> INT64)
+    | |   | +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
+    | |   +-Literal(type=INT64, value=1)
+    | +-$col2#6 :=
+    |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
+    |     +-Cast(INT32 -> INT64)
+    |     | +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
+    |     +-Cast(INT32 -> INT64)
+    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
     +-input_scan=
       +-AggregateScan
         +-column_list=[$groupby.int32_val1#4]
@@ -2444,254 +2431,37 @@ QueryStmt
               +-default_value=0
 ==
 
-select sum(int32_val1)
+select int32_val1+1, int32_val1, int32_val1 k, int32_val1+2 as k2
 from TestExtraValueTable
-group by int32_val2
-having int32_val2 > 1
+group by k;
 --
 QueryStmt
 +-output_column_list=
-| +-$aggregate.$agg1#4 AS `$col1` [INT64]
+| +-$query.$col1#5 AS `$col1` [INT64]
+| +-$groupby.k#4 AS int32_val1 [INT32]
+| +-$groupby.k#4 AS k [INT32]
+| +-$query.k2#6 AS k2 [INT64]
 +-query=
   +-ProjectScan
-    +-column_list=[$aggregate.$agg1#4]
-    +-input_scan=
-      +-FilterScan
-        +-column_list=[$groupby.int32_val2#5, $aggregate.$agg1#4]
-        +-input_scan=
-        | +-AggregateScan
-        |   +-column_list=[$groupby.int32_val2#5, $aggregate.$agg1#4]
-        |   +-input_scan=
-        |   | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
-        |   +-group_by_list=
-        |   | +-int32_val2#5 :=
-        |   |   +-GetProtoField
-        |   |     +-type=INT32
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-        |   |     +-field_descriptor=int32_val2
-        |   |     +-default_value=0
-        |   +-aggregate_list=
-        |     +-$agg1#4 :=
-        |       +-AggregateFunctionCall(ZetaSQL:sum(INT64) -> INT64)
-        |         +-Cast(INT32 -> INT64)
-        |           +-GetProtoField
-        |             +-type=INT32
-        |             +-expr=
-        |             | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-        |             +-field_descriptor=int32_val1
-        |             +-default_value=0
-        +-filter_expr=
-          +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-            +-ColumnRef(type=INT32, column=$groupby.int32_val2#5)
-            +-Literal(type=INT32, value=1)
-==
-
-select sum(int32_val1)
-from TestExtraValueTable
-where int32_val1 > 5
-group by int32_val1
-having sum(int32_val1) > 5 and int32_val1 < 10;
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#4 AS `$col1` [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=[$aggregate.$agg1#4]
-    +-input_scan=
-      +-FilterScan
-        +-column_list=[$groupby.int32_val1#5, $aggregate.$agg1#4, $aggregate.$agg2#6]
-        +-input_scan=
-        | +-AggregateScan
-        |   +-column_list=[$groupby.int32_val1#5, $aggregate.$agg1#4, $aggregate.$agg2#6]
-        |   +-input_scan=
-        |   | +-FilterScan
-        |   |   +-column_list=[TestExtraValueTable.value#1]
-        |   |   +-input_scan=
-        |   |   | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
-        |   |   +-filter_expr=
-        |   |     +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-        |   |       +-GetProtoField
-        |   |       | +-type=INT32
-        |   |       | +-expr=
-        |   |       | | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-        |   |       | +-field_descriptor=int32_val1
-        |   |       | +-default_value=0
-        |   |       +-Literal(type=INT32, value=5)
-        |   +-group_by_list=
-        |   | +-int32_val1#5 :=
-        |   |   +-GetProtoField
-        |   |     +-type=INT32
-        |   |     +-expr=
-        |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-        |   |     +-field_descriptor=int32_val1
-        |   |     +-default_value=0
-        |   +-aggregate_list=
-        |     +-$agg1#4 :=
-        |     | +-AggregateFunctionCall(ZetaSQL:sum(INT64) -> INT64)
-        |     |   +-Cast(INT32 -> INT64)
-        |     |     +-GetProtoField
-        |     |       +-type=INT32
-        |     |       +-expr=
-        |     |       | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-        |     |       +-field_descriptor=int32_val1
-        |     |       +-default_value=0
-        |     +-$agg2#6 :=
-        |       +-AggregateFunctionCall(ZetaSQL:sum(INT64) -> INT64)
-        |         +-Cast(INT32 -> INT64)
-        |           +-GetProtoField
-        |             +-type=INT32
-        |             +-expr=
-        |             | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-        |             +-field_descriptor=int32_val1
-        |             +-default_value=0
-        +-filter_expr=
-          +-FunctionCall(ZetaSQL:$and(BOOL, repeated(1) BOOL) -> BOOL)
-            +-FunctionCall(ZetaSQL:$greater(INT64, INT64) -> BOOL)
-            | +-ColumnRef(type=INT64, column=$aggregate.$agg2#6)
-            | +-Literal(type=INT64, value=5)
-            +-FunctionCall(ZetaSQL:$less(INT32, INT32) -> BOOL)
-              +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-              +-Literal(type=INT32, value=10)
-==
-
-select int32_val1 as foo, count(int32_val2)
-from TestExtraValueTable
-GROUP BY foo
-order by int32_val1
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.foo#6 AS foo [INT32]
-| +-$aggregate.$agg1#4 AS `$col2` [INT64]
-+-query=
-  +-OrderByScan
-    +-column_list=[$groupby.foo#6, $aggregate.$agg1#4]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.foo#6, $aggregate.$agg1#4]
-    |   +-input_scan=
-    |   | +-ProjectScan
-    |   |   +-column_list=[TestExtraValueTable.value#1, $pre_groupby.foo#5]
-    |   |   +-expr_list=
-    |   |   | +-foo#5 :=
-    |   |   |   +-GetProtoField
-    |   |   |     +-type=INT32
-    |   |   |     +-expr=
-    |   |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |   |     +-field_descriptor=int32_val1
-    |   |   |     +-default_value=0
-    |   |   +-input_scan=
-    |   |     +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
-    |   +-group_by_list=
-    |   | +-foo#6 := ColumnRef(type=INT32, column=$pre_groupby.foo#5)
-    |   +-aggregate_list=
-    |     +-$agg1#4 :=
-    |       +-AggregateFunctionCall(ZetaSQL:count(INT32) -> INT64)
-    |         +-GetProtoField
-    |           +-type=INT32
-    |           +-expr=
-    |           | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |           +-field_descriptor=int32_val2
-    |           +-default_value=0
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=91-101
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.foo#6)
-==
-
-select max(int32_val2)
-from TestExtraValueTable
-WHERE int32_val1 > 1000000
-  and int32_val1 < 1500000
-group by int32_val1
-order by int32_val1
---
-QueryStmt
-+-output_column_list=
-| +-$aggregate.$agg1#4 AS `$col1` [INT32]
-+-query=
-  +-OrderByScan
-    +-column_list=[$aggregate.$agg1#4]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-AggregateScan
-    |   +-column_list=[$groupby.int32_val1#5, $aggregate.$agg1#4]
-    |   +-input_scan=
-    |   | +-FilterScan
-    |   |   +-column_list=[TestExtraValueTable.value#1]
-    |   |   +-input_scan=
-    |   |   | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
-    |   |   +-filter_expr=
-    |   |     +-FunctionCall(ZetaSQL:$and(BOOL, repeated(1) BOOL) -> BOOL)
-    |   |       +-FunctionCall(ZetaSQL:$greater(INT32, INT32) -> BOOL)
-    |   |       | +-GetProtoField
-    |   |       | | +-type=INT32
-    |   |       | | +-expr=
-    |   |       | | | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |       | | +-field_descriptor=int32_val1
-    |   |       | | +-default_value=0
-    |   |       | +-Literal(type=INT32, value=1000000)
-    |   |       +-FunctionCall(ZetaSQL:$less(INT32, INT32) -> BOOL)
-    |   |         +-GetProtoField
-    |   |         | +-type=INT32
-    |   |         | +-expr=
-    |   |         | | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |         | +-field_descriptor=int32_val1
-    |   |         | +-default_value=0
-    |   |         +-Literal(type=INT32, value=1500000)
-    |   +-group_by_list=
-    |   | +-int32_val1#5 :=
-    |   |   +-GetProtoField
-    |   |     +-type=INT32
-    |   |     +-expr=
-    |   |     | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |   |     +-field_descriptor=int32_val1
-    |   |     +-default_value=0
-    |   +-aggregate_list=
-    |     +-$agg1#4 :=
-    |       +-AggregateFunctionCall(ZetaSQL:max(INT32) -> INT32)
-    |         +-GetProtoField
-    |           +-type=INT32
-    |           +-expr=
-    |           | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-    |           +-field_descriptor=int32_val2
-    |           +-default_value=0
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=131-141
-        +-column_ref=
-          +-ColumnRef(type=INT32, column=$groupby.int32_val1#5)
-==
-
-select int32_val2, int32_val1
-from TestExtraValueTable
-group by 1, 2
---
-QueryStmt
-+-output_column_list=
-| +-$groupby.int32_val2#4 AS int32_val2 [INT32]
-| +-$groupby.int32_val1#5 AS int32_val1 [INT32]
-+-query=
-  +-ProjectScan
-    +-column_list=$groupby.[int32_val2#4, int32_val1#5]
+    +-column_list=[$query.$col1#5, $groupby.k#4, $groupby.k#4, $query.k2#6]
+    +-expr_list=
+    | +-$col1#5 :=
+    | | +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
+    | |   +-Cast(INT32 -> INT64)
+    | |   | +-ColumnRef(type=INT32, column=$groupby.k#4)
+    | |   +-Literal(type=INT64, value=1)
+    | +-k2#6 :=
+    |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
+    |     +-Cast(INT32 -> INT64)
+    |     | +-ColumnRef(type=INT32, column=$groupby.k#4)
+    |     +-Literal(type=INT64, value=2)
     +-input_scan=
       +-AggregateScan
-        +-column_list=$groupby.[int32_val2#4, int32_val1#5]
+        +-column_list=[$groupby.k#4]
         +-input_scan=
         | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
         +-group_by_list=
-          +-int32_val2#4 :=
-          | +-GetProtoField
-          |   +-type=INT32
-          |   +-expr=
-          |   | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-          |   +-field_descriptor=int32_val2
-          |   +-default_value=0
-          +-int32_val1#5 :=
+          +-k#4 :=
             +-GetProtoField
               +-type=INT32
               +-expr=
@@ -2700,103 +2470,26 @@ QueryStmt
               +-default_value=0
 ==
 
-select int32_val1+1, int32_val1+int32_val1
+select int32_val1+1, int32_val1, int32_val1 k, int32_val1+2 as k2
 from TestExtraValueTable
-group by int32_val1
+group by 2;
 --
 QueryStmt
 +-output_column_list=
 | +-$query.$col1#5 AS `$col1` [INT64]
-| +-$query.$col2#6 AS `$col2` [INT64]
+| +-$groupby.int32_val1#4 AS int32_val1 [INT32]
+| +-$groupby.int32_val1#4 AS k [INT32]
+| +-$query.k2#6 AS k2 [INT64]
 +-query=
   +-ProjectScan
-    +-column_list=$query.[$col1#5, $col2#6]
+    +-column_list=[$query.$col1#5, $groupby.int32_val1#4, $groupby.int32_val1#4, $query.k2#6]
     +-expr_list=
     | +-$col1#5 :=
     | | +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
     | |   +-Cast(INT32 -> INT64)
     | |   | +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
     | |   +-Literal(type=INT64, value=1)
-    | +-$col2#6 :=
-    |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |     +-Cast(INT32 -> INT64)
-    |     | +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
-    |     +-Cast(INT32 -> INT64)
-    |       +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$groupby.int32_val1#4]
-        +-input_scan=
-        | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
-        +-group_by_list=
-          +-int32_val1#4 :=
-            +-GetProtoField
-              +-type=INT32
-              +-expr=
-              | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-              +-field_descriptor=int32_val1
-              +-default_value=0
-==
-
-select int32_val1+1, int32_val1, int32_val1 k, int32_val1+2 as k2
-from TestExtraValueTable
-group by k;
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
-| +-$groupby.k#4 AS int32_val1 [INT32]
-| +-$groupby.k#4 AS k [INT32]
-| +-$query.k2#6 AS k2 [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=[$query.$col1#5, $groupby.k#4, $groupby.k#4, $query.k2#6]
-    +-expr_list=
-    | +-$col1#5 :=
-    | | +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    | |   +-Cast(INT32 -> INT64)
-    | |   | +-ColumnRef(type=INT32, column=$groupby.k#4)
-    | |   +-Literal(type=INT64, value=1)
-    | +-k2#6 :=
-    |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    |     +-Cast(INT32 -> INT64)
-    |     | +-ColumnRef(type=INT32, column=$groupby.k#4)
-    |     +-Literal(type=INT64, value=2)
-    +-input_scan=
-      +-AggregateScan
-        +-column_list=[$groupby.k#4]
-        +-input_scan=
-        | +-TableScan(column_list=[TestExtraValueTable.value#1], table=TestExtraValueTable, column_index_list=[0])
-        +-group_by_list=
-          +-k#4 :=
-            +-GetProtoField
-              +-type=INT32
-              +-expr=
-              | +-ColumnRef(type=PROTO, column=TestExtraValueTable.value#1)
-              +-field_descriptor=int32_val1
-              +-default_value=0
-==
-
-select int32_val1+1, int32_val1, int32_val1 k, int32_val1+2 as k2
-from TestExtraValueTable
-group by 2;
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#5 AS `$col1` [INT64]
-| +-$groupby.int32_val1#4 AS int32_val1 [INT32]
-| +-$groupby.int32_val1#4 AS k [INT32]
-| +-$query.k2#6 AS k2 [INT64]
-+-query=
-  +-ProjectScan
-    +-column_list=[$query.$col1#5, $groupby.int32_val1#4, $groupby.int32_val1#4, $query.k2#6]
-    +-expr_list=
-    | +-$col1#5 :=
-    | | +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
-    | |   +-Cast(INT32 -> INT64)
-    | |   | +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
-    | |   +-Literal(type=INT64, value=1)
-    | +-k2#6 :=
+    | +-k2#6 :=
     |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
     |     +-Cast(INT32 -> INT64)
     |     | +-ColumnRef(type=INT32, column=$groupby.int32_val1#4)
@@ -2975,15 +2668,12 @@ QueryStmt
     |         +-Literal(type=INT64, value=4)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=216-227
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.int64_key_1#4)
       +-OrderByItem
-      | +-parse_location=229-254
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.nested_int64#5)
       +-OrderByItem
-        +-parse_location=256-270
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg2#7)
 ==
@@ -3046,7 +2736,6 @@ QueryStmt
     |           +-Literal(type=INT64, value=2)
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=145-174
         +-column_ref=
           +-ColumnRef(type=INT64, column=$orderby.$orderbycol1#4)
 ==
@@ -3184,27 +2873,21 @@ QueryStmt
     |         +-Literal(type=INT64, value=5)
     +-order_by_item_list=
       +-OrderByItem
-      | +-parse_location=417-428
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.int64_key_1#6)
       +-OrderByItem
-      | +-parse_location=430-443
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.int64_key_2#7)
       +-OrderByItem
-      | +-parse_location=445-470
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.nested_int64#8)
       +-OrderByItem
-      | +-parse_location=481-508
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$groupby.nested_int64#8)
       +-OrderByItem
-      | +-parse_location=510-524
       | +-column_ref=
       |   +-ColumnRef(type=INT64, column=$aggregate.$agg3#11)
       +-OrderByItem
-        +-parse_location=526-545
         +-column_ref=
           +-ColumnRef(type=INT64, column=$aggregate.$agg4#12)
 ==
@@ -3392,426 +3075,6 @@ group by {{T.|}}f.d.b
 having {{T.|}}f.d.b = 'foo'
 order by concat({{T.|}}f.d.b, "b")
 --
-ALTERNATION GROUP: T.,T.,T.,T.
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#3 AS `$col1` [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.b#2, $query.$col1#3, $orderby.$orderbycol1#4]
-    |   +-expr_list=
-    |   | +-$orderbycol1#4 :=
-    |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |   |     +-Literal(type=STRING, value="b")
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       |   +-expr_list=
-    |       |   | +-$col1#3 :=
-    |       |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |       |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |       |   |     +-Literal(type=STRING, value="a")
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.b#2]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-b#2 :=
-    |       |           +-GetStructField
-    |       |             +-type=STRING
-    |       |             +-expr=
-    |       |             | +-GetStructField
-    |       |             |   +-type=STRUCT
-    |       |             |   +-expr=
-    |       |             |   | +-GetStructField
-    |       |             |   |   +-type=STRUCT>
-    |       |             |   |   +-expr=
-    |       |             |   |   | +-ColumnRef(type=STRUCT>>, column=TestNestedStructValueTable.value#1)
-    |       |             |   |   +-field_idx=1
-    |       |             |   +-field_idx=1
-    |       |             +-field_idx=1
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$equal(STRING, STRING) -> BOOL)
-    |           +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |           +-Literal(type=STRING, value="foo")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=111-131
-        +-column_ref=
-          +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
---
-ALTERNATION GROUP: T.,T.,T.,
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#3 AS `$col1` [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.b#2, $query.$col1#3, $orderby.$orderbycol1#4]
-    |   +-expr_list=
-    |   | +-$orderbycol1#4 :=
-    |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |   |     +-Literal(type=STRING, value="b")
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       |   +-expr_list=
-    |       |   | +-$col1#3 :=
-    |       |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |       |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |       |   |     +-Literal(type=STRING, value="a")
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.b#2]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-b#2 :=
-    |       |           +-GetStructField
-    |       |             +-type=STRING
-    |       |             +-expr=
-    |       |             | +-GetStructField
-    |       |             |   +-type=STRUCT
-    |       |             |   +-expr=
-    |       |             |   | +-GetStructField
-    |       |             |   |   +-type=STRUCT>
-    |       |             |   |   +-expr=
-    |       |             |   |   | +-ColumnRef(type=STRUCT>>, column=TestNestedStructValueTable.value#1)
-    |       |             |   |   +-field_idx=1
-    |       |             |   +-field_idx=1
-    |       |             +-field_idx=1
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$equal(STRING, STRING) -> BOOL)
-    |           +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |           +-Literal(type=STRING, value="foo")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=111-129
-        +-column_ref=
-          +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
---
-ALTERNATION GROUPS:
-    T.,T.,,T.
-    T.,,T.,T.
-    T.,T.,T.
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#3 AS `$col1` [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.b#2, $query.$col1#3, $orderby.$orderbycol1#4]
-    |   +-expr_list=
-    |   | +-$orderbycol1#4 :=
-    |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |   |     +-Literal(type=STRING, value="b")
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       |   +-expr_list=
-    |       |   | +-$col1#3 :=
-    |       |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |       |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |       |   |     +-Literal(type=STRING, value="a")
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.b#2]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-b#2 :=
-    |       |           +-GetStructField
-    |       |             +-type=STRING
-    |       |             +-expr=
-    |       |             | +-GetStructField
-    |       |             |   +-type=STRUCT
-    |       |             |   +-expr=
-    |       |             |   | +-GetStructField
-    |       |             |   |   +-type=STRUCT>
-    |       |             |   |   +-expr=
-    |       |             |   |   | +-ColumnRef(type=STRUCT>>, column=TestNestedStructValueTable.value#1)
-    |       |             |   |   +-field_idx=1
-    |       |             |   +-field_idx=1
-    |       |             +-field_idx=1
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$equal(STRING, STRING) -> BOOL)
-    |           +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |           +-Literal(type=STRING, value="foo")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=109-129
-        +-column_ref=
-          +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
---
-ALTERNATION GROUPS:
-    T.,T.,,
-    T.,,T.,
-    T.,T.,
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#3 AS `$col1` [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.b#2, $query.$col1#3, $orderby.$orderbycol1#4]
-    |   +-expr_list=
-    |   | +-$orderbycol1#4 :=
-    |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |   |     +-Literal(type=STRING, value="b")
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       |   +-expr_list=
-    |       |   | +-$col1#3 :=
-    |       |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |       |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |       |   |     +-Literal(type=STRING, value="a")
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.b#2]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-b#2 :=
-    |       |           +-GetStructField
-    |       |             +-type=STRING
-    |       |             +-expr=
-    |       |             | +-GetStructField
-    |       |             |   +-type=STRUCT
-    |       |             |   +-expr=
-    |       |             |   | +-GetStructField
-    |       |             |   |   +-type=STRUCT>
-    |       |             |   |   +-expr=
-    |       |             |   |   | +-ColumnRef(type=STRUCT>>, column=TestNestedStructValueTable.value#1)
-    |       |             |   |   +-field_idx=1
-    |       |             |   +-field_idx=1
-    |       |             +-field_idx=1
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$equal(STRING, STRING) -> BOOL)
-    |           +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |           +-Literal(type=STRING, value="foo")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=109-127
-        +-column_ref=
-          +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
---
-ALTERNATION GROUPS:
-    T.,,,T.
-    T.,,T.
-    T.,T.
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#3 AS `$col1` [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.b#2, $query.$col1#3, $orderby.$orderbycol1#4]
-    |   +-expr_list=
-    |   | +-$orderbycol1#4 :=
-    |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |   |     +-Literal(type=STRING, value="b")
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       |   +-expr_list=
-    |       |   | +-$col1#3 :=
-    |       |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |       |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |       |   |     +-Literal(type=STRING, value="a")
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.b#2]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-b#2 :=
-    |       |           +-GetStructField
-    |       |             +-type=STRING
-    |       |             +-expr=
-    |       |             | +-GetStructField
-    |       |             |   +-type=STRUCT
-    |       |             |   +-expr=
-    |       |             |   | +-GetStructField
-    |       |             |   |   +-type=STRUCT>
-    |       |             |   |   +-expr=
-    |       |             |   |   | +-ColumnRef(type=STRUCT>>, column=TestNestedStructValueTable.value#1)
-    |       |             |   |   +-field_idx=1
-    |       |             |   +-field_idx=1
-    |       |             +-field_idx=1
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$equal(STRING, STRING) -> BOOL)
-    |           +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |           +-Literal(type=STRING, value="foo")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=107-127
-        +-column_ref=
-          +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
---
-ALTERNATION GROUPS:
-    T.,,,
-    T.,,
-    T.,
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#3 AS `$col1` [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.b#2, $query.$col1#3, $orderby.$orderbycol1#4]
-    |   +-expr_list=
-    |   | +-$orderbycol1#4 :=
-    |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |   |     +-Literal(type=STRING, value="b")
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       |   +-expr_list=
-    |       |   | +-$col1#3 :=
-    |       |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |       |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |       |   |     +-Literal(type=STRING, value="a")
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.b#2]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-b#2 :=
-    |       |           +-GetStructField
-    |       |             +-type=STRING
-    |       |             +-expr=
-    |       |             | +-GetStructField
-    |       |             |   +-type=STRUCT
-    |       |             |   +-expr=
-    |       |             |   | +-GetStructField
-    |       |             |   |   +-type=STRUCT>
-    |       |             |   |   +-expr=
-    |       |             |   |   | +-ColumnRef(type=STRUCT>>, column=TestNestedStructValueTable.value#1)
-    |       |             |   |   +-field_idx=1
-    |       |             |   +-field_idx=1
-    |       |             +-field_idx=1
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$equal(STRING, STRING) -> BOOL)
-    |           +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |           +-Literal(type=STRING, value="foo")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=107-125
-        +-column_ref=
-          +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
---
-ALTERNATION GROUP: T.
---
-QueryStmt
-+-output_column_list=
-| +-$query.$col1#3 AS `$col1` [STRING]
-+-query=
-  +-OrderByScan
-    +-column_list=[$query.$col1#3]
-    +-is_ordered=TRUE
-    +-input_scan=
-    | +-ProjectScan
-    |   +-column_list=[$groupby.b#2, $query.$col1#3, $orderby.$orderbycol1#4]
-    |   +-expr_list=
-    |   | +-$orderbycol1#4 :=
-    |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |   |     +-Literal(type=STRING, value="b")
-    |   +-input_scan=
-    |     +-FilterScan
-    |       +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       +-input_scan=
-    |       | +-ProjectScan
-    |       |   +-column_list=[$groupby.b#2, $query.$col1#3]
-    |       |   +-expr_list=
-    |       |   | +-$col1#3 :=
-    |       |   |   +-FunctionCall(ZetaSQL:concat(STRING, repeated(1) STRING) -> STRING)
-    |       |   |     +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |       |   |     +-Literal(type=STRING, value="a")
-    |       |   +-input_scan=
-    |       |     +-AggregateScan
-    |       |       +-column_list=[$groupby.b#2]
-    |       |       +-input_scan=
-    |       |       | +-TableScan(column_list=[TestNestedStructValueTable.value#1], table=TestNestedStructValueTable, column_index_list=[0], alias="T")
-    |       |       +-group_by_list=
-    |       |         +-b#2 :=
-    |       |           +-GetStructField
-    |       |             +-type=STRING
-    |       |             +-expr=
-    |       |             | +-GetStructField
-    |       |             |   +-type=STRUCT
-    |       |             |   +-expr=
-    |       |             |   | +-GetStructField
-    |       |             |   |   +-type=STRUCT>
-    |       |             |   |   +-expr=
-    |       |             |   |   | +-ColumnRef(type=STRUCT>>, column=TestNestedStructValueTable.value#1)
-    |       |             |   |   +-field_idx=1
-    |       |             |   +-field_idx=1
-    |       |             +-field_idx=1
-    |       +-filter_expr=
-    |         +-FunctionCall(ZetaSQL:$equal(STRING, STRING) -> BOOL)
-    |           +-ColumnRef(type=STRING, column=$groupby.b#2)
-    |           +-Literal(type=STRING, value="foo")
-    +-order_by_item_list=
-      +-OrderByItem
-        +-parse_location=105-125
-        +-column_ref=
-          +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
---
-ALTERNATION GROUP: 
---
 QueryStmt
 +-output_column_list=
 | +-$query.$col1#3 AS `$col1` [STRING]
@@ -3864,7 +3127,6 @@ QueryStmt
     |           +-Literal(type=STRING, value="foo")
     +-order_by_item_list=
       +-OrderByItem
-        +-parse_location=105-123
         +-column_ref=
           +-ColumnRef(type=STRING, column=$orderby.$orderbycol1#4)
 ==
diff --git a/zetasql/analyzer/testdata/with_recursive.test b/zetasql/analyzer/testdata/with_recursive.test
index 560eb0fa7..08b295068 100644
--- a/zetasql/analyzer/testdata/with_recursive.test
+++ b/zetasql/analyzer/testdata/with_recursive.test
@@ -3387,7 +3387,6 @@ QueryStmt
     |   |   +-unit=ROWS
     |   +-order_by_item_list=
     |     +-OrderByItem
-    |       +-parse_location=167-168
     |       +-column_ref=
     |         +-ColumnRef(type=INT64, column=t.n#6)
     +-recursive=TRUE
@@ -3471,7 +3470,6 @@ QueryStmt
     |           |           |   |   +-unit=ROWS
     |           |           |   +-order_by_item_list=
     |           |           |     +-OrderByItem
-    |           |           |       +-parse_location=236-237
     |           |           |       +-column_ref=
     |           |           |         +-ColumnRef(type=INT64, column=tbl.n#5)
     |           |           +-join_expr=
@@ -3564,7 +3562,6 @@ QueryStmt
     |           |           |   |   +-unit=ROWS
     |           |           |   +-order_by_item_list=
     |           |           |     +-OrderByItem
-    |           |           |       +-parse_location=222-223
     |           |           |       +-column_ref=
     |           |           |         +-ColumnRef(type=INT64, column=tbl.n#4)
     |           |           +-right_scan=
diff --git a/zetasql/base/BUILD b/zetasql/base/BUILD
index aa1f00da4..4db0cd273 100644
--- a/zetasql/base/BUILD
+++ b/zetasql/base/BUILD
@@ -850,3 +850,32 @@ cc_test(
         "//zetasql/base/testing:zetasql_gtest_main",
     ],
 )
+
+cc_library(
+    name = "optional_ref",
+    hdrs = ["optional_ref.h"],
+    deps = [
+        "@com_google_absl//absl/base:core_headers",
+    ],
+)
+
+cc_library(
+    name = "optional_ref_matchers",
+    testonly = 1,
+    hdrs = ["optional_ref_matchers.h"],
+    deps = [
+        "//zetasql/base/testing:zetasql_gtest_main",
+    ],
+)
+
+cc_test(
+    name = "optional_ref_test",
+    srcs = ["optional_ref_test.cc"],
+    deps = [
+        ":logging",
+        ":optional_ref",
+        ":optional_ref_matchers",
+        "//zetasql/base/testing:zetasql_gtest_main",
+        "@com_google_absl//absl/strings",
+    ],
+)
diff --git a/zetasql/base/optional_ref.h b/zetasql/base/optional_ref.h
new file mode 100644
index 000000000..6b041e154
--- /dev/null
+++ b/zetasql/base/optional_ref.h
@@ -0,0 +1,224 @@
+//
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef THIRD_PARTY_ZETASQL_ZETASQL_BASE_OPTIONAL_REF_H_
+#define THIRD_PARTY_ZETASQL_ZETASQL_BASE_OPTIONAL_REF_H_
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "absl/base/attributes.h"
+#include "absl/base/macros.h"
+#include "absl/base/optimization.h"
+
+namespace zetasql_base {
+
+// `optional_ref` looks and feels like `std::optional`, but instead of
+// owning the underlying value, it retains a reference to the value it accepts
+// in its constructor.
+//
+// It can be constructed in the following ways:
+//   * optional_ref ref;
+//   * optional_ref ref = std::nullopt;
+//   * T foo; optional_ref ref = foo;
+//   * std::optional foo; optional_ref ref = foo;
+//   * T* foo = ...; optional_ref ref = foo;
+//   * optional_ref foo; optional_ref ref = foo;
+//
+// Since it is trivially copyable and destructible, it should be passed by
+// value.
+//
+// Other properties:
+//   * Assignment is not allowed. Example:
+//       optional_ref ref;
+//       // Compile error.
+//       ref = 2;
+//
+//   * Equality is only supported with respect to std::nullopt. Example:
+//       optional_ref ref1, ref2;
+//       // OK.
+//       ref1 == std::nullopt;
+//       // Compile error.
+//       ref1 == ref2;
+//
+//   * operator bool() is intentionally not defined, as it would be error prone
+//     for optional_ref.
+//
+// Example usage, assuming some type `T` that is expensive to copy:
+//   void ProcessT(optional_ref input) {
+//     if (!input.has_value()) {
+//       // Handle empty case.
+//       return;
+//     }
+//     const T& val = *input;
+//     // Do something with val.
+//   }
+//
+//   ProcessT(std::nullopt);
+//   ProcessT(BuildT());
+template 
+class optional_ref {
+ public:
+  using value_type = T;
+
+  constexpr optional_ref() : ptr_(nullptr) {}
+  constexpr optional_ref(  // NOLINT
+      std::nullopt_t)
+      : ptr_(nullptr) {}
+
+  // Constructor given a concrete value.
+  constexpr optional_ref(  // NOLINT
+      T& input ABSL_ATTRIBUTE_LIFETIME_BOUND)
+      : ptr_(std::addressof(input)) {}
+
+  // Constructors given an existing std::optional value.
+  // Templated on the input optional's type to avoid implicit conversions.
+  template  &&
+                            std::is_same_v, std::decay_t>>>
+  constexpr optional_ref(  // NOLINT
+      const std::optional& input ABSL_ATTRIBUTE_LIFETIME_BOUND)
+      : ptr_(input.has_value() ? std::addressof(*input) : nullptr) {}
+  template , std::decay_t>>>
+  constexpr optional_ref(  // NOLINT
+      std::optional& input ABSL_ATTRIBUTE_LIFETIME_BOUND)
+      : ptr_(input.has_value() ? std::addressof(*input) : nullptr) {}
+
+  // Constructor given a T*, where nullptr indicates empty/absent.
+  constexpr optional_ref(  // NOLINT
+      T* input ABSL_ATTRIBUTE_LIFETIME_BOUND)
+      : ptr_(input) {}
+
+  // Don't allow naked nullptr as input, as this creates confusion in the case
+  // of optional_ref. Use std::nullopt instead to create an empty
+  // optional_ref.
+  constexpr optional_ref(  // NOLINT
+      std::nullptr_t) = delete;
+
+  // Constructor to allow non-const ==> const conversions.
+  template >>
+  constexpr optional_ref(  // NOLINT
+      const optional_ref>& input)
+      : ptr_(input.as_pointer()) {}
+
+  // Copying is allowed.
+  optional_ref(const optional_ref&) = default;
+  // Assignment is not allowed.
+  optional_ref& operator=(const optional_ref&) = delete;
+
+  // Determines whether the `optional_ref` contains a value. Returns `false` if
+  // and only if `*this` is empty.
+  constexpr bool has_value() const { return ptr_ != nullptr; }
+
+  // Returns a reference to an `optional_ref`s underlying value. The constness
+  // and lvalue/rvalue-ness of the `optional_ref` is preserved to the view of
+  // the `T` sub-object. Throws the same error as `std::optional`'s `value()`
+  // when the `optional_ref` is empty.
+  constexpr T& value() const {
+    return ABSL_PREDICT_TRUE(ptr_ != nullptr)
+               ? *ptr_
+               // Replicate the same error logic as in `std::optional`'s
+               // `value()`. It either throws an exception or aborts the
+               // program.
+               : (std::optional().value(), *ptr_);
+  }
+
+  // Returns the value iff *this has a value, otherwise returns `default_value`.
+  template 
+  constexpr T value_or(U&& default_value) const {
+    // Instantiate std::optional::value_or(U) to trigger its static_asserts.
+    if (false)
+      (void)(const std::optional){}.value_or(std::forward(default_value));
+    return ptr_ != nullptr ? *ptr_
+                           : static_cast(std::forward(default_value));
+  }
+
+  // Accesses the underlying `T` value of an `optional_ref`. If the
+  // `optional_ref` is empty, behavior is undefined.
+  constexpr T& operator*() const {
+    ABSL_HARDENING_ASSERT(ptr_ != nullptr);
+    return *ptr_;
+  }
+  constexpr T* operator->() const {
+    ABSL_HARDENING_ASSERT(ptr_ != nullptr);
+    return ptr_;
+  }
+
+  // Convenience function to represent the `optional_ref` as a `T*` pointer.
+  constexpr T* as_pointer() const { return ptr_; }
+  // Convenience function to represent the `optional_ref` as an `optional`,
+  // which incurs a copy when the `optional_ref` is non-empty. The template type
+  // allows for implicit type conversion; example:
+  //   optional_ref a = ...;
+  //   std::optional b = a.as_optional();
+  template >
+  constexpr std::optional as_optional() const {
+    if (ptr_ == nullptr) return std::nullopt;
+    return *ptr_;
+  }
+
+ private:
+  T* const ptr_;
+
+  // T constraint checks.  You can't have an optional of nullopt_t or
+  // in_place_t.
+  static_assert(!std::is_same_v>,
+                "optional_ref is not allowed.");
+  static_assert(!std::is_same_v>,
+                "optional_ref is not allowed.");
+};
+
+// Template type deduction guides:
+
+template 
+optional_ref(const T&) -> optional_ref;
+template 
+optional_ref(T&) -> optional_ref;
+
+template 
+optional_ref(const std::optional&) -> optional_ref;
+template 
+optional_ref(std::optional&) -> optional_ref;
+
+template 
+optional_ref(T*) -> optional_ref;
+
+// operator==, operator!=:
+
+template 
+constexpr bool operator==(optional_ref a, std::nullopt_t) {
+  return !a.has_value();
+}
+template 
+constexpr bool operator==(std::nullopt_t, optional_ref b) {
+  return !b.has_value();
+}
+template 
+constexpr bool operator!=(optional_ref a, std::nullopt_t) {
+  return a.has_value();
+}
+template 
+constexpr bool operator!=(std::nullopt_t, optional_ref b) {
+  return b.has_value();
+}
+
+}  // namespace zetasql_base
+
+#endif  // THIRD_PARTY_ZETASQL_ZETASQL_BASE_OPTIONAL_REF_H_
diff --git a/zetasql/base/optional_ref_matchers.h b/zetasql/base/optional_ref_matchers.h
new file mode 100644
index 000000000..6796ef68b
--- /dev/null
+++ b/zetasql/base/optional_ref_matchers.h
@@ -0,0 +1,39 @@
+//
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef THIRD_PARTY_ZETASQL_ZETASQL_BASE_OPTIONAL_REF_MATCHERS_H_
+#define THIRD_PARTY_ZETASQL_ZETASQL_BASE_OPTIONAL_REF_MATCHERS_H_
+
+#include "gmock/gmock.h"
+
+namespace zetasql_base {
+namespace testing {
+
+// We define a custom matcher instead of reusing testing::Optional because
+// testing::Optional depends on operator bool() being defined. We avoid
+// defining operator bool() because it is error prone for optional_ref.
+MATCHER_P(HasValue, matcher, "") {
+  if (!arg.has_value()) {
+    *result_listener << "which is not engaged";
+    return false;
+  }
+  return ExplainMatchResult(matcher, *arg, result_listener);
+}
+
+}  // namespace testing
+}  // namespace zetasql_base
+
+#endif  // THIRD_PARTY_ZETASQL_ZETASQL_BASE_OPTIONAL_REF_MATCHERS_H_
diff --git a/zetasql/base/optional_ref_test.cc b/zetasql/base/optional_ref_test.cc
new file mode 100644
index 000000000..cf26ba5e9
--- /dev/null
+++ b/zetasql/base/optional_ref_test.cc
@@ -0,0 +1,223 @@
+//
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "zetasql/base/optional_ref.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "zetasql/base/logging.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "zetasql/base/optional_ref_matchers.h"
+
+namespace zetasql_base {
+namespace {
+
+using ::zetasql_base::testing::HasValue;
+using ::testing::Pointee;
+
+TEST(OptionalRefTest, SimpleType) {
+  int val = 5;
+  optional_ref ref = optional_ref(val);
+  EXPECT_THAT(ref, HasValue(5));
+  EXPECT_TRUE(ref.has_value());
+  EXPECT_EQ(*ref, val);
+  EXPECT_EQ(ref.value(), val);
+}
+
+TEST(OptionalRefTest, SimpleConstType) {
+  const int val = 5;
+  optional_ref ref = optional_ref(val);
+  EXPECT_THAT(ref, HasValue(5));
+}
+
+TEST(OptionalRefTest, DefaultConstructed) {
+  optional_ref ref;
+  EXPECT_EQ(ref, std::nullopt);
+}
+
+TEST(OptionalRefTest, EmptyOptional) {
+  auto ref = optional_ref(std::nullopt);
+  EXPECT_EQ(ref, std::nullopt);
+}
+
+TEST(OptionalRefTest, OptionalType) {
+  const auto val = std::optional(5);
+  optional_ref ref = optional_ref(val);
+  EXPECT_THAT(ref, HasValue(5));
+
+  const std::optional empty;
+  optional_ref empty_ref = empty;
+  EXPECT_EQ(empty_ref, std::nullopt);
+
+  static_assert(
+      !std::is_constructible_v, const std::optional&>,
+      "optional_ref must not be constructible with const "
+      "std::optional");
+}
+
+class TestInterface {};
+class TestDerivedClass : public TestInterface {};
+
+TEST(OptionalRefTest, PointerCtor) {
+  int val = 5;
+  optional_ref ref = &val;
+  EXPECT_THAT(ref, HasValue(5));
+
+  auto auto_ref = optional_ref(&val);
+  static_assert(std::is_same_v>,
+                "optional_ref(T*) should deduce to optional_ref.");
+  EXPECT_THAT(auto_ref, HasValue(5));
+
+  int* foo = nullptr;
+  optional_ref empty_ref = foo;
+  EXPECT_EQ(empty_ref, std::nullopt);
+
+  optional_ref ptr_ref = foo;
+  EXPECT_THAT(ptr_ref, HasValue(nullptr));
+  static_assert(
+      !std::is_constructible_v, std::nullptr_t>,
+      "optional_ref should not be constructible with std::nullptr_t.");
+
+  // Pointer polymorphism works.
+  TestDerivedClass dc;
+  optional_ref dc_ref = &dc;
+  EXPECT_NE(dc_ref, std::nullopt);
+}
+
+TEST(OptionalRefTest, ImplicitCtor) {
+  const int val = 5;
+  optional_ref ref = val;
+  EXPECT_THAT(ref, HasValue(5));
+}
+
+TEST(OptionalRefTest, DoesNotCopy) {
+  // Non-copyable type.
+  auto val = std::make_unique(5);
+  optional_ref> ref = optional_ref(val);
+  EXPECT_THAT(ref, HasValue(Pointee(5)));
+}
+
+TEST(OptionalRefTest, DoesNotCopyConst) {
+  // Non-copyable type.
+  const auto val = std::make_unique(5);
+  optional_ref> ref = optional_ref(val);
+  EXPECT_THAT(ref, HasValue(Pointee(5)));
+}
+
+TEST(OptionalRefTest, RefCopyable) {
+  auto val = std::make_unique(5);
+  optional_ref> ref = optional_ref(val);
+  optional_ref> copy = ref;
+  EXPECT_THAT(copy, HasValue(Pointee(5)));
+}
+
+TEST(OptionalRefTest, ConstConvertible) {
+  auto val = std::make_unique(5);
+  optional_ref> ref = optional_ref(val);
+  optional_ref> copy = ref;
+  EXPECT_THAT(copy, HasValue(Pointee(5)));
+}
+
+TEST(OptionalRefTest, TriviallyCopyable) {
+  EXPECT_TRUE(std::is_trivially_copyable_v>>);
+}
+
+TEST(OptionalRefTest, TriviallyDestructible) {
+  EXPECT_TRUE(
+      std::is_trivially_destructible_v>>);
+}
+
+TEST(OptionalRefTest, RefNotAssignable) {
+  EXPECT_FALSE(std::is_copy_assignable_v>);
+  EXPECT_FALSE(std::is_move_assignable_v>);
+}
+
+struct TestStructWithCopy {
+  TestStructWithCopy() = default;
+  TestStructWithCopy(TestStructWithCopy&&) {
+    ABSL_LOG(FATAL) << "Move constructor should not be called";
+  }
+  TestStructWithCopy(const TestStructWithCopy&) {
+    ABSL_LOG(FATAL) << "Copy constructor should not be called";
+  }
+  TestStructWithCopy& operator=(const TestStructWithCopy&) {
+    ABSL_LOG(FATAL) << "Assign operator should not be called";
+  }
+};
+
+TEST(OptionalRefTest, DoesNotCopyUsingFatalCopyAssignOps) {
+  TestStructWithCopy val;
+  optional_ref ref = optional_ref(val);
+  EXPECT_NE(ref, std::nullopt);
+  EXPECT_NE(optional_ref(TestStructWithCopy{}), std::nullopt);
+}
+
+std::string AddExclamation(optional_ref input) {
+  if (!input.has_value()) {
+    return "";
+  }
+  return absl::StrCat(*input, "!");
+}
+
+TEST(OptionalRefTest, RefAsFunctionParameter) {
+  EXPECT_EQ(AddExclamation(std::nullopt), "");
+  EXPECT_EQ(AddExclamation(std::string("abc")), "abc!");
+  std::string s = "def";
+  EXPECT_EQ(AddExclamation(s), "def!");
+  EXPECT_EQ(AddExclamation(std::make_optional(s)), "def!");
+}
+
+TEST(OptionalRefTest, ValueOrWhenHasValue) {
+  std::optional val = 5;
+  EXPECT_EQ(optional_ref(val).value_or(2), 5);
+}
+
+TEST(OptionalRefTest, ValueOrWhenEmpty) {
+  std::optional val = std::nullopt;
+  EXPECT_EQ(optional_ref(val).value_or(2), 2);
+}
+
+TEST(OptionalRefTest, AsOptional) {
+  EXPECT_EQ(optional_ref().as_optional(), std::nullopt);
+  std::string val = "foo";
+  optional_ref ref = val;
+  static_assert(
+      std::is_same_v>,
+      "The type parameter of optional_ref should decay by default for the "
+      "return type in as_optional().");
+  std::optional opt_string = ref.as_optional();
+  EXPECT_THAT(opt_string, HasValue(val));
+
+  std::optional opt_view =
+      ref.as_optional();
+  EXPECT_THAT(opt_view, HasValue(val));
+}
+
+TEST(OptionalRefTest, Constexpr) {
+  static constexpr int foo = 123;
+  constexpr optional_ref ref(foo);
+  static_assert(ref.has_value() && *ref == foo && ref.value() == foo, "");
+}
+
+}  // namespace
+}  // namespace zetasql_base
diff --git a/zetasql/common/BUILD b/zetasql/common/BUILD
index 6a72a50cc..4cb8bba38 100644
--- a/zetasql/common/BUILD
+++ b/zetasql/common/BUILD
@@ -273,7 +273,6 @@ cc_library(
     deps = [
         ":string_util",
         "//zetasql/base",
-        "//zetasql/base:mathlimits",
         "//zetasql/base:mathutil",
         "@com_google_absl//absl/strings",
     ],
diff --git a/zetasql/common/builtin_enum_type.cc b/zetasql/common/builtin_enum_type.cc
index daa5b862f..3b8c3bbc5 100644
--- a/zetasql/common/builtin_enum_type.cc
+++ b/zetasql/common/builtin_enum_type.cc
@@ -23,14 +23,6 @@
 
 namespace zetasql {
 
-absl::Status GetToJsonBuiltinEnumTypes(
-    TypeFactory* type_factory, const ZetaSQLBuiltinFunctionOptions& options,
-    NameToTypeMap* types) {
-  ZETASQL_RETURN_IF_ERROR(
-      InsertType(types, options, types::UnsupportedFieldsEnumType()));
-  return absl::OkStatus();
-}
-
 absl::Status GetStandaloneBuiltinEnumTypes(
     TypeFactory* type_factory, const ZetaSQLBuiltinFunctionOptions& options,
     NameToTypeMap* types) {
diff --git a/zetasql/common/builtin_function_internal.h b/zetasql/common/builtin_function_internal.h
index 7c353264f..df6796835 100644
--- a/zetasql/common/builtin_function_internal.h
+++ b/zetasql/common/builtin_function_internal.h
@@ -725,10 +725,6 @@ absl::Status GetArrayZipFunctions(
     TypeFactory* type_factory, const ZetaSQLBuiltinFunctionOptions& options,
     NameToFunctionMap* functions, NameToTypeMap* types);
 
-absl::Status GetToJsonBuiltinEnumTypes(
-    TypeFactory* type_factory, const ZetaSQLBuiltinFunctionOptions& options,
-    NameToTypeMap* types);
-
 absl::Status GetStandaloneBuiltinEnumTypes(
     TypeFactory* type_factory, const ZetaSQLBuiltinFunctionOptions& options,
     NameToTypeMap* types);
@@ -737,9 +733,10 @@ void GetSubscriptFunctions(TypeFactory* type_factory,
                            const ZetaSQLBuiltinFunctionOptions& options,
                            NameToFunctionMap* functions);
 
-void GetJSONFunctions(TypeFactory* type_factory,
-                      const ZetaSQLBuiltinFunctionOptions& options,
-                      NameToFunctionMap* functions);
+absl::Status GetJSONFunctions(TypeFactory* type_factory,
+                              const ZetaSQLBuiltinFunctionOptions& options,
+                              NameToFunctionMap* functions,
+                              NameToTypeMap* types);
 
 absl::Status GetNumericFunctions(TypeFactory* type_factory,
                                  const ZetaSQLBuiltinFunctionOptions& options,
diff --git a/zetasql/common/builtin_function_internal_3.cc b/zetasql/common/builtin_function_internal_3.cc
index c2849ba2b..33bc16ad2 100644
--- a/zetasql/common/builtin_function_internal_3.cc
+++ b/zetasql/common/builtin_function_internal_3.cc
@@ -1238,9 +1238,10 @@ void GetSubscriptFunctions(TypeFactory* type_factory,
                                           /*offset_or_ordinal=*/"ORDINAL")));
 }
 
-void GetJSONFunctions(TypeFactory* type_factory,
-                      const ZetaSQLBuiltinFunctionOptions& options,
-                      NameToFunctionMap* functions) {
+absl::Status GetJSONFunctions(TypeFactory* type_factory,
+                              const ZetaSQLBuiltinFunctionOptions& options,
+                              NameToFunctionMap* functions,
+                              NameToTypeMap* types) {
   const Type* int32_type = types::Int32Type();
   const Type* int64_type = types::Int64Type();
   const Type* uint32_type = types::Uint32Type();
@@ -1359,25 +1360,45 @@ void GetJSONFunctions(TypeFactory* type_factory,
          {json_type, optional_json_path_argument},
          FN_JSON_VALUE_ARRAY_JSON});
 
-    FunctionArgumentTypeList to_json_args(
-        {ARG_TYPE_ANY_1,
-         {bool_type,
-          FunctionArgumentTypeOptions()
-              .set_cardinality(FunctionEnums::OPTIONAL)
-              .set_argument_name("stringify_wide_numbers", kNamedOnly)
-              .set_default(values::Bool(false))}});
-    if (options.language_options.LanguageFeatureEnabled(
-            FEATURE_TO_JSON_UNSUPPORTED_FIELDS)) {
-      to_json_args.push_back(
-          {unsupported_fields_type,
-           FunctionArgumentTypeOptions()
-               .set_cardinality(FunctionEnums::OPTIONAL)
-               .set_argument_name("unsupported_fields", kNamedOnly)
-               .set_default(values::Enum(unsupported_fields_type,
-                                         functions::UnsupportedFields::FAIL))});
+    {
+      std::vector signatures = {
+          {json_type,
+           {ARG_TYPE_ANY_1,
+            {bool_type,
+             FunctionArgumentTypeOptions()
+                 .set_cardinality(FunctionEnums::OPTIONAL)
+                 .set_argument_name("stringify_wide_numbers", kNamedOnly)
+                 .set_default(values::Bool(false))}},
+           FN_TO_JSON},
+      };
+      if (options.language_options.LanguageFeatureEnabled(
+              FEATURE_TO_JSON_UNSUPPORTED_FIELDS)) {
+        signatures.push_back(
+            {json_type,
+             {ARG_TYPE_ANY_1,
+              {bool_type,
+               FunctionArgumentTypeOptions()
+                   .set_cardinality(FunctionEnums::OPTIONAL)
+                   .set_argument_name("stringify_wide_numbers", kNamedOnly)
+                   .set_default(values::Bool(false))},
+              {
+                  unsupported_fields_type,
+                  FunctionArgumentTypeOptions()
+                      .set_cardinality(FunctionEnums::OPTIONAL)
+                      .set_argument_name("unsupported_fields", kNamedOnly)
+                      .set_default(
+                          values::Enum(unsupported_fields_type,
+                                       functions::UnsupportedFieldsEnum::FAIL)),
+              }},
+             FN_TO_JSON_UNSUPPORTED_FIELDS});
+
+        ZETASQL_RETURN_IF_ERROR(InsertFunctionAndTypes(
+            functions, types, options, "to_json", SCALAR, signatures,
+            /*function_options=*/{}, {unsupported_fields_type}));
+      } else {
+        InsertFunction(functions, options, "to_json", SCALAR, signatures);
+      }
     }
-    InsertFunction(functions, options, "to_json", SCALAR,
-                   {{json_type, to_json_args, FN_TO_JSON}});
 
     InsertFunction(
         functions, options, "parse_json", SCALAR,
@@ -1748,6 +1769,7 @@ void GetJSONFunctions(TypeFactory* type_factory,
       FunctionOptions()
           .AddRequiredLanguageFeature(FEATURE_JSON_TYPE)
           .AddRequiredLanguageFeature(FEATURE_JSON_KEYS_FUNCTION));
+  return absl::OkStatus();
 }
 
 absl::Status GetNumericFunctions(TypeFactory* type_factory,
diff --git a/zetasql/common/builtin_function_map.cc b/zetasql/common/builtin_function_map.cc
index 3a59ae302..d3cda0ba0 100644
--- a/zetasql/common/builtin_function_map.cc
+++ b/zetasql/common/builtin_function_map.cc
@@ -371,7 +371,7 @@ void GetMapCoreFunctions(TypeFactory* type_factory,
       },
       FunctionOptions().AddRequiredLanguageFeature(FEATURE_V_1_4_MAP_TYPE));
 
-  const FunctionArgumentTypeList map_insert_argument_types = {
+  const FunctionArgumentTypeList arglist_map_and_kv_pairs = {
       input_map_argument_type,
       ARG_TYPE_ANY_1,
       ARG_TYPE_ANY_2,
@@ -384,12 +384,27 @@ void GetMapCoreFunctions(TypeFactory* type_factory,
       FunctionOptions().AddRequiredLanguageFeature(FEATURE_V_1_4_MAP_TYPE);
   InsertFunction(
       functions, options, "map_insert", Function::SCALAR,
-      {{ARG_MAP_TYPE_ANY_1_2, map_insert_argument_types, FN_MAP_INSERT}},
+      {{ARG_MAP_TYPE_ANY_1_2, arglist_map_and_kv_pairs, FN_MAP_INSERT}},
       map_insert_function_options);
   InsertFunction(functions, options, "map_insert_or_replace", Function::SCALAR,
-                 {{ARG_MAP_TYPE_ANY_1_2, map_insert_argument_types,
+                 {{ARG_MAP_TYPE_ANY_1_2, arglist_map_and_kv_pairs,
                    FN_MAP_INSERT_OR_REPLACE}},
                  map_insert_function_options);
+  InsertFunction(functions, options, "map_replace", Function::SCALAR,
+                 {
+                     {ARG_MAP_TYPE_ANY_1_2, arglist_map_and_kv_pairs,
+                      FN_MAP_REPLACE_KV_PAIRS},
+                     {ARG_MAP_TYPE_ANY_1_2,
+                      {input_map_argument_type,
+                       ARG_TYPE_ANY_1,
+                       {ARG_TYPE_ANY_1, FunctionArgumentType::REPEATED},
+                       FunctionArgumentType::Lambda(
+                           {ARG_TYPE_ANY_2}, ARG_TYPE_ANY_2,
+                           FunctionArgumentTypeOptions().set_argument_name(
+                               "value", kPositionalOnly))},
+                      FN_MAP_REPLACE_K_REPEATED_V_LAMBDA},
+                 },
+                 map_insert_function_options);
 }
 
 }  // namespace zetasql
diff --git a/zetasql/compliance/BUILD b/zetasql/compliance/BUILD
index 0df75ed74..687b98d6f 100644
--- a/zetasql/compliance/BUILD
+++ b/zetasql/compliance/BUILD
@@ -55,7 +55,6 @@ cc_library(
         "//zetasql/common:status_payload_utils",
         "//zetasql/public:analyzer",
         "//zetasql/public:analyzer_options",
-        "//zetasql/public:catalog",
         "//zetasql/public:language_options",
         "//zetasql/public:options_cc_proto",
         "//zetasql/public:parse_helpers",
diff --git a/zetasql/compliance/depth_limit_detector_test_cases.cc b/zetasql/compliance/depth_limit_detector_test_cases.cc
index 3bba936ff..08587051c 100644
--- a/zetasql/compliance/depth_limit_detector_test_cases.cc
+++ b/zetasql/compliance/depth_limit_detector_test_cases.cc
@@ -521,6 +521,26 @@ AllDepthLimitDetectorTestCases() {
                       LanguageFeature::FEATURE_V_1_3_ALLOW_CONSECUTIVE_ON,
                   },
           },
+          {
+              .depth_limit_test_case_name = "to_json_struct_array",
+              .depth_limit_template = {"SELECT TO_JSON((SELECT ",
+                                       R({"STRUCT(["}), "1", R({"] AS f)"}),
+                                       "))", R({"[\"f\"][0]"})},
+              .depth_limit_required_features =
+                  {
+                      LanguageFeature::FEATURE_JSON_TYPE,
+                  },
+          },
+          {
+              .depth_limit_test_case_name = "to_json_nested_struct",
+              .depth_limit_template = {"SELECT TO_JSON((SELECT ",
+                                       R({"STRUCT("}), "1", R({" AS f)"}), "))",
+                                       R({"[\"f\"]"})},
+              .depth_limit_required_features =
+                  {
+                      LanguageFeature::FEATURE_JSON_TYPE,
+                  },
+          },
           {
               .depth_limit_test_case_name = "with_union_all",
               .depth_limit_template = {"WITH ",
diff --git a/zetasql/compliance/functions_testlib_string_5.cc b/zetasql/compliance/functions_testlib_string_5.cc
index b122798ef..f58a79240 100644
--- a/zetasql/compliance/functions_testlib_string_5.cc
+++ b/zetasql/compliance/functions_testlib_string_5.cc
@@ -24,6 +24,7 @@
 #include "zetasql/testing/using_test_value.cc"  // NOLINT
 #include "absl/status/status.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 
 namespace zetasql {
 
@@ -765,7 +766,7 @@ struct FunctionTestWithCollator {
 };
 
 std::vector GetFunctionTestCalls(
-    const std::vector& tests, bool skip_collation) {
+    absl::Span tests, bool skip_collation) {
   std::vector function_test_calls;
   for (const auto& test : tests) {
     for (const auto& result : test.results) {
diff --git a/zetasql/compliance/run_compliance_driver.cc b/zetasql/compliance/run_compliance_driver.cc
index 88f1076d7..1a10e4669 100644
--- a/zetasql/compliance/run_compliance_driver.cc
+++ b/zetasql/compliance/run_compliance_driver.cc
@@ -16,23 +16,32 @@
 
 #include 
 
-#include 
 #include 
 #include 
 #include 
 #include 
 #include 
-#include 
 #include 
 
 #include "zetasql/base/init_google.h"
 #include "zetasql/base/logging.h"
 #include "zetasql/base/fileutils.h"
+#include "zetasql/base/helpers.h"
+#include "zetasql/base/options.h"
 #include "zetasql/common/options_utils.h"
 #include "zetasql/compliance/test_driver.h"
 #include "zetasql/compliance/test_driver.pb.h"
+#include "zetasql/public/analyzer_options.h"
+#include "zetasql/public/builtin_function_options.h"
 #include "zetasql/public/simple_catalog.h"
+#include "zetasql/public/types/annotation.h"
+#include "zetasql/public/types/type_factory.h"
 #include "zetasql/public/value.h"
+#include "absl/flags/flag.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/string_view.h"
+#include "google/protobuf/descriptor.h"
 
 namespace zetasql {
 struct QueryParameterFlagValue {
diff --git a/zetasql/compliance/sql_test_base.cc b/zetasql/compliance/sql_test_base.cc
index dfce879df..f70e79a5d 100644
--- a/zetasql/compliance/sql_test_base.cc
+++ b/zetasql/compliance/sql_test_base.cc
@@ -55,7 +55,6 @@
 #include "zetasql/compliance/test_util.h"
 #include "zetasql/public/analyzer.h"
 #include "zetasql/public/analyzer_options.h"
-#include "zetasql/public/catalog.h"
 #include "zetasql/public/functions/string.h"
 #include "zetasql/public/language_options.h"
 #include "zetasql/public/options.pb.h"
diff --git a/zetasql/compliance/test_driver.proto b/zetasql/compliance/test_driver.proto
index 5905f9931..702709c47 100644
--- a/zetasql/compliance/test_driver.proto
+++ b/zetasql/compliance/test_driver.proto
@@ -20,7 +20,6 @@ package zetasql;
 
 import "zetasql/public/annotation.proto";
 import "zetasql/public/options.proto";
-
 import "zetasql/public/type.proto";
 import "zetasql/public/value.proto";
 
diff --git a/zetasql/compliance/testdata/map_functions.test b/zetasql/compliance/testdata/map_functions.test
index c3d087d2e..b64c2341a 100644
--- a/zetasql/compliance/testdata/map_functions.test
+++ b/zetasql/compliance/testdata/map_functions.test
@@ -721,3 +721,56 @@ SELECT
   MAP_INSERT_OR_REPLACE(MAP_FROM_ARRAY([('a', 1)]), NULL, 2, CAST(NULL AS STRING), 2);
 --
 ERROR: generic::out_of_range: Key provided more than once as argument: NULL
+==
+
+[name=map_replace]
+SELECT
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1)]), 'a', 2) as one_element_one_replaced,
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1), ('b', 2)]), 'a', 2) as two_elements_one_replaced,
+  MAP_REPLACE(MAP_FROM_ARRAY([('a', 1), ('b', 2)]), 'a', 2, 'b', 1) as two_elements_two_replaced;
+--
+ARRAY,
+        two_elements_one_replaced MAP,
+        two_elements_two_replaced MAP
+      >>[{{"a": 2}, {"a": 2, "b": 2}, {"a": 2, "b": 1}}]
+==
+
+[name=map_replace_error_nonexistent_key]
+SELECT MAP_REPLACE(MAP_FROM_ARRAY([('a', 1), ('b', 2)]), 'b', 3, 'a', 2, 'c', 1);
+--
+ERROR: generic::out_of_range: Key does not exist in map: "c"
+==
+
+[name=map_replace_error_duplicate_key_arg]
+SELECT MAP_REPLACE(MAP_FROM_ARRAY([('a', 1), ('b', 2)]), 'a', 2, 'b', 3, 'a', 0);
+--
+ERROR: generic::out_of_range: Key provided more than once as argument: "a"
+==
+
+[name=map_replace_null_map]
+SELECT
+  MAP_REPLACE(NULL, 'b', 2) as null_map,
+  MAP_REPLACE(NULL, NULL, NULL) as null_map_null_key,
+  MAP_REPLACE(NULL, NULL, NULL) as null_map_null_key_and_value;
+--
+ARRAY,
+        null_map_null_key MAP,
+        null_map_null_key_and_value MAP
+      >>[{NULL, NULL, NULL}]
+==
+
+[name=map_modify_fns_null_map_duplicate_key_arg]
+# All map modification functions should return NULL if the map is NULL, and this
+# takes precedence over the duplicate key argument error.
+SELECT
+  MAP_INSERT(NULL, 'b', 2, 'b', 3) as map_insert,
+  MAP_INSERT_OR_REPLACE(NULL, 'b', 2, 'b', 3) as map_insert_or_replace,
+  MAP_REPLACE(NULL, 'b', 2, 'b', 3) as map_replace;
+--
+ARRAY,
+        map_insert_or_replace MAP,
+        map_replace MAP
+      >>[{NULL, NULL, NULL}]
diff --git a/zetasql/examples/pipe_queries/BUILD b/zetasql/examples/pipe_queries/BUILD
new file mode 100644
index 000000000..5010c4168
--- /dev/null
+++ b/zetasql/examples/pipe_queries/BUILD
@@ -0,0 +1,35 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+load("//zetasql/tools/execute_query:build_rules.bzl", "execute_query_test")
+
+execute_query_test(
+    name = "execute_pipe_examples_test",
+    args = ["--catalog=tpch"],
+    sql_file = "pipe_examples.sql",
+)
+
+execute_query_test(
+    name = "execute_pipe_pivot_test",
+    args = ["--catalog=tpch"],
+    sql_file = "pipe_pivot.sql",
+)
+
+execute_query_test(
+    name = "execute_walkthrough_7day_test",
+    args = ["--catalog=tpch"],
+    sql_file = "walkthrough_7day.sql",
+)
diff --git a/zetasql/examples/pipe_queries/README.md b/zetasql/examples/pipe_queries/README.md
new file mode 100644
index 000000000..52c400dd6
--- /dev/null
+++ b/zetasql/examples/pipe_queries/README.md
@@ -0,0 +1,15 @@
+This directory contains some example scripts demonstrating
+[SQL pipe syntax](https://github.com/google/zetasql/blob/master/docs/pipe-syntax.md).
+
+These mostly use tables from the TPC-H schema. For more information, see
+[examples/tpch](https://github.com/google/zetasql/tree/master/zetasql/examples/tpch)
+which also includes standard TPC-H queries in standard syntax and pipe syntax.
+
+The scripts here can be run in the `execute_query` tool by selecting the `tpch`
+catalog in the web UI with `--web` or using `--catalog=tpch` flag on the command
+line.
+See [execute\_query](https://github.com/google/zetasql/blob/master/execute_query.md) for more details on this tool.
+
+For more information on pipe query syntax, see the
+[reference documentation](https://github.com/google/zetasql/blob/master/docs/pipe-syntax.md)
+and [research paper](https://research.google/pubs/pub1005959/).
diff --git a/zetasql/examples/pipe_queries/pipe_examples.sql b/zetasql/examples/pipe_queries/pipe_examples.sql
new file mode 100644
index 000000000..98ab1ec32
--- /dev/null
+++ b/zetasql/examples/pipe_queries/pipe_examples.sql
@@ -0,0 +1,218 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+-- This file has example queries demonstrating several features of
+-- ZetaSQL pipe syntax, running against the TPCH table schemas.
+--
+-- The queries are runnable in the `execute_query` query tool.
+-- Run with
+--   execute_query --catalog=tpch --web
+-- and then paste in this script and execute.
+
+--
+-- First example
+-- -------------
+--
+
+-- Many operators look just like the standard SQL clauses, but can be applied in
+-- any order, any number of times.
+FROM Orders
+|> SELECT o_orderkey, o_orderpriority, o_orderdate AS date
+|> SELECT *, EXTRACT(MONTH FROM date) AS month
+|> WHERE month = 2
+|> WHERE o_orderpriority = '1-URGENT'
+|> ORDER BY date
+|> LIMIT 20;
+
+--
+-- Two-level aggregation example
+-- -----------------------------
+--
+
+-- Standard syntax - inside-out data flow starting from the middle of a subquery.
+SELECT c_count, COUNT(*) AS custdist
+FROM
+  (
+    SELECT c_custkey, COUNT(o_orderkey) c_count
+    FROM customer
+    LEFT OUTER JOIN orders
+      ON c_custkey = o_custkey
+    GROUP BY c_custkey
+  ) AS c_orders
+GROUP BY c_count
+ORDER BY custdist DESC, c_count DESC;
+
+-- Pipe syntax - top-to-bottom linear data flow.
+FROM customer
+|> LEFT OUTER JOIN orders ON c_custkey = o_custkey
+|> AGGREGATE COUNT(o_orderkey) c_count
+   GROUP BY c_custkey
+|> AGGREGATE COUNT(*) AS custdist
+   GROUP BY c_count
+|> ORDER BY custdist DESC, c_count DESC;
+
+--
+-- FROM queries - starting a query with FROM
+-- -----------------------------------------
+--
+
+-- Querying with just a FROM clause - returns the whole table.
+FROM Region;
+
+-- A standard FROM clause containing a JOIN.
+FROM Nation JOIN Region ON n_regionkey = r_regionkey;
+
+-- A standard FROM clause with comma joins, with post-filtering in a WHERE.
+FROM Nation, Region
+|> WHERE n_regionkey = r_regionkey;
+
+--
+-- JOIN works as a pipe operator, using the usual syntax
+-- -----------------------------------------------------
+--
+
+FROM Nation
+|> JOIN Region ON n_regionkey = r_regionkey;
+
+-- Same thing with a LEFT JOIN
+FROM Nation
+|> LEFT JOIN Region ON n_regionkey = r_regionkey AND r_name LIKE 'A%';
+
+-- Join with UNNEST of an array
+FROM Nation
+|> SELECT N_NAME, split(n_name, '') AS letters
+|> JOIN UNNEST(letters) AS letter
+|> WHERE letter = 'U';
+
+--
+-- SELECT, and other syntaxes to update columns
+-- --------------------------------------------
+--
+
+-- SELECT supports all standard syntax SELECT features and modifiers.
+FROM Region
+|> SELECT *;
+
+FROM Nation
+|> SELECT DISTINCT n_regionkey;
+
+FROM Region
+|> SELECT r_name, length(r_name) AS name_length;
+
+-- Alternatively, use EXTEND to compute an expression and add it, keeping
+-- existing columns.
+FROM Region
+|> EXTEND length(r_name) AS name_length;
+
+-- Applying SELECT multiple times.  Each can reference previously selected
+-- columns.
+FROM Region
+|> SELECT r_name
+|> SELECT r_name, LOWER(r_name) AS lower_name
+|> SELECT lower_name, SUBSTR(r_name, 1, 1) || SUBSTR(lower_name, 2) AS mixed_name;
+
+-- Same thing, using EXTEND.
+FROM Region
+|> SELECT r_name
+|> EXTEND LOWER(r_name) AS lower_name
+|> EXTEND SUBSTR(r_name, 1, 1) || SUBSTR(lower_name, 2) AS mixed_name;
+
+-- DROP: Drop columns I don't want in the output.
+FROM Nation
+|> DROP n_regionkey, n_comment;
+
+-- SET: I can use SET to replace a column with a computed expression.
+-- I can even apply it multiple times to update a column value incrementally.
+FROM Region
+|> SET r_name = INITCAP(r_name)
+|> SET r_name = CONCAT(r_name, "!");
+
+-- RENAME: I can use RENAME to change the name of an output column.
+FROM Region
+|> DROP r_comment
+|> RENAME r_name AS RegionName;
+
+--
+-- AGGREGATE in pipe syntax
+-- ------------------------
+--
+
+-- Full-table aggregation
+FROM Orders
+|> AGGREGATE COUNT(*) AS num_orders, COUNT(DISTINCT o_custkey) AS num_customers;
+
+-- Aggregation with grouping
+FROM Orders
+|> AGGREGATE COUNT(*) AS num_orders, COUNT(DISTINCT o_custkey) AS num_customers
+   GROUP BY o_orderpriority;
+
+-- The GROUP BY can include computed expressions, and assign them column names.
+-- We can add an ORDER BY, but it seems repetitive to list the column names.
+FROM Orders
+|> AGGREGATE COUNT(*) AS num_orders, SUM(o_totalprice) AS price
+   GROUP BY EXTRACT(YEAR FROM o_orderdate) AS year, o_orderpriority
+|> ORDER BY year, o_orderpriority
+|> LIMIT 12;
+
+-- Aggregation ordering shorthand #1:
+-- - GROUP AND ORDER BY to order by everything in GROUP BY.
+-- - Optionally order some columns DESC.
+FROM Orders
+|> AGGREGATE COUNT(*) AS num_orders, SUM(o_totalprice) AS price
+   GROUP AND ORDER BY EXTRACT(YEAR FROM o_orderdate) AS year, o_orderpriority DESC
+|> LIMIT 12;
+
+-- Aggregation ordering shorthand #2:
+-- - ASC/DESC on GROUP BY columns
+-- - ASC/DESC on aggregate columns
+-- Orders by selected grouping columns first, then selected aggregate columns.
+FROM Orders
+|> AGGREGATE COUNT(*) AS num_orders, SUM(o_totalprice) AS price DESC
+   GROUP BY EXTRACT(YEAR FROM o_orderdate) AS year, o_orderpriority ASC
+|> LIMIT 12;
+
+--
+-- AGGREGATE example, with standard and pipe syntax
+-- ------------------------------------------------
+--
+
+-- Aggregation in standard syntax, with ordering and a computed
+-- grouping expression.
+SELECT
+  EXTRACT(YEAR FROM o_orderdate) AS year,
+  SUM(o_totalprice) AS price
+FROM Orders
+GROUP BY year
+ORDER BY price DESC;
+
+-- The same aggregation in pipe syntax in one step.
+FROM Orders
+|> AGGREGATE SUM(o_totalprice) AS price DESC
+   GROUP BY EXTRACT(YEAR FROM o_orderdate) AS year;
+
+--
+-- Adding pipe operators on the end of standard syntax queries
+-- -----------------------------------------------------------
+--
+
+-- e.g. Add an aggregate on the end of a query to compute some stats.
+SELECT
+  EXTRACT(YEAR FROM o_orderdate) AS year,
+  SUM(o_totalprice) AS price
+FROM Orders
+GROUP BY year
+ORDER BY price DESC
+|> AGGREGATE COUNT(*), MIN(year), MAX(year);
diff --git a/zetasql/examples/pipe_queries/pipe_pivot.sql b/zetasql/examples/pipe_queries/pipe_pivot.sql
new file mode 100644
index 000000000..87ddbb9e2
--- /dev/null
+++ b/zetasql/examples/pipe_queries/pipe_pivot.sql
@@ -0,0 +1,68 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+-- These are examples of pipe PIVOT and UNPIVOT operators.
+-- They can be executed in `execute_query` with the `tpch` catalog.
+
+-- PIVOT
+--
+-- This produces one column for each value listed in the IN, computing the
+-- requested aggregate functions over the rows for that value.
+--
+-- More detail: Columns not mentioned in the PIVOT clause are grouping columns.
+-- The FOR column is the source column to pivot from.  Output includes one row
+-- for each group.  Output has the grouping columns, and then one column for each
+-- value in the IN list, with the aggregate computed where the source column had
+-- that value.
+--
+-- See
+-- https://github.com/google/zetasql/blob/master/docs/pipe-syntax.md#pivot
+-- for more details and options.
+
+FROM Orders
+|> SELECT EXTRACT(YEAR FROM o_orderdate) AS year, o_orderpriority, o_totalprice
+|> PIVOT
+     (
+       COUNT(*) AS count,
+       SUM(o_totalprice) AS price
+           FOR o_orderpriority IN ('1-URGENT', '2-HIGH', '3-MEDIUM'));
+
+-- UNPIVOT
+--
+-- For input rows with values in N columns, this produces N rows in a key/value
+-- representation, with one row for each of the N columns.
+--
+-- More detail:
+-- For each input row, this produces multiple output rows - one for each of
+-- columns listed in IN.  The IN columns are removed and replaced by one column
+-- (`animal` here), which indicates which source column this row came from.
+-- The single `cnt` output column stores the values from each of the IN columns.
+
+-- See
+-- https://github.com/google/zetasql/blob/master/docs/pipe-syntax.md#unpivot
+-- for more details and options.
+
+WITH
+  animals AS (
+    SELECT 2000 AS year, 10 AS dogs, 12 AS cats, 14 AS birds
+    UNION ALL
+    SELECT 2001 AS year, 11 AS dogs, 15 AS cats, 17 AS birds
+    UNION ALL
+    SELECT 2002 AS year, 6 AS dogs, 5 AS cats, 2 AS birds
+  )
+FROM animals
+|> UNPIVOT (cnt FOR animal IN (dogs, cats, birds))
+|> ORDER BY year, cnt;
diff --git a/zetasql/examples/pipe_queries/walkthrough_7day.sql b/zetasql/examples/pipe_queries/walkthrough_7day.sql
new file mode 100644
index 000000000..24116bc27
--- /dev/null
+++ b/zetasql/examples/pipe_queries/walkthrough_7day.sql
@@ -0,0 +1,269 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+-- These queries are intended as part of a tutorial on working with
+-- pipe syntax. They walk through construcing a query incrementally
+-- using pipe syntax, explaining the thinking as the query evolves.
+
+-- These can be executed in `execute_query` with the `tpch` catalog.
+-- See the README for more information.
+
+-- Goal: Compute a metric for 7-day-active users from an event log.
+--
+-- These queries have been adapted to run against the Orders table since
+-- that is available in the TPC-H catalog available in `execute_query`.
+-- They can be adapted for any table with a date and user ID.
+
+-- This is the final query, as a standalone query:
+
+-- Compute 7-day-active distinct users, with sliding windows.
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> EXTEND max(date) OVER () AS max_date
+|> JOIN UNNEST(generate_array(0, 7)) diff_days
+|> EXTEND date_add(date, INTERVAL diff_days DAY) active_date
+|> WHERE active_date <= max_date
+|> AGGREGATE COUNT(DISTINCT user_id) active_7d_users
+   GROUP BY active_date AS date DESC
+|> LIMIT 20;
+
+-- Now I'll show the steps, and my thought process as I built that up
+-- incrementally, using pipe syntax.
+
+-- First, look at the table I'm starting with.
+DESCRIBE Orders;
+
+-- And see what the data looks like.
+FROM Orders
+|> LIMIT 10;
+
+-- I'll rename the columns so the queries can work with `date` and
+-- `user_id` columns.
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> LIMIT 10;
+
+-- Let's take a look at it, grouped by date, and count distinct users per day.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> AGGREGATE
+     COUNT(*) cnt,
+     COUNT(DISTINCT user_id) cnt_distinct
+   GROUP BY date
+|> LIMIT 10;
+
+-- It's out of order - add DESC order on the date.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> AGGREGATE
+     COUNT(*) cnt,
+     COUNT(DISTINCT user_id) cnt_distinct
+   GROUP BY date DESC
+|> LIMIT 20;
+
+-- I see I have rows up to 1998-08-02, and just a few distinct users per day.
+-- That's a short list, so I can take a look at them with ARRAY_AGG.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> AGGREGATE
+     COUNT(*) cnt,
+     COUNT(DISTINCT user_id) cnt_distinct,
+     array_agg(DISTINCT user_id ORDER BY user_id) user_ids
+   GROUP BY date DESC
+|> LIMIT 20;
+
+-- Now, how do I do 7-day active counts?
+
+-- The easiest way is to add a DATE_TRUNC expression in my GROUP BY,
+-- while will bucket rows by WEEK.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> AGGREGATE
+     COUNT(*) cnt,
+     COUNT(DISTINCT user_id) cnt_distinct,
+     array_agg(DISTINCT user_id ORDER BY user_id) user_ids
+   GROUP BY DATE_TRUNC(date, WEEK) DESC
+|> LIMIT 10;
+
+-- That's nice, but I really wanted sliding windows in my dashboard.
+-- So I'll need a new approach.
+
+-- For every day a user is active, that user also counts as active for the next 6
+-- days.
+-- So it should work if I copy the rows forward with those extra dates.
+
+-- I can add a join to an array to get a count of days to add.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> JOIN UNNEST([0, 1, 2, 3, 4, 5, 6]) diff_days
+|> LIMIT 10;
+
+-- Using GENERATE_ARRAY is a nicer way.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> JOIN UNNEST(generate_array(0, 6)) diff_days
+|> LIMIT 10;
+
+-- And then I can compute the new date by adding `diff_days`.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> JOIN UNNEST(generate_array(0, 6)) diff_days
+|> EXTEND date_add(date, INTERVAL diff_days DAY) active_date
+|> LIMIT 10;
+
+-- Now, let's group by `active_date` and count distinct users.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> JOIN UNNEST(generate_array(0, 6)) diff_days
+|> EXTEND date_add(date, INTERVAL diff_days DAY) active_date
+|> AGGREGATE COUNT(DISTINCT user_id) active_7d_users
+   GROUP BY active_date AS date DESC
+|> LIMIT 10;
+
+-- That worked. One thing I notice is that now I have dates going into the
+-- future, because I added rows with dates past the max date in the input.
+-- So I want to filter those out.
+
+-- I can compute the max date in the input with a window function.
+-- Try that with EXTEND, and run that prefix of the query to check.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> EXTEND max(date) OVER () AS max_date
+/*
+|> JOIN UNNEST(generate_array(0, 6)) diff_days
+|> EXTEND date_add(date, INTERVAL diff_days day) active_date
+|> AGGREGATE COUNT(DISTINCT user_id) active_7d_users
+   GROUP BY active_date AS date DESC
+*/
+|> LIMIT 10;
+
+-- Then I'll add a WHERE clause to filter generated rows past that date.
+
+-- And we're done!  That's 7-day active users, with sliding windows.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> EXTEND max(date) OVER () AS max_date
+|> JOIN UNNEST(generate_array(0, 6)) diff_days
+|> EXTEND date_add(date, INTERVAL diff_days DAY) active_date
+|> WHERE active_date <= max_date
+|> AGGREGATE COUNT(DISTINCT user_id) active_7d_users
+   GROUP BY active_date AS date DESC
+|> LIMIT 10;
+
+--
+--
+--
+-- NEXT STEP: Now what if want to reuse that multiple times?
+--
+
+-- That's a lot of code to copy-paste, or figure out again.
+-- Maybe I can make a reusable TVF to do it.
+
+-- Let's grab the middle part and make a TVF from it.
+
+CREATE TEMP TABLE FUNCTION ExtendDates1(input ANY TABLE)
+AS
+FROM input
+|> EXTEND max(date) OVER () AS max_date
+|> JOIN UNNEST(generate_array(0, 6)) diff_days
+|> EXTEND date_add(date, INTERVAL diff_days DAY) active_date
+|> WHERE active_date <= max_date;
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> CALL ExtendDates1()
+|> LIMIT 10;
+
+-- That worked, but the output has extra columns `max_date`, `diff_days`.
+-- So add a DROP to remove those extra columns.
+-- Also make num_days an argument.
+
+CREATE TEMP TABLE FUNCTION ExtendDates2(input ANY TABLE, num_days INT64)
+AS
+FROM input
+|> EXTEND max(date) OVER () AS max_date
+|> JOIN UNNEST(generate_array(0, num_days - 1)) diff_days
+|> EXTEND date_add(date, INTERVAL diff_days DAY) active_date
+|> WHERE active_date <= max_date
+-- Temporarily use SELECT * EXCEPT instead because of a bug with TVF inlining.
+-- |> DROP max_date, diff_days;
+|> SELECT * EXCEPT (max_date, diff_days);
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> CALL ExtendDates2(7)
+|> LIMIT 10;
+
+-- That looks better!
+-- But it's probably nicer if the modified date comes out as `date`, and the original
+-- is called `original_date`, so my following query can still write `GROUP BY date`.
+
+-- So I'll edit this to copy `date` to `original_date` with EXTEND, and
+-- change the later EXTEND to a SET that overwrites `date`.
+
+CREATE TEMP TABLE FUNCTION ExtendDates3(input ANY TABLE, num_days INT64)
+AS
+FROM input
+|> EXTEND date AS original_date
+|> EXTEND max(date) OVER () AS max_date
+|> JOIN UNNEST(generate_array(0, num_days - 1)) diff_days
+|> SET date = date_add(date, INTERVAL diff_days DAY)
+|> WHERE date <= max_date
+-- Temporarily use SELECT * EXCEPT instead because of a bug with TVF inlining.
+-- |> DROP max_date, diff_days;
+|> SELECT * EXCEPT (max_date, diff_days);
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> CALL ExtendDates3(7)
+|> LIMIT 10;
+
+-- That looks like the reusable function I want.
+-- I can call this on anything that has a `date` column.
+
+-- Then I can add my aggregation on the end, to compute my 7-day active users.
+
+CREATE TEMP TABLE FUNCTION ExtendDates(input ANY TABLE, num_days INT64)
+AS
+FROM input
+|> EXTEND date AS original_date
+|> EXTEND max(date) OVER () AS max_date
+|> JOIN UNNEST(generate_array(0, num_days - 1)) diff_days
+|> SET date = date_add(date, INTERVAL diff_days DAY)
+|> WHERE date <= max_date
+-- Temporarily use SELECT * EXCEPT instead because of a bug with TVF inlining.
+-- |> DROP max_date, diff_days;
+|> SELECT * EXCEPT (max_date, diff_days);
+
+-- Look at that, the date replication is nicely encapsulated in a reusable
+-- function!  And calling it as a pipe operator fits cleanly into the query flow.
+
+FROM Orders
+|> RENAME o_orderdate AS date, o_custkey AS user_id
+|> CALL ExtendDates(7)
+|> AGGREGATE COUNT(DISTINCT user_id) active_7d_users
+   GROUP BY date DESC
+|> LIMIT 20;
diff --git a/zetasql/examples/tpch/BUILD b/zetasql/examples/tpch/BUILD
new file mode 100644
index 000000000..e0100fbeb
--- /dev/null
+++ b/zetasql/examples/tpch/BUILD
@@ -0,0 +1,29 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+load("//zetasql/tools/execute_query:build_rules.bzl", "execute_query_test")
+
+execute_query_test(
+    name = "execute_all_queries_test",
+    args = ["--catalog=tpch"],
+    sql_file = ":all_queries.sql",
+)
+
+execute_query_test(
+    name = "execute_all_pipe_queries_test",
+    args = ["--catalog=tpch"],
+    sql_file = ":all_pipe_queries.sql",
+)
diff --git a/zetasql/examples/tpch/all_pipe_queries.sql b/zetasql/examples/tpch/all_pipe_queries.sql
index f00aea48f..177ea7311 100644
--- a/zetasql/examples/tpch/all_pipe_queries.sql
+++ b/zetasql/examples/tpch/all_pipe_queries.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # TPCH 1
 # A simple aggregate query with grouping and ordering collapses
 # to just an AGGREGATE call using GROUP AND ORDER BY shorthand.
diff --git a/zetasql/examples/tpch/all_queries.sql b/zetasql/examples/tpch/all_queries.sql
index f0ff58696..baad967ee 100644
--- a/zetasql/examples/tpch/all_queries.sql
+++ b/zetasql/examples/tpch/all_queries.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # TPCH 1
 SELECT
   l_returnflag,
diff --git a/zetasql/examples/tpch/describe.sql b/zetasql/examples/tpch/describe.sql
index 512f4b7e3..0f99239ca 100644
--- a/zetasql/examples/tpch/describe.sql
+++ b/zetasql/examples/tpch/describe.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 DESCRIBE Customer;
 DESCRIBE LineItem;
 DESCRIBE Nation;
diff --git a/zetasql/examples/tpch/pipe_queries/1.sql b/zetasql/examples/tpch/pipe_queries/1.sql
index d073b9512..60a47d520 100644
--- a/zetasql/examples/tpch/pipe_queries/1.sql
+++ b/zetasql/examples/tpch/pipe_queries/1.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # A simple aggregate query with grouping and ordering collapses
 # to just an AGGREGATE call using GROUP AND ORDER BY shorthand.
 FROM lineitem
diff --git a/zetasql/examples/tpch/pipe_queries/10.sql b/zetasql/examples/tpch/pipe_queries/10.sql
index d8e8b70d3..3bc7bba60 100644
--- a/zetasql/examples/tpch/pipe_queries/10.sql
+++ b/zetasql/examples/tpch/pipe_queries/10.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # This is written with SELECT after ORDER BY and LIMIT.
 # The other order would work too since SELECT preserves order.
 FROM
diff --git a/zetasql/examples/tpch/pipe_queries/11.sql b/zetasql/examples/tpch/pipe_queries/11.sql
index b97d04a49..cf41c5b8d 100644
--- a/zetasql/examples/tpch/pipe_queries/11.sql
+++ b/zetasql/examples/tpch/pipe_queries/11.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # HAVING is replaced by just a WHERE after the aggregate.
 # It can use the `value` column rather than repeating the aggregate expression.
 FROM
diff --git a/zetasql/examples/tpch/pipe_queries/12.sql b/zetasql/examples/tpch/pipe_queries/12.sql
index c921b9cb0..167d1a9c3 100644
--- a/zetasql/examples/tpch/pipe_queries/12.sql
+++ b/zetasql/examples/tpch/pipe_queries/12.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   orders,
   lineitem
diff --git a/zetasql/examples/tpch/pipe_queries/13.sql b/zetasql/examples/tpch/pipe_queries/13.sql
index b05858af9..16e4277f5 100644
--- a/zetasql/examples/tpch/pipe_queries/13.sql
+++ b/zetasql/examples/tpch/pipe_queries/13.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # This is much shorter because it can apply AGGREGATE twice for
 # two-level aggregation without needing a subquery.
 # This uses pipe JOIN. Using JOIN inside FROM would also work.
diff --git a/zetasql/examples/tpch/pipe_queries/14.sql b/zetasql/examples/tpch/pipe_queries/14.sql
index 30c2d59fe..031facc4d 100644
--- a/zetasql/examples/tpch/pipe_queries/14.sql
+++ b/zetasql/examples/tpch/pipe_queries/14.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   lineitem,
   part
diff --git a/zetasql/examples/tpch/pipe_queries/15.sql b/zetasql/examples/tpch/pipe_queries/15.sql
index 45ad83723..db4982dc2 100644
--- a/zetasql/examples/tpch/pipe_queries/15.sql
+++ b/zetasql/examples/tpch/pipe_queries/15.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # This uses a WITH clause, with the subquery in pipe syntax.
 # It aliases the grouping column like the original query, although
 # this is unnecessary.
diff --git a/zetasql/examples/tpch/pipe_queries/16.sql b/zetasql/examples/tpch/pipe_queries/16.sql
index 1562930a6..f74e74651 100644
--- a/zetasql/examples/tpch/pipe_queries/16.sql
+++ b/zetasql/examples/tpch/pipe_queries/16.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   partsupp,
   part
diff --git a/zetasql/examples/tpch/pipe_queries/17.sql b/zetasql/examples/tpch/pipe_queries/17.sql
index fc4f4f0e5..39549cc7f 100644
--- a/zetasql/examples/tpch/pipe_queries/17.sql
+++ b/zetasql/examples/tpch/pipe_queries/17.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   lineitem,
   part
diff --git a/zetasql/examples/tpch/pipe_queries/18.sql b/zetasql/examples/tpch/pipe_queries/18.sql
index 25bab4eaf..f15725486 100644
--- a/zetasql/examples/tpch/pipe_queries/18.sql
+++ b/zetasql/examples/tpch/pipe_queries/18.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   customer,
   orders,
diff --git a/zetasql/examples/tpch/pipe_queries/19.sql b/zetasql/examples/tpch/pipe_queries/19.sql
index 6ed6aac13..60f35c5c8 100644
--- a/zetasql/examples/tpch/pipe_queries/19.sql
+++ b/zetasql/examples/tpch/pipe_queries/19.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   lineitem,
   part
diff --git a/zetasql/examples/tpch/pipe_queries/2.sql b/zetasql/examples/tpch/pipe_queries/2.sql
index fd77c1e43..33120d8c9 100644
--- a/zetasql/examples/tpch/pipe_queries/2.sql
+++ b/zetasql/examples/tpch/pipe_queries/2.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   part,
   supplier,
diff --git a/zetasql/examples/tpch/pipe_queries/20.sql b/zetasql/examples/tpch/pipe_queries/20.sql
index 68fcefe34..026599c75 100644
--- a/zetasql/examples/tpch/pipe_queries/20.sql
+++ b/zetasql/examples/tpch/pipe_queries/20.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # All layers of expression subqueries are rewritten to pipe syntax here,
 # although this is not required.
 FROM
diff --git a/zetasql/examples/tpch/pipe_queries/21.sql b/zetasql/examples/tpch/pipe_queries/21.sql
index 5b91d9fe6..8da3a0c35 100644
--- a/zetasql/examples/tpch/pipe_queries/21.sql
+++ b/zetasql/examples/tpch/pipe_queries/21.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # EXISTS subqueries omit the placeholder SELECTs.
 FROM
   supplier,
diff --git a/zetasql/examples/tpch/pipe_queries/22.sql b/zetasql/examples/tpch/pipe_queries/22.sql
index eb76818ea..7cfb091ff 100644
--- a/zetasql/examples/tpch/pipe_queries/22.sql
+++ b/zetasql/examples/tpch/pipe_queries/22.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # A subquery isn't needed to compute an expression column so it
 # can be referenced multiple times.
 # It can just be computed directly in GROUP BY and given an alias.
diff --git a/zetasql/examples/tpch/pipe_queries/3.sql b/zetasql/examples/tpch/pipe_queries/3.sql
index 8c5e0dc2a..80b0a3c8f 100644
--- a/zetasql/examples/tpch/pipe_queries/3.sql
+++ b/zetasql/examples/tpch/pipe_queries/3.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   customer,
   orders,
diff --git a/zetasql/examples/tpch/pipe_queries/4.sql b/zetasql/examples/tpch/pipe_queries/4.sql
index 03e200351..91ed59172 100644
--- a/zetasql/examples/tpch/pipe_queries/4.sql
+++ b/zetasql/examples/tpch/pipe_queries/4.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # The simple outer aggregate involved repeating columns across
 # SELECT, GROUP BY and ORDERY.  In pipe syntax, all of that is
 # just once with AGGREGATE, using GROUP AND ORDER BY.
diff --git a/zetasql/examples/tpch/pipe_queries/5.sql b/zetasql/examples/tpch/pipe_queries/5.sql
index c9ffc4658..596facd41 100644
--- a/zetasql/examples/tpch/pipe_queries/5.sql
+++ b/zetasql/examples/tpch/pipe_queries/5.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   customer,
   orders,
diff --git a/zetasql/examples/tpch/pipe_queries/6.sql b/zetasql/examples/tpch/pipe_queries/6.sql
index d86306d5f..bfe94d970 100644
--- a/zetasql/examples/tpch/pipe_queries/6.sql
+++ b/zetasql/examples/tpch/pipe_queries/6.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 FROM
   lineitem
 |> WHERE
diff --git a/zetasql/examples/tpch/pipe_queries/7.sql b/zetasql/examples/tpch/pipe_queries/7.sql
index fb87d78b9..f0cfd4883 100644
--- a/zetasql/examples/tpch/pipe_queries/7.sql
+++ b/zetasql/examples/tpch/pipe_queries/7.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # Removes a subquery in favor of linear flow, with SELECT to compute
 # expressions before the AGGREGATE.
 # The final SELECT is unnecessary since it just selects the grouping and
diff --git a/zetasql/examples/tpch/pipe_queries/8.sql b/zetasql/examples/tpch/pipe_queries/8.sql
index a1c280d08..86e5ba751 100644
--- a/zetasql/examples/tpch/pipe_queries/8.sql
+++ b/zetasql/examples/tpch/pipe_queries/8.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # The subquery is unnecessary. Linear pipe flow works without it.
 # Selecting n2.n_name and aliasing it is unnecessary since the original
 # columns (and their table aliases) are still present after using EXTEND
diff --git a/zetasql/examples/tpch/pipe_queries/9.sql b/zetasql/examples/tpch/pipe_queries/9.sql
index 812a77b83..84ea164b2 100644
--- a/zetasql/examples/tpch/pipe_queries/9.sql
+++ b/zetasql/examples/tpch/pipe_queries/9.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 # Removes unnecessary subquery and internal SELECT and aliasing of
 # n_name column by using EXTEND to compute expressions.
 FROM
diff --git a/zetasql/examples/tpch/queries/1.sql b/zetasql/examples/tpch/queries/1.sql
index 4f3275d06..1fe9945ab 100644
--- a/zetasql/examples/tpch/queries/1.sql
+++ b/zetasql/examples/tpch/queries/1.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   l_returnflag,
   l_linestatus,
diff --git a/zetasql/examples/tpch/queries/10.sql b/zetasql/examples/tpch/queries/10.sql
index b2684c99d..053247a03 100644
--- a/zetasql/examples/tpch/queries/10.sql
+++ b/zetasql/examples/tpch/queries/10.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   c_custkey,
   c_name,
diff --git a/zetasql/examples/tpch/queries/11.sql b/zetasql/examples/tpch/queries/11.sql
index 4c1a0e4d9..edbf59587 100644
--- a/zetasql/examples/tpch/queries/11.sql
+++ b/zetasql/examples/tpch/queries/11.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   ps_partkey,
   sum(ps_supplycost * ps_availqty) AS value
diff --git a/zetasql/examples/tpch/queries/12.sql b/zetasql/examples/tpch/queries/12.sql
index 6c0f4eb58..0a8dd5a5f 100644
--- a/zetasql/examples/tpch/queries/12.sql
+++ b/zetasql/examples/tpch/queries/12.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   l_shipmode,
   sum(
diff --git a/zetasql/examples/tpch/queries/13.sql b/zetasql/examples/tpch/queries/13.sql
index e0811753e..619937cb9 100644
--- a/zetasql/examples/tpch/queries/13.sql
+++ b/zetasql/examples/tpch/queries/13.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   c_count,
   COUNT(*) AS custdist
diff --git a/zetasql/examples/tpch/queries/14.sql b/zetasql/examples/tpch/queries/14.sql
index 7793bd301..fe7d8ab63 100644
--- a/zetasql/examples/tpch/queries/14.sql
+++ b/zetasql/examples/tpch/queries/14.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   100.00
   * sum(
diff --git a/zetasql/examples/tpch/queries/15.sql b/zetasql/examples/tpch/queries/15.sql
index 1b7b043e8..15b6e6097 100644
--- a/zetasql/examples/tpch/queries/15.sql
+++ b/zetasql/examples/tpch/queries/15.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 WITH
   revenue AS (
     SELECT
diff --git a/zetasql/examples/tpch/queries/16.sql b/zetasql/examples/tpch/queries/16.sql
index aff60b04e..d2744d394 100644
--- a/zetasql/examples/tpch/queries/16.sql
+++ b/zetasql/examples/tpch/queries/16.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   p_brand,
   p_type,
diff --git a/zetasql/examples/tpch/queries/17.sql b/zetasql/examples/tpch/queries/17.sql
index 88025f980..3ebb30940 100644
--- a/zetasql/examples/tpch/queries/17.sql
+++ b/zetasql/examples/tpch/queries/17.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   sum(l_extendedprice) / 7.0 AS avg_yearly
 FROM
diff --git a/zetasql/examples/tpch/queries/18.sql b/zetasql/examples/tpch/queries/18.sql
index 4410d2391..21ad5ddd3 100644
--- a/zetasql/examples/tpch/queries/18.sql
+++ b/zetasql/examples/tpch/queries/18.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   c_name,
   c_custkey,
diff --git a/zetasql/examples/tpch/queries/19.sql b/zetasql/examples/tpch/queries/19.sql
index f28fc3552..4e5c7bb93 100644
--- a/zetasql/examples/tpch/queries/19.sql
+++ b/zetasql/examples/tpch/queries/19.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   sum(l_extendedprice * (1 - l_discount)) AS revenue
 FROM
diff --git a/zetasql/examples/tpch/queries/2.sql b/zetasql/examples/tpch/queries/2.sql
index 2bf777a0c..1ea28304f 100644
--- a/zetasql/examples/tpch/queries/2.sql
+++ b/zetasql/examples/tpch/queries/2.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   s_acctbal,
   s_name,
diff --git a/zetasql/examples/tpch/queries/20.sql b/zetasql/examples/tpch/queries/20.sql
index b4dea0cbc..03c26195c 100644
--- a/zetasql/examples/tpch/queries/20.sql
+++ b/zetasql/examples/tpch/queries/20.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   s_name,
   s_address
diff --git a/zetasql/examples/tpch/queries/21.sql b/zetasql/examples/tpch/queries/21.sql
index f53a1c960..07090f23a 100644
--- a/zetasql/examples/tpch/queries/21.sql
+++ b/zetasql/examples/tpch/queries/21.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   s_name,
   COUNT(*) AS numwait
diff --git a/zetasql/examples/tpch/queries/22.sql b/zetasql/examples/tpch/queries/22.sql
index 6392f548b..e18d02dc7 100644
--- a/zetasql/examples/tpch/queries/22.sql
+++ b/zetasql/examples/tpch/queries/22.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   cntrycode,
   COUNT(*) AS numcust,
diff --git a/zetasql/examples/tpch/queries/3.sql b/zetasql/examples/tpch/queries/3.sql
index fd721fea7..74fa61351 100644
--- a/zetasql/examples/tpch/queries/3.sql
+++ b/zetasql/examples/tpch/queries/3.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   l_orderkey,
   sum(l_extendedprice * (1 - l_discount)) AS revenue,
diff --git a/zetasql/examples/tpch/queries/4.sql b/zetasql/examples/tpch/queries/4.sql
index 463ec6bc9..71cf927c7 100644
--- a/zetasql/examples/tpch/queries/4.sql
+++ b/zetasql/examples/tpch/queries/4.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   o_orderpriority,
   COUNT(*) AS order_count
diff --git a/zetasql/examples/tpch/queries/5.sql b/zetasql/examples/tpch/queries/5.sql
index 90020b02f..4ac9fd50c 100644
--- a/zetasql/examples/tpch/queries/5.sql
+++ b/zetasql/examples/tpch/queries/5.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   n_name,
   sum(l_extendedprice * (1 - l_discount)) AS revenue
diff --git a/zetasql/examples/tpch/queries/6.sql b/zetasql/examples/tpch/queries/6.sql
index d8a7375c1..42e91cdc5 100644
--- a/zetasql/examples/tpch/queries/6.sql
+++ b/zetasql/examples/tpch/queries/6.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   sum(l_extendedprice * l_discount) AS revenue
 FROM
diff --git a/zetasql/examples/tpch/queries/7.sql b/zetasql/examples/tpch/queries/7.sql
index ce5428410..edad63f82 100644
--- a/zetasql/examples/tpch/queries/7.sql
+++ b/zetasql/examples/tpch/queries/7.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   supp_nation,
   cust_nation,
diff --git a/zetasql/examples/tpch/queries/8.sql b/zetasql/examples/tpch/queries/8.sql
index 0d861605f..ddfe810b5 100644
--- a/zetasql/examples/tpch/queries/8.sql
+++ b/zetasql/examples/tpch/queries/8.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   o_year,
   sum(CASE WHEN nation = 'PERU' THEN volume ELSE 0 END) / sum(volume) AS mkt_share
diff --git a/zetasql/examples/tpch/queries/9.sql b/zetasql/examples/tpch/queries/9.sql
index 260df2ecb..2275c12a1 100644
--- a/zetasql/examples/tpch/queries/9.sql
+++ b/zetasql/examples/tpch/queries/9.sql
@@ -1,3 +1,19 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
 SELECT
   nation,
   o_year,
diff --git a/zetasql/local_service/local_service.cc b/zetasql/local_service/local_service.cc
index 97d4bf393..4b117af71 100644
--- a/zetasql/local_service/local_service.cc
+++ b/zetasql/local_service/local_service.cc
@@ -1567,12 +1567,14 @@ absl::Status ZetaSqlLocalServiceImpl::ParseScriptImpl(
   if (request.has_sql_statement()) {
     const std::string& sql = request.sql_statement();
 
-    ZETASQL_RETURN_IF_ERROR(ParseScript(
-        sql, parser_options, ErrorMessageMode::ERROR_MESSAGE_ONE_LINE,
-        /*keep_error_location_payload=*/
-            ErrorMessageMode::ERROR_MESSAGE_ONE_LINE ==
-            ErrorMessageMode::ERROR_MESSAGE_WITH_PAYLOAD,
-        &parser_output));
+    ZETASQL_RETURN_IF_ERROR(
+        ParseScript(sql, parser_options,
+                    {.mode = ErrorMessageMode::ERROR_MESSAGE_ONE_LINE,
+                     .attach_error_location_payload =
+                         (ErrorMessageMode::ERROR_MESSAGE_ONE_LINE ==
+                          ErrorMessageMode::ERROR_MESSAGE_WITH_PAYLOAD),
+                     .stability = GetDefaultErrorMessageStability()},
+                    &parser_output));
 
     return ParseTreeSerializer::Serialize(parser_output->script(),
                                           response->mutable_parsed_script());
diff --git a/zetasql/parser/bison_parser.y b/zetasql/parser/bison_parser.y
index 88e994373..5a9d2a72e 100644
--- a/zetasql/parser/bison_parser.y
+++ b/zetasql/parser/bison_parser.y
@@ -148,7 +148,6 @@
   zetasql::parser_internal::SeparatedIdentifierTmpNode* slashed_identifier;
   zetasql::ASTPivotClause* pivot_clause;
   zetasql::ASTUnpivotClause* unpivot_clause;
-  zetasql::ASTMatchRecognizeClause* match_recognize_clause;
   zetasql::ASTRowPatternExpression* row_pattern_expression;
   zetasql::ASTSetOperationType* set_operation_type;
   zetasql::ASTSetOperationAllOrDistinct* set_operation_all_or_distinct;
@@ -163,10 +162,8 @@
     zetasql::ASTUnpivotClause* unpivot_clause;
     zetasql::ASTAlias* alias;
   } pivot_or_unpivot_clause_and_alias;
-  struct {
-    zetasql::ASTMatchRecognizeClause* match_recognize_clause;
-    zetasql::ASTSampleClause* sample_clause;
-  } match_recognize_and_sample_clauses;
+  zetasql::ASTPostfixTableOperator* postfix_table_operator;
+  zetasql::ASTTableExpression* table_expression;
   struct {
     zetasql::ASTNode* where;
     zetasql::ASTNode* group_by;
@@ -1005,8 +1002,8 @@ using namespace zetasql::parser_internal;
 %type  int_literal_or_parameter
 %type  opt_int_literal_or_parameter
 %type  integer_literal
-%type  join
-%type  join_input
+%type  join
+%type  join_input
 %type  json_literal
 %type  lambda_argument
 %type  lambda_argument_list
@@ -1014,7 +1011,7 @@ using namespace zetasql::parser_internal;
 %type  macro_body
 %type  merge_action
 %type  merge_insert_value_list_or_source_row
-%type  merge_source
+%type  merge_source
 %type  merge_statement
 %type  merge_statement_prefix
 %type  merge_when_clause
@@ -1029,6 +1026,10 @@ using namespace zetasql::parser_internal;
 %type  braced_constructor_field_value
 %type  braced_constructor_field
 %type  braced_constructor_extension
+%type  braced_constructor_extension_expression_start
+%type  braced_constructor_extension_expression
+%type  braced_constructor_extension_lhs
+%type  braced_constructor_lhs
 %type  braced_constructor_start
 %type  braced_constructor_prefix
 %type  braced_constructor
@@ -1141,15 +1142,10 @@ using namespace zetasql::parser_internal;
 %type  unpivot_clause
 %type  pivot_expression
 %type  pivot_expression_list
-%type  match_recognize_clause
-// Use a combined struct to make sure they always are applied together, to avoid
-// forgetting one of them.
-%type  opt_match_recognize_and_sample_clauses
-%type  opt_match_recognize_clause
+%type  match_recognize_clause
 %type  row_pattern_expr
 %type  row_pattern_concatenation
 %type  row_pattern_factor
-%type  opt_sample_clause
 %type  opt_sample_clause_suffix
 %type  opt_select_as_clause
 %type  opt_table_element_list
@@ -1287,19 +1283,19 @@ using namespace zetasql::parser_internal;
 %type  table_element_list_prefix
 %type  table_and_column_info
 %type  table_and_column_info_list
-%type  table_path_expression
+%type  table_path_expression
 %type  table_path_expression_base
-%type  table_primary
-%type  table_subquery
+%type  table_primary
+%type  table_subquery
 %type  templated_parameter_type
 %type  transaction_mode
 %type  transaction_mode_list
 %type  truncate_statement
-%type  tvf_with_suffixes
-%type  tvf
+%type  tvf_with_suffixes
+%type  tvf
 %type  tvf_argument
-%type  tvf_prefix
-%type  tvf_prefix_no_args
+%type  tvf_prefix
+%type  tvf_prefix_no_args
 %type  type
 %type  type_parameter
 %type  type_parameters_prefix
@@ -6067,29 +6063,6 @@ sample_clause:
       }
     ;
 
-opt_match_recognize_and_sample_clauses:
-  %empty
-  {
-    $$.match_recognize_clause = nullptr;
-    $$.sample_clause = nullptr;
-  }
-  | match_recognize_clause[match_recognize] opt_sample_clause[sample]
-    {
-      $$.match_recognize_clause = $match_recognize;
-      $$.sample_clause = $sample;
-    }
-  | sample_clause[sample] opt_match_recognize_clause[match_recognize]
-    {
-      $$.match_recognize_clause = $match_recognize;
-      $$.sample_clause = $sample;
-    }
-  ;
-
-opt_sample_clause:
-    sample_clause
-    | %empty { $$ = nullptr; }
-    ;
-
 pivot_expression:
   expression opt_as_alias {
     $$ = MAKE_NODE(ASTPivotExpression, @$, {$1, $2});
@@ -6293,11 +6266,6 @@ opt_pivot_or_unpivot_clause_and_alias:
   }
   ;
 
-opt_match_recognize_clause:
-  match_recognize_clause
-  | %empty { $$ = nullptr; }
-  ;
-
 match_recognize_clause:
   KW_MATCH_RECOGNIZE_RESERVED "("
     opt_partition_by_clause[partition_by]
@@ -6349,7 +6317,6 @@ row_pattern_factor:
 table_subquery:
   parenthesized_query[query]
   opt_pivot_or_unpivot_clause_and_alias[pivot_and_alias]
-  opt_match_recognize_and_sample_clauses[postfix_ops]
       {
         zetasql::ASTQuery* query = $query;
         if ($pivot_and_alias.pivot_clause != nullptr) {
@@ -6360,10 +6327,11 @@ table_subquery:
         // we print two sets of brackets in very disorderly way.
         // So set parenthesized to false.
         query->set_parenthesized(false);
-        $$ = MAKE_NODE(ASTTableSubquery, @$, {
-            $query, $pivot_and_alias.alias, $pivot_and_alias.pivot_clause,
-            $pivot_and_alias.unpivot_clause,
-            $postfix_ops.match_recognize_clause, $postfix_ops.sample_clause});
+        auto* node = MAKE_NODE(ASTTableSubquery, @$,
+                                {$query, $pivot_and_alias.alias});
+        $$ = MaybeApplyPivotOrUnpivot(node,
+                                      $pivot_and_alias.pivot_clause,
+                                      $pivot_and_alias.unpivot_clause);
       }
     ;
 
@@ -6534,22 +6502,24 @@ tvf_with_suffixes:
     tvf_prefix_no_args[prefix] ")"
     opt_hint[hint]
     opt_pivot_or_unpivot_clause_and_alias[pivot_and_alias]
-    opt_match_recognize_and_sample_clauses[postfix_ops]
       {
-        $$ = WithExtraChildren(parser->WithEndLocation($prefix, @$), {
-            $hint, $pivot_and_alias.alias, $pivot_and_alias.pivot_clause,
-            $pivot_and_alias.unpivot_clause,
-            $postfix_ops.match_recognize_clause, $postfix_ops.sample_clause});
+        auto* node = WithExtraChildren(
+                        parser->WithEndLocation($prefix, @$),
+                        {$hint, $pivot_and_alias.alias});
+        $$ = MaybeApplyPivotOrUnpivot(node,
+                                      $pivot_and_alias.pivot_clause,
+                                      $pivot_and_alias.unpivot_clause);
       }
     | tvf_prefix[prefix] ")"
       opt_hint[hint]
       opt_pivot_or_unpivot_clause_and_alias[pivot_and_alias]
-      opt_match_recognize_and_sample_clauses[postfix_ops]
       {
-        $$ = WithExtraChildren(parser->WithEndLocation($prefix, @$), {
-            $hint, $pivot_and_alias.alias, $pivot_and_alias.pivot_clause,
-            $pivot_and_alias.unpivot_clause,
-            $postfix_ops.match_recognize_clause, $postfix_ops.sample_clause});
+        auto* node = WithExtraChildren(
+                        parser->WithEndLocation($prefix, @$),
+                        {$hint, $pivot_and_alias.alias});
+        $$ = MaybeApplyPivotOrUnpivot(node,
+                                      $pivot_and_alias.pivot_clause,
+                                      $pivot_and_alias.unpivot_clause);
       }
     ;
 
@@ -6592,7 +6562,6 @@ table_path_expression:
     opt_pivot_or_unpivot_clause_and_alias[pivot_and_alias]
     opt_with_offset_and_alias[offset]
     opt_at_system_time[time]
-    opt_match_recognize_and_sample_clauses[postfix_ops]
       {
         if ( $offset != nullptr) {
           // We do not support combining PIVOT or UNPIVOT with WITH OFFSET.
@@ -6608,11 +6577,11 @@ table_path_expression:
           // happens if we have a WITH OFFSET without PIVOT) and give an explicit
           // error if both clauses are present.
           if ($pivot_and_alias.pivot_clause != nullptr) {
-            YYERROR_AND_ABORT_AT(@4,
+            YYERROR_AND_ABORT_AT(@offset,
               "PIVOT and WITH OFFSET cannot be combined");
           }
           if ($pivot_and_alias.unpivot_clause != nullptr) {
-            YYERROR_AND_ABORT_AT(@4,
+            YYERROR_AND_ABORT_AT(@offset,
               "UNPIVOT and WITH OFFSET cannot be combined");
           }
         }
@@ -6620,32 +6589,30 @@ table_path_expression:
         if ($time != nullptr) {
           if ($pivot_and_alias.pivot_clause != nullptr) {
             YYERROR_AND_ABORT_AT(
-                @5,
+                @time,
                 "Syntax error: PIVOT and FOR SYSTEM TIME AS OF "
                 "may not be combined");
           }
           if ($pivot_and_alias.unpivot_clause != nullptr) {
             YYERROR_AND_ABORT_AT(
-                @5,
+                @time,
                 "Syntax error: UNPIVOT and FOR SYSTEM TIME AS OF "
                 "may not be combined");
           }
         }
-        $$ = MAKE_NODE(ASTTablePathExpression, @$, {$path, $hint,
-            $pivot_and_alias.alias, $pivot_and_alias.pivot_clause,
-            $pivot_and_alias.unpivot_clause, $offset, $time,
-            $postfix_ops.match_recognize_clause, $postfix_ops.sample_clause});
+        auto* node = MAKE_NODE(ASTTablePathExpression, @$,
+                               {$path, $hint, $pivot_and_alias.alias, $offset,
+                                $time});
+
+        $$ = MaybeApplyPivotOrUnpivot(node,
+                                      $pivot_and_alias.pivot_clause,
+                                      $pivot_and_alias.unpivot_clause);
       };
 
 table_primary:
-    // TODO: sample, pivot, match_recognize, etc should be grouped
-    //                both in the grammar and in the AST, to apply to all
-    //                table primary types, and would help dedup the unparser.
-    // Test coverage to add: pipes, parenthesized join.
     tvf_with_suffixes
     | table_path_expression
     | "(" join ")"
-      opt_match_recognize_and_sample_clauses[postfix_ops]
       {
         zetasql::parser::ErrorInfo error_info;
         auto node = zetasql::parser::TransformJoinExpression(
@@ -6654,11 +6621,21 @@ table_primary:
           YYERROR_AND_ABORT_AT(error_info.location, error_info.message);
         }
 
-        $$ = MAKE_NODE(ASTParenthesizedJoin, @$,
-                       {node, $postfix_ops.match_recognize_clause,
-                        $postfix_ops.sample_clause});
+        $$ = MAKE_NODE(ASTParenthesizedJoin, @$,{node});
       }
     | table_subquery
+    // Postfix operators. Note that PIVOT/UNPIVOT are lumped together with each
+    // rule because they're entangled with alias to work around the fact that
+    // PIVOT and UNPIVOT are not reserved keywords.
+    // Ideally they should be listed here.
+    | table_primary[table] match_recognize_clause
+      {
+        $$ = WithExtraChildren($table, {$match_recognize_clause});
+      }
+    | table_primary[table] sample_clause
+      {
+        $$ = WithExtraChildren($table, {$sample_clause});
+      }
     ;
 
 opt_at_system_time:
@@ -6778,12 +6755,12 @@ join:
           YYERROR_AND_ABORT_AT(error_info.location, error_info.message);
         }
 
-        $$ = node;
+        $$ = node->GetAsOrDie();
       }
     ;
 
 from_clause_contents:
-    table_primary
+    table_primary[table] { $$ = $table; }
     | from_clause_contents "," table_primary
       {
         zetasql::parser::ErrorInfo error_info;
@@ -8998,15 +8975,43 @@ braced_constructor_field_value:
       }
     ;
 
+braced_constructor_extension_expression_start:
+    "(" path_expression ")"
+      {
+        $2->set_parenthesized(true);
+        $$ = $2;
+      }
+    ;
+
+braced_constructor_extension_expression:
+    // This production exists to allow for future expansion on the types of
+    // paths that are supported.
+    braced_constructor_extension_expression_start
+    ;
+
+braced_constructor_extension_lhs:
+    braced_constructor_extension_expression
+      {
+        $$ = MAKE_NODE(ASTBracedConstructorLhs, @$, {$1});
+      }
+    ;
+
 braced_constructor_extension:
-    "(" path_expression ")" braced_constructor_field_value
+    braced_constructor_extension_lhs braced_constructor_field_value
+      {
+        $$ = MAKE_NODE(ASTBracedConstructorField, @$, {$1, $2});
+      }
+    ;
+
+braced_constructor_lhs:
+    generalized_path_expression
       {
-        $$ = MAKE_NODE(ASTBracedConstructorField, @$, {$2, $4});
+        $$ = MAKE_NODE(ASTBracedConstructorLhs, @$, {$1});
       }
     ;
 
 braced_constructor_field:
-    identifier braced_constructor_field_value
+    braced_constructor_lhs braced_constructor_field_value
       {
         $$ = MAKE_NODE(ASTBracedConstructorField, @$, {$1, $2});
       }
diff --git a/zetasql/parser/gen_parse_tree.py b/zetasql/parser/gen_parse_tree.py
index 3eaa5136b..6af0128ad 100644
--- a/zetasql/parser/gen_parse_tree.py
+++ b/zetasql/parser/gen_parse_tree.py
@@ -39,7 +39,7 @@
 from zetasql.parser.generator_utils import Trim
 from zetasql.parser.generator_utils import UpperCamelCase
 
-NEXT_NODE_TAG_ID = 488
+NEXT_NODE_TAG_ID = 490
 
 ROOT_NODE_NAME = 'ASTNode'
 
@@ -1581,6 +1581,21 @@ def main(argv):
       """
     )
 
+  gen.AddNode(
+      name='ASTPostfixTableOperator',
+      tag_id=488,
+      parent='ASTNode',
+      is_abstract=True,
+      comment="""
+      A common superclass for all postfix table operators like TABLESAMPLE.
+      """,
+      fields=[],
+      extra_public_defs="""
+  // The name of the operator to show in user-visible error messages.
+  virtual absl::string_view Name() const = 0;
+      """,
+  )
+
   gen.AddNode(
       name='ASTTableExpression',
       tag_id=15,
@@ -1600,8 +1615,43 @@ def main(argv):
   // Return the ASTNode location of the alias for this table expression,
   // if applicable.
   const ASTNode* alias_location() const;
-      """
-    )
+
+  // Compatibility getters until callers are migrated to directly use the list
+  // of posfix operators.
+  const ASTPivotClause* pivot_clause() const {
+    for (const auto* op : postfix_operators()) {
+      if (op->node_kind() == AST_PIVOT_CLAUSE) {
+        return op->GetAsOrDie();
+      }
+    }
+    return nullptr;
+  }
+  const ASTUnpivotClause* unpivot_clause() const {
+    for (const auto* op : postfix_operators()) {
+      if (op->node_kind() == AST_UNPIVOT_CLAUSE) {
+        return op->GetAsOrDie();
+      }
+    }
+    return nullptr;
+  }
+  const ASTSampleClause* sample_clause() const {
+    for (const auto* op : postfix_operators()) {
+      if (op->node_kind() == AST_SAMPLE_CLAUSE) {
+        return op->GetAsOrDie();
+      }
+    }
+    return nullptr;
+  }
+      """,
+      fields=[
+          Field(
+              'postfix_operators',
+              'ASTPostfixTableOperator',
+              tag_id=2,
+              field_loader=FieldLoaderMethod.REST_AS_REPEATED,
+              visibility=Visibility.PROTECTED,
+          ),
+      ])
 
   gen.AddNode(
       name='ASTTablePathExpression',
@@ -1640,31 +1690,9 @@ def main(argv):
               tag_id=6,
               comment="""
               Present if the scan had WITH OFFSET.
-              """),
-          Field(
-              'pivot_clause',
-              'ASTPivotClause',
-              tag_id=7,
-              comment="""
-              At most one of pivot_clause or unpivot_clause can be present.
-              """),
-          Field(
-              'unpivot_clause',
-              'ASTUnpivotClause',
-              tag_id=8),
-          Field(
-              'for_system_time',
-              'ASTForSystemTime',
-              tag_id=9),
-          Field(
-              'match_recognize_clause',
-              'ASTMatchRecognizeClause',
-              tag_id=11,
+              """,
           ),
-          Field(
-              'sample_clause',
-              'ASTSampleClause',
-              tag_id=10),
+          Field('for_system_time', 'ASTForSystemTime', tag_id=9),
       ],
       extra_public_defs="""
   const ASTAlias* alias() const override { return alias_; }
@@ -3365,14 +3393,6 @@ def main(argv):
               private_comment="""
               Required.
               """),
-          Field('match_recognize_clause', 'ASTMatchRecognizeClause', tag_id=4),
-          Field(
-              'sample_clause',
-              'ASTSampleClause',
-              tag_id=3,
-              private_comment="""
-              Optional.
-              """),
       ])
 
   gen.AddNode(
@@ -3663,25 +3683,6 @@ def main(argv):
               'ASTAlias',
               tag_id=3,
               gen_setters_and_getters=False),
-          Field(
-              'pivot_clause',
-              'ASTPivotClause',
-              tag_id=4,
-              private_comment="""
-              One of pivot_clause or unpivot_clause can be present but not both.
-              """),
-          Field(
-              'unpivot_clause',
-              'ASTUnpivotClause',
-              tag_id=5),
-          Field(
-              'match_recognize_clause',
-              'ASTMatchRecognizeClause',
-              tag_id=7),
-          Field(
-              'sample_clause',
-              'ASTSampleClause',
-              tag_id=6),
       ])
 
   gen.AddNode(
@@ -4660,7 +4661,7 @@ def main(argv):
   gen.AddNode(
       name='ASTPivotClause',
       tag_id=129,
-      parent='ASTNode',
+      parent='ASTPostfixTableOperator',
       fields=[
           Field(
               'pivot_expressions',
@@ -4681,7 +4682,10 @@ def main(argv):
               'output_alias',
               'ASTAlias',
               tag_id=5),
-      ])
+      ],
+      extra_public_defs="""
+  absl::string_view Name() const override { return "PIVOT"; }
+    """)
 
   gen.AddNode(
       name='ASTUnpivotInItem',
@@ -4714,7 +4718,7 @@ def main(argv):
   gen.AddNode(
       name='ASTUnpivotClause',
       tag_id=132,
-      parent='ASTNode',
+      parent='ASTPostfixTableOperator',
       use_custom_debug_string=True,
       fields=[
           Field(
@@ -4743,6 +4747,7 @@ def main(argv):
       ],
       extra_public_defs="""
   std::string GetSQLForNullFilter() const;
+  absl::string_view Name() const override { return "UNPIVOT"; }
       """)
 
   gen.AddNode(
@@ -4772,7 +4777,7 @@ def main(argv):
   gen.AddNode(
       name='ASTMatchRecognizeClause',
       tag_id=484,
-      parent='ASTNode',
+      parent='ASTPostfixTableOperator',
       comment="""
       Represents a row pattern recognition clause, i.e., MATCH_RECOGNIZE().
       """,
@@ -4795,7 +4800,9 @@ def main(argv):
           ),
           Field('output_alias', 'ASTAlias', tag_id=8),
       ],
-  )
+      extra_public_defs="""
+  absl::string_view Name() const override { return "MATCH_RECOGNIZE"; }
+    """)
 
   gen.AddNode(
       name='ASTRowPatternExpression',
@@ -5099,6 +5106,20 @@ def main(argv):
   const ASTNewConstructorArg* argument(int i) const { return arguments_[i]; }
       """)
 
+  gen.AddNode(
+      name='ASTBracedConstructorLhs',
+      tag_id=489,
+      parent='ASTExpression',
+      fields=[
+          Field(
+              'extended_path_expr',
+              'ASTGeneralizedPathExpression',
+              tag_id=2,
+              field_loader=FieldLoaderMethod.REQUIRED,
+          ),
+      ],
+  )
+
   gen.AddNode(
       name='ASTBracedConstructorFieldValue',
       tag_id=330,
@@ -5108,7 +5129,8 @@ def main(argv):
               'expression',
               'ASTExpression',
               tag_id=2,
-              field_loader=FieldLoaderMethod.REQUIRED),
+              field_loader=FieldLoaderMethod.REQUIRED,
+          ),
           Field(
               'colon_prefixed',
               SCALAR_BOOL,
@@ -5117,27 +5139,28 @@ def main(argv):
               True if "field:value" syntax is used.
               False if "field value" syntax is used.
               The later is only allowed in proto instead of struct.
-              """),
-      ])
+              """,
+          ),
+      ],
+  )
 
   gen.AddNode(
       name='ASTBracedConstructorField',
       tag_id=331,
       parent='ASTNode',
-      comment="""Exactly one of 'identifier' and 'parenthesized_path' is
-                 set.""",
       fields=[
           Field(
-              'identifier',
-              'ASTIdentifier',
-              tag_id=2,
+              'braced_constructor_lhs',
+              'ASTBracedConstructorLhs',
+              tag_id=6,
+              field_loader=FieldLoaderMethod.REQUIRED,
           ),
-          Field('parenthesized_path', 'ASTPathExpression', tag_id=3),
           Field(
               'value',
               'ASTBracedConstructorFieldValue',
               tag_id=4,
-              field_loader=FieldLoaderMethod.REQUIRED),
+              field_loader=FieldLoaderMethod.REQUIRED,
+          ),
           Field(
               'comma_separated',
               SCALAR_BOOL,
@@ -5147,8 +5170,10 @@ def main(argv):
               e.g.all e.g. "a:1,b:2".
               False if separated by whitespace, e.g. "a:1 b:2".
               The latter is only allowed in proto instead of struct.
-              """),
-      ])
+              """,
+          ),
+      ],
+  )
 
   gen.AddNode(
       name='ASTBracedConstructor',
@@ -5503,20 +5528,14 @@ def main(argv):
               'alias',
               'ASTAlias',
               tag_id=5),
-          Field(
-              'pivot_clause',
-              'ASTPivotClause',
-              tag_id=6),
-          Field(
-              'unpivot_clause',
-              'ASTUnpivotClause',
-              tag_id=7),
-          Field('match_recognize_clause', 'ASTMatchRecognizeClause', tag_id=9),
-          Field(
-              'sample',
-              'ASTSampleClause',
-              tag_id=8),
-      ])
+      ],
+      extra_public_defs="""
+  // Compatibility getters until callers are migrated to directly use the list
+  // of posfix operators.
+  const ASTSampleClause* sample() const {
+      return sample_clause();
+  }
+      """)
 
   gen.AddNode(
       name='ASTTableClause',
@@ -7183,7 +7202,7 @@ def main(argv):
   gen.AddNode(
       name='ASTSampleClause',
       tag_id=232,
-      parent='ASTNode',
+      parent='ASTPostfixTableOperator',
       fields=[
           Field(
               'sample_method',
@@ -7199,7 +7218,10 @@ def main(argv):
               'sample_suffix',
               'ASTSampleSuffix',
               tag_id=4),
-      ])
+      ],
+      extra_public_defs="""
+  absl::string_view Name() const override { return "TABLESAMPLE"; }
+    """)
 
   gen.AddNode(
       name='ASTAlterAction',
diff --git a/zetasql/parser/parser_internal.h b/zetasql/parser/parser_internal.h
index d386b881b..492197a96 100644
--- a/zetasql/parser/parser_internal.h
+++ b/zetasql/parser/parser_internal.h
@@ -407,6 +407,22 @@ inline absl::Status IsIdentifierOrKeyword(absl::string_view text) {
   return absl::OkStatus();
 }
 
+inline ASTTableExpression* MaybeApplyPivotOrUnpivot(
+    ASTTableExpression* table_expr, ASTPivotClause* pivot_clause,
+    ASTUnpivotClause* unpivot_clause) {
+  ABSL_DCHECK(pivot_clause == nullptr || unpivot_clause == nullptr)
+      << "pivot_clause and unpivot_clause cannot both be non-null";
+  if (pivot_clause != nullptr) {
+    return WithExtraChildren(table_expr, {pivot_clause});
+  }
+
+  if (unpivot_clause != nullptr) {
+    return WithExtraChildren(table_expr, {unpivot_clause});
+  }
+
+  return table_expr;
+}
+
 template 
 inline zetasql::ASTRowPatternExpression* MakeOrCombineRowPatternOperation(
     const zetasql::ASTRowPatternOperation::OperationType op,
diff --git a/zetasql/parser/testdata/match_recognize.test b/zetasql/parser/testdata/match_recognize.test
index 1bb7aefd3..5a16430da 100644
--- a/zetasql/parser/testdata/match_recognize.test
+++ b/zetasql/parser/testdata/match_recognize.test
@@ -453,9 +453,9 @@ select * from t match_recognize(
 --
 ALTERNATION GROUP:  MATCH_RECOGNIZE 
 --
-ERROR: Syntax error: Expected end of input but got keyword MATCH_RECOGNIZE [at 6:19]
+ERROR: Syntax error: Expected "(" but got end of statement [at 6:34]
 ) as outer_alias  MATCH_RECOGNIZE 
-                  ^
+                                 ^
 --
 ALTERNATION GROUP:  TABLESAMPLE 
 --
@@ -631,6 +631,188 @@ FROM
     DEFINE A AS true)
 ==
 
+# MATCH_RECOGNIZE() on a parenthesized join
+SELECT * FROM (t1 INNER JOIN t2 ON t1.a = t2.b)
+  match_recognize(
+    ORDER BY c DESC, f(d)
+    MEASURES max(A.x) AS m1, min(B.y) AS m2
+    PATTERN ( A B )
+    DEFINE A AS x.y
+  )
+--
+QueryStatement [0-180] [SELECT * FROM...AS x.y   )]
+  Query [0-180] [SELECT * FROM...AS x.y   )]
+    Select [0-180] [SELECT * FROM...AS x.y   )]
+      SelectList [7-8] [*]
+        SelectColumn [7-8] [*]
+          Star(*) [7-8] [*]
+      FromClause [9-180] [FROM (t1 INNER...AS x.y   )]
+        ParenthesizedJoin [14-180] [(t1 INNER...AS x.y   )]
+          Join(INNER) [15-46] [t1 INNER JOIN...t1.a = t2.b]
+            TablePathExpression [15-17] [t1]
+              PathExpression [15-17] [t1]
+                Identifier(t1) [15-17] [t1]
+            Location [18-28] [INNER JOIN]
+            TablePathExpression [29-31] [t2]
+              PathExpression [29-31] [t2]
+                Identifier(t2) [29-31] [t2]
+            OnClause [32-46] [ON t1.a = t2.b]
+              BinaryExpression(=) [35-46] [t1.a = t2.b]
+                PathExpression [35-39] [t1.a]
+                  Identifier(t1) [35-37] [t1]
+                  Identifier(a) [38-39] [a]
+                PathExpression [42-46] [t2.b]
+                  Identifier(t2) [42-44] [t2]
+                  Identifier(b) [45-46] [b]
+          MatchRecognizeClause [50-180] [match_recognize...AS x.y   )]
+            OrderBy [71-92] [ORDER BY c DESC, f(d)]
+              OrderingExpression(DESC) [80-86] [c DESC]
+                PathExpression [80-81] [c]
+                  Identifier(c) [80-81] [c]
+              OrderingExpression(ASC) [88-92] [f(d)]
+                FunctionCall [88-92] [f(d)]
+                  PathExpression [88-89] [f]
+                    Identifier(f) [88-89] [f]
+                  PathExpression [90-91] [d]
+                    Identifier(d) [90-91] [d]
+            SelectList [106-136] [max(A.x) AS m1, min(B.y) AS m2]
+              SelectColumn [106-120] [max(A.x) AS m1]
+                FunctionCall [106-114] [max(A.x)]
+                  PathExpression [106-109] [max]
+                    Identifier(max) [106-109] [max]
+                  PathExpression [110-113] [A.x]
+                    Identifier(A) [110-111] [A]
+                    Identifier(x) [112-113] [x]
+                Alias [115-120] [AS m1]
+                  Identifier(m1) [118-120] [m1]
+              SelectColumn [122-136] [min(B.y) AS m2]
+                FunctionCall [122-130] [min(B.y)]
+                  PathExpression [122-125] [min]
+                    Identifier(min) [122-125] [min]
+                  PathExpression [126-129] [B.y]
+                    Identifier(B) [126-127] [B]
+                    Identifier(y) [128-129] [y]
+                Alias [131-136] [AS m2]
+                  Identifier(m2) [134-136] [m2]
+            RowPatternOperation [151-154] [A B]
+              RowPatternVariable [151-152] [A]
+                Identifier(A) [151-152] [A]
+              RowPatternVariable [153-154] [B]
+                Identifier(B) [153-154] [B]
+            SelectList [168-176] [A AS x.y]
+              SelectColumn [168-176] [A AS x.y]
+                PathExpression [173-176] [x.y]
+                  Identifier(x) [173-174] [x]
+                  Identifier(y) [175-176] [y]
+                Alias [168-172] [A AS]
+                  Identifier(A) [168-169] [A]
+--
+SELECT
+  *
+FROM
+  (
+    t1
+    INNER JOIN
+    t2
+    ON t1.a = t2.b
+  ) MATCH_RECOGNIZE(
+    ORDER BY c DESC, f(d)
+    MEASURES
+      max(A.x) AS m1,
+      min(B.y) AS m2
+    PATTERN (A  B)
+    DEFINE A AS x.y)
+==
+
+# MATCH_RECOGNIZE() on a table subquery
+SELECT * FROM ( SELECT * FROM t) AS u
+  match_recognize(
+    ORDER BY c DESC, f(d)
+    MEASURES max(A.x) AS m1, min(B.y) AS m2
+    PATTERN ( A B )
+    DEFINE A AS x.y
+  )
+--
+
+QueryStatement [0-170] [SELECT * FROM...AS x.y   )]
+  Query [0-170] [SELECT * FROM...AS x.y   )]
+    Select [0-170] [SELECT * FROM...AS x.y   )]
+      SelectList [7-8] [*]
+        SelectColumn [7-8] [*]
+          Star(*) [7-8] [*]
+      FromClause [9-170] [FROM ( SELECT...AS x.y   )]
+        TableSubquery [14-170] [( SELECT *...AS x.y   )]
+          Query [16-31] [SELECT * FROM t]
+            Select [16-31] [SELECT * FROM t]
+              SelectList [23-24] [*]
+                SelectColumn [23-24] [*]
+                  Star(*) [23-24] [*]
+              FromClause [25-31] [FROM t]
+                TablePathExpression [30-31] [t]
+                  PathExpression [30-31] [t]
+                    Identifier(t) [30-31] [t]
+          Alias [33-37] [AS u]
+            Identifier(u) [36-37] [u]
+          MatchRecognizeClause [40-170] [match_recognize...AS x.y   )]
+            OrderBy [61-82] [ORDER BY c DESC, f(d)]
+              OrderingExpression(DESC) [70-76] [c DESC]
+                PathExpression [70-71] [c]
+                  Identifier(c) [70-71] [c]
+              OrderingExpression(ASC) [78-82] [f(d)]
+                FunctionCall [78-82] [f(d)]
+                  PathExpression [78-79] [f]
+                    Identifier(f) [78-79] [f]
+                  PathExpression [80-81] [d]
+                    Identifier(d) [80-81] [d]
+            SelectList [96-126] [max(A.x) AS m1, min(B.y) AS m2]
+              SelectColumn [96-110] [max(A.x) AS m1]
+                FunctionCall [96-104] [max(A.x)]
+                  PathExpression [96-99] [max]
+                    Identifier(max) [96-99] [max]
+                  PathExpression [100-103] [A.x]
+                    Identifier(A) [100-101] [A]
+                    Identifier(x) [102-103] [x]
+                Alias [105-110] [AS m1]
+                  Identifier(m1) [108-110] [m1]
+              SelectColumn [112-126] [min(B.y) AS m2]
+                FunctionCall [112-120] [min(B.y)]
+                  PathExpression [112-115] [min]
+                    Identifier(min) [112-115] [min]
+                  PathExpression [116-119] [B.y]
+                    Identifier(B) [116-117] [B]
+                    Identifier(y) [118-119] [y]
+                Alias [121-126] [AS m2]
+                  Identifier(m2) [124-126] [m2]
+            RowPatternOperation [141-144] [A B]
+              RowPatternVariable [141-142] [A]
+                Identifier(A) [141-142] [A]
+              RowPatternVariable [143-144] [B]
+                Identifier(B) [143-144] [B]
+            SelectList [158-166] [A AS x.y]
+              SelectColumn [158-166] [A AS x.y]
+                PathExpression [163-166] [x.y]
+                  Identifier(x) [163-164] [x]
+                  Identifier(y) [165-166] [y]
+                Alias [158-162] [A AS]
+                  Identifier(A) [158-159] [A]
+--
+SELECT
+  *
+FROM
+  (
+    SELECT
+      *
+    FROM
+      t
+  ) AS u MATCH_RECOGNIZE(
+    ORDER BY c DESC, f(d)
+    MEASURES
+      max(A.x) AS m1,
+      min(B.y) AS m2
+    PATTERN (A  B)
+    DEFINE A AS x.y)
+==
+
 select * from t match_recognize(
   PARTITION BY a, f.g(b)
   ORDER BY c DESC, f(d)
diff --git a/zetasql/parser/testdata/proto_braced_constructors.test b/zetasql/parser/testdata/proto_braced_constructors.test
index ee57af1f2..1d721ab91 100644
--- a/zetasql/parser/testdata/proto_braced_constructors.test
+++ b/zetasql/parser/testdata/proto_braced_constructors.test
@@ -58,7 +58,9 @@ QueryStatement [0-15] [SELECT {bar:3,}]
         SelectColumn [7-15] [{bar:3,}]
           BracedConstructor [7-15] [{bar:3,}]
             BracedConstructorField [8-13] [bar:3]
-              Identifier(bar) [8-11] [bar]
+              BracedConstructorLhs [8-11] [bar]
+                PathExpression [8-11] [bar]
+                  Identifier(bar) [8-11] [bar]
               BracedConstructorFieldValue [11-13] [:3]
                 IntLiteral(3) [12-13] [3]
 --
@@ -88,12 +90,16 @@ QueryStatement [0-35] [SELECT NEW..." bar: 3 }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-35] [{ foo: "blah" bar: 3 }]
               BracedConstructorField [15-26] [foo: "blah"]
-                Identifier(foo) [15-18] [foo]
+                BracedConstructorLhs [15-18] [foo]
+                  PathExpression [15-18] [foo]
+                    Identifier(foo) [15-18] [foo]
                 BracedConstructorFieldValue [18-26] [: "blah"]
                   StringLiteral [20-26] ["blah"]
                     StringLiteralComponent("blah") [20-26] ["blah"]
               BracedConstructorField [27-33] [bar: 3]
-                Identifier(bar) [27-30] [bar]
+                BracedConstructorLhs [27-30] [bar]
+                  PathExpression [27-30] [bar]
+                    Identifier(bar) [27-30] [bar]
                 BracedConstructorFieldValue [30-33] [: 3]
                   IntLiteral(3) [32-33] [3]
 --
@@ -115,12 +121,16 @@ QueryStatement [0-36] [SELECT NEW...bar: 3, }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-36] [{ foo: "blah" bar: 3, }]
               BracedConstructorField [15-26] [foo: "blah"]
-                Identifier(foo) [15-18] [foo]
+                BracedConstructorLhs [15-18] [foo]
+                  PathExpression [15-18] [foo]
+                    Identifier(foo) [15-18] [foo]
                 BracedConstructorFieldValue [18-26] [: "blah"]
                   StringLiteral [20-26] ["blah"]
                     StringLiteralComponent("blah") [20-26] ["blah"]
               BracedConstructorField [27-33] [bar: 3]
-                Identifier(bar) [27-30] [bar]
+                BracedConstructorLhs [27-30] [bar]
+                  PathExpression [27-30] [bar]
+                    Identifier(bar) [27-30] [bar]
                 BracedConstructorFieldValue [30-33] [: 3]
                   IntLiteral(3) [32-33] [3]
 --
@@ -150,12 +160,16 @@ QueryStatement [0-36] [SELECT NEW..., bar: 3 }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-36] [{ foo: "blah", bar: 3 }]
               BracedConstructorField [15-26] [foo: "blah"]
-                Identifier(foo) [15-18] [foo]
+                BracedConstructorLhs [15-18] [foo]
+                  PathExpression [15-18] [foo]
+                    Identifier(foo) [15-18] [foo]
                 BracedConstructorFieldValue [18-26] [: "blah"]
                   StringLiteral [20-26] ["blah"]
                     StringLiteralComponent("blah") [20-26] ["blah"]
               BracedConstructorField [28-34] [bar: 3]
-                Identifier(bar) [28-31] [bar]
+                BracedConstructorLhs [28-31] [bar]
+                  PathExpression [28-31] [bar]
+                    Identifier(bar) [28-31] [bar]
                 BracedConstructorFieldValue [31-34] [: 3]
                   IntLiteral(3) [33-34] [3]
 --
@@ -177,12 +191,16 @@ QueryStatement [0-37] [SELECT NEW...bar: 3, }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-37] [{ foo: "blah", bar: 3, }]
               BracedConstructorField [15-26] [foo: "blah"]
-                Identifier(foo) [15-18] [foo]
+                BracedConstructorLhs [15-18] [foo]
+                  PathExpression [15-18] [foo]
+                    Identifier(foo) [15-18] [foo]
                 BracedConstructorFieldValue [18-26] [: "blah"]
                   StringLiteral [20-26] ["blah"]
                     StringLiteralComponent("blah") [20-26] ["blah"]
               BracedConstructorField [28-34] [bar: 3]
-                Identifier(bar) [28-31] [bar]
+                BracedConstructorLhs [28-31] [bar]
+                  PathExpression [28-31] [bar]
+                    Identifier(bar) [28-31] [bar]
                 BracedConstructorFieldValue [31-34] [: 3]
                   IntLiteral(3) [33-34] [3]
 --
@@ -226,20 +244,28 @@ QueryStatement [0-77] [SELECT NEW...[1,2,3] }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-77] [{   foo {...[1,2,3] }]
               BracedConstructorField [17-45] [foo {     monkey: "blah"   }]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [21-45] [{     monkey: "blah"   }]
                   BracedConstructor [21-45] [{     monkey: "blah"   }]
                     BracedConstructorField [27-41] [monkey: "blah"]
-                      Identifier(monkey) [27-33] [monkey]
+                      BracedConstructorLhs [27-33] [monkey]
+                        PathExpression [27-33] [monkey]
+                          Identifier(monkey) [27-33] [monkey]
                       BracedConstructorFieldValue [33-41] [: "blah"]
                         StringLiteral [35-41] ["blah"]
                           StringLiteralComponent("blah") [35-41] ["blah"]
               BracedConstructorField [48-54] [bar: 3]
-                Identifier(bar) [48-51] [bar]
+                BracedConstructorLhs [48-51] [bar]
+                  PathExpression [48-51] [bar]
+                    Identifier(bar) [48-51] [bar]
                 BracedConstructorFieldValue [51-54] [: 3]
                   IntLiteral(3) [53-54] [3]
               BracedConstructorField [57-75] [int_array: [1,2,3]]
-                Identifier(int_array) [57-66] [int_array]
+                BracedConstructorLhs [57-66] [int_array]
+                  PathExpression [57-66] [int_array]
+                    Identifier(int_array) [57-66] [int_array]
                 BracedConstructorFieldValue [66-75] [: [1,2,3]]
                   ArrayConstructor [68-75] [[1,2,3]]
                     IntLiteral(1) [69-70] [1]
@@ -268,11 +294,15 @@ QueryStatement [0-49] [SELECT NEW...blah",   }, }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-49] [{   foo {...blah",   }, }]
               BracedConstructorField [17-46] [foo {     monkey: "blah",   }]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [21-46] [{     monkey: "blah",   }]
                   BracedConstructor [21-46] [{     monkey: "blah",   }]
                     BracedConstructorField [27-41] [monkey: "blah"]
-                      Identifier(monkey) [27-33] [monkey]
+                      BracedConstructorLhs [27-33] [monkey]
+                        PathExpression [27-33] [monkey]
+                          Identifier(monkey) [27-33] [monkey]
                       BracedConstructorFieldValue [33-41] [: "blah"]
                         StringLiteral [35-41] ["blah"]
                           StringLiteralComponent("blah") [35-41] ["blah"]
@@ -301,20 +331,28 @@ QueryStatement [0-78] [SELECT NEW...[1,2,3] }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-78] [{   foo: {...[1,2,3] }]
               BracedConstructorField [17-46] [foo: {     monkey: "blah"   }]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-46] [: {     monkey: "blah"   }]
                   BracedConstructor [22-46] [{     monkey: "blah"   }]
                     BracedConstructorField [28-42] [monkey: "blah"]
-                      Identifier(monkey) [28-34] [monkey]
+                      BracedConstructorLhs [28-34] [monkey]
+                        PathExpression [28-34] [monkey]
+                          Identifier(monkey) [28-34] [monkey]
                       BracedConstructorFieldValue [34-42] [: "blah"]
                         StringLiteral [36-42] ["blah"]
                           StringLiteralComponent("blah") [36-42] ["blah"]
               BracedConstructorField [49-55] [bar: 3]
-                Identifier(bar) [49-52] [bar]
+                BracedConstructorLhs [49-52] [bar]
+                  PathExpression [49-52] [bar]
+                    Identifier(bar) [49-52] [bar]
                 BracedConstructorFieldValue [52-55] [: 3]
                   IntLiteral(3) [54-55] [3]
               BracedConstructorField [58-76] [int_array: [1,2,3]]
-                Identifier(int_array) [58-67] [int_array]
+                BracedConstructorLhs [58-67] [int_array]
+                  PathExpression [58-67] [int_array]
+                    Identifier(int_array) [58-67] [int_array]
                 BracedConstructorFieldValue [67-76] [: [1,2,3]]
                   ArrayConstructor [69-76] [[1,2,3]]
                     IntLiteral(1) [70-71] [1]
@@ -346,22 +384,30 @@ QueryStatement [0-100] [SELECT NEW...abc"   }] }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-100] [{   int_field...abc"   }] }]
               BracedConstructorField [17-29] [int_field: 1]
-                Identifier(int_field) [17-26] [int_field]
+                BracedConstructorLhs [17-26] [int_field]
+                  PathExpression [17-26] [int_field]
+                    Identifier(int_field) [17-26] [int_field]
                 BracedConstructorFieldValue [26-29] [: 1]
                   IntLiteral(1) [28-29] [1]
               BracedConstructorField [32-98] [submessage_array..."abc"   }]]
-                Identifier(submessage_array) [32-48] [submessage_array]
+                BracedConstructorLhs [32-48] [submessage_array]
+                  PathExpression [32-48] [submessage_array]
+                    Identifier(submessage_array) [32-48] [submessage_array]
                 BracedConstructorFieldValue [48-98] [: [{     monkey..."abc"   }]]
                   ArrayConstructor [50-98] [[{     monkey..."abc"   }]]
                     BracedConstructor [51-75] [{     monkey: "blah"   }]
                       BracedConstructorField [57-71] [monkey: "blah"]
-                        Identifier(monkey) [57-63] [monkey]
+                        BracedConstructorLhs [57-63] [monkey]
+                          PathExpression [57-63] [monkey]
+                            Identifier(monkey) [57-63] [monkey]
                         BracedConstructorFieldValue [63-71] [: "blah"]
                           StringLiteral [65-71] ["blah"]
                             StringLiteralComponent("blah") [65-71] ["blah"]
                     BracedConstructor [77-97] [{     baz: "abc"   }]
                       BracedConstructorField [83-93] [baz: "abc"]
-                        Identifier(baz) [83-86] [baz]
+                        BracedConstructorLhs [83-86] [baz]
+                          PathExpression [83-86] [baz]
+                            Identifier(baz) [83-86] [baz]
                         BracedConstructorFieldValue [86-93] [: "abc"]
                           StringLiteral [88-93] ["abc"]
                             StringLiteralComponent("abc") [88-93] ["abc"]
@@ -393,31 +439,43 @@ QueryStatement [0-116] [SELECT NEW...: 2   }] }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-116] [{   int_field...: 2   }] }]
               BracedConstructorField [17-29] [int_field: 1]
-                Identifier(int_field) [17-26] [int_field]
+                BracedConstructorLhs [17-26] [int_field]
+                  PathExpression [17-26] [int_field]
+                    Identifier(int_field) [17-26] [int_field]
                 BracedConstructorFieldValue [26-29] [: 1]
                   IntLiteral(1) [28-29] [1]
               BracedConstructorField [32-114] [map_field:...value: 2   }]]
-                Identifier(map_field) [32-41] [map_field]
+                BracedConstructorLhs [32-41] [map_field]
+                  PathExpression [32-41] [map_field]
+                    Identifier(map_field) [32-41] [map_field]
                 BracedConstructorFieldValue [41-114] [: [{     key...value: 2   }]]
                   ArrayConstructor [43-114] [[{     key...value: 2   }]]
                     BracedConstructor [44-78] [{     key:...value: 1   }]
                       BracedConstructorField [50-61] [key: "blah"]
-                        Identifier(key) [50-53] [key]
+                        BracedConstructorLhs [50-53] [key]
+                          PathExpression [50-53] [key]
+                            Identifier(key) [50-53] [key]
                         BracedConstructorFieldValue [53-61] [: "blah"]
                           StringLiteral [55-61] ["blah"]
                             StringLiteralComponent("blah") [55-61] ["blah"]
                       BracedConstructorField [66-74] [value: 1]
-                        Identifier(value) [66-71] [value]
+                        BracedConstructorLhs [66-71] [value]
+                          PathExpression [66-71] [value]
+                            Identifier(value) [66-71] [value]
                         BracedConstructorFieldValue [71-74] [: 1]
                           IntLiteral(1) [73-74] [1]
                     BracedConstructor [80-113] [{     key:...value: 2   }]
                       BracedConstructorField [86-96] [key: "abc"]
-                        Identifier(key) [86-89] [key]
+                        BracedConstructorLhs [86-89] [key]
+                          PathExpression [86-89] [key]
+                            Identifier(key) [86-89] [key]
                         BracedConstructorFieldValue [89-96] [: "abc"]
                           StringLiteral [91-96] ["abc"]
                             StringLiteralComponent("abc") [91-96] ["abc"]
                       BracedConstructorField [101-109] [value: 2]
-                        Identifier(value) [101-106] [value]
+                        BracedConstructorLhs [101-106] [value]
+                          PathExpression [101-106] [value]
+                            Identifier(value) [101-106] [value]
                         BracedConstructorFieldValue [106-109] [: 2]
                           IntLiteral(2) [108-109] [2]
 --
@@ -443,14 +501,17 @@ QueryStatement [0-57] [SELECT NEW...value: 1   } }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-57] [{   (path....value: 1   } }]
               BracedConstructorField [17-55] [(path.to.extensio...lue: 1   }]
-                PathExpression [18-35] [path.to.extension]
-                  Identifier(path) [18-22] [path]
-                  Identifier(`to`) [23-25] [to]
-                  Identifier(extension) [26-35] [extension]
+                BracedConstructorLhs [17-36] [(path.to.extension)]
+                  PathExpression [18-35] [path.to.extension]
+                    Identifier(path) [18-22] [path]
+                    Identifier(`to`) [23-25] [to]
+                    Identifier(extension) [26-35] [extension]
                 BracedConstructorFieldValue [37-55] [{     value: 1   }]
                   BracedConstructor [37-55] [{     value: 1   }]
                     BracedConstructorField [43-51] [value: 1]
-                      Identifier(value) [43-48] [value]
+                      BracedConstructorLhs [43-48] [value]
+                        PathExpression [43-48] [value]
+                          Identifier(value) [43-48] [value]
                       BracedConstructorFieldValue [48-51] [: 1]
                         IntLiteral(1) [50-51] [1]
 --
@@ -458,12 +519,30 @@ SELECT
   NEW x {(path.`to`.extension)  { value : 1 } }
 ==
 
-# Extension without parenthesis is an error.
+# Extension without parenthesis is not a parser error but an analyzer error.
 SELECT NEW x { path.to.extension: 1 }
 --
-ERROR: Syntax error: Expected ":" or "{" but got "." [at 1:20]
-SELECT NEW x { path.to.extension: 1 }
-                   ^
+QueryStatement [0-37] [SELECT NEW...extension: 1 }]
+  Query [0-37] [SELECT NEW...extension: 1 }]
+    Select [0-37] [SELECT NEW...extension: 1 }]
+      SelectList [7-37] [NEW x { path.to.extension: 1 }]
+        SelectColumn [7-37] [NEW x { path.to.extension: 1 }]
+          BracedNewConstructor [7-37] [NEW x { path.to.extension: 1 }]
+            SimpleType [11-12] [x]
+              PathExpression [11-12] [x]
+                Identifier(x) [11-12] [x]
+            BracedConstructor [13-37] [{ path.to.extension: 1 }]
+              BracedConstructorField [15-35] [path.to.extension: 1]
+                BracedConstructorLhs [15-32] [path.to.extension]
+                  PathExpression [15-32] [path.to.extension]
+                    Identifier(path) [15-19] [path]
+                    Identifier(`to`) [20-22] [to]
+                    Identifier(extension) [23-32] [extension]
+                BracedConstructorFieldValue [32-35] [: 1]
+                  IntLiteral(1) [34-35] [1]
+--
+SELECT
+  NEW x { path.`to`.extension : 1 }
 ==
 
 # Extension with fields.
@@ -486,23 +565,30 @@ QueryStatement [0-80] [SELECT NEW...baz: 1 }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-80] [{   foo: "...baz: 1 }]
               BracedConstructorField [17-27] [foo: "bar"]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-27] [: "bar"]
                   StringLiteral [22-27] ["bar"]
                     StringLiteralComponent("bar") [22-27] ["bar"]
               BracedConstructorField [31-69] [(path.to.extensio...lue: 1   }]
-                PathExpression [32-49] [path.to.extension]
-                  Identifier(path) [32-36] [path]
-                  Identifier(`to`) [37-39] [to]
-                  Identifier(extension) [40-49] [extension]
+                BracedConstructorLhs [31-50] [(path.to.extension)]
+                  PathExpression [32-49] [path.to.extension]
+                    Identifier(path) [32-36] [path]
+                    Identifier(`to`) [37-39] [to]
+                    Identifier(extension) [40-49] [extension]
                 BracedConstructorFieldValue [51-69] [{     value: 1   }]
                   BracedConstructor [51-69] [{     value: 1   }]
                     BracedConstructorField [57-65] [value: 1]
-                      Identifier(value) [57-62] [value]
+                      BracedConstructorLhs [57-62] [value]
+                        PathExpression [57-62] [value]
+                          Identifier(value) [57-62] [value]
                       BracedConstructorFieldValue [62-65] [: 1]
                         IntLiteral(1) [64-65] [1]
               BracedConstructorField [72-78] [baz: 1]
-                Identifier(baz) [72-75] [baz]
+                BracedConstructorLhs [72-75] [baz]
+                  PathExpression [72-75] [baz]
+                    Identifier(baz) [72-75] [baz]
                 BracedConstructorFieldValue [75-78] [: 1]
                   IntLiteral(1) [77-78] [1]
 --
@@ -560,7 +646,9 @@ QueryStatement [0-52] [SELECT NEW...extension) }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-52] [{   foo: column...extension) }]
               BracedConstructorField [17-50] [foo: column...extension)]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-50] [: column   (path.to.extension)]
                   FunctionCall [22-50] [column   (path.to.extension)]
                     PathExpression [22-28] [column]
@@ -593,7 +681,9 @@ QueryStatement [0-63] [SELECT NEW...bar: 3 }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-63] [{   foo: NEW...bar: 3 }]
               BracedConstructorField [17-52] [foo: NEW y..."blah"   }]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-52] [: NEW y {..."blah"   }]
                   BracedNewConstructor [22-52] [NEW y {     monkey: "blah"   }]
                     SimpleType [26-27] [y]
@@ -601,12 +691,16 @@ QueryStatement [0-63] [SELECT NEW...bar: 3 }]
                         Identifier(y) [26-27] [y]
                     BracedConstructor [28-52] [{     monkey: "blah"   }]
                       BracedConstructorField [34-48] [monkey: "blah"]
-                        Identifier(monkey) [34-40] [monkey]
+                        BracedConstructorLhs [34-40] [monkey]
+                          PathExpression [34-40] [monkey]
+                            Identifier(monkey) [34-40] [monkey]
                         BracedConstructorFieldValue [40-48] [: "blah"]
                           StringLiteral [42-48] ["blah"]
                             StringLiteralComponent("blah") [42-48] ["blah"]
               BracedConstructorField [55-61] [bar: 3]
-                Identifier(bar) [55-58] [bar]
+                BracedConstructorLhs [55-58] [bar]
+                  PathExpression [55-58] [bar]
+                    Identifier(bar) [55-58] [bar]
                 BracedConstructorFieldValue [58-61] [: 3]
                   IntLiteral(3) [60-61] [3]
 --
@@ -633,19 +727,24 @@ QueryStatement [0-69] [SELECT NEW...blah"   } }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-69] [{   foo: (...blah"   } }]
               BracedConstructorField [17-29] [foo: (3 + 5)]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-29] [: (3 + 5)]
                   BinaryExpression(+) [23-28] [3 + 5]
                     IntLiteral(3) [23-24] [3]
                     IntLiteral(5) [27-28] [5]
               BracedConstructorField [33-67] [(bar.baz)..."blah"   }]
-                PathExpression [34-41] [bar.baz]
-                  Identifier(bar) [34-37] [bar]
-                  Identifier(baz) [38-41] [baz]
+                BracedConstructorLhs [33-42] [(bar.baz)]
+                  PathExpression [34-41] [bar.baz]
+                    Identifier(bar) [34-37] [bar]
+                    Identifier(baz) [38-41] [baz]
                 BracedConstructorFieldValue [43-67] [{     monkey: "blah"   }]
                   BracedConstructor [43-67] [{     monkey: "blah"   }]
                     BracedConstructorField [49-63] [monkey: "blah"]
-                      Identifier(monkey) [49-55] [monkey]
+                      BracedConstructorLhs [49-55] [monkey]
+                        PathExpression [49-55] [monkey]
+                          Identifier(monkey) [49-55] [monkey]
                       BracedConstructorFieldValue [55-63] [: "blah"]
                         StringLiteral [57-63] ["blah"]
                           StringLiteralComponent("blah") [57-63] ["blah"]
@@ -672,7 +771,9 @@ QueryStatement [0-95] [SELECT NEW...blah"   } }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-95] [{   foo: (...blah"   } }]
               BracedConstructorField [17-55] [foo: (SELECT...WHERE t.a = 1)]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-55] [: (SELECT...WHERE t.a = 1)]
                   ExpressionSubquery [22-55] [(SELECT t....WHERE t.a = 1)]
                     Query [23-54] [SELECT t.*...WHERE t.a = 1]
@@ -693,13 +794,16 @@ QueryStatement [0-95] [SELECT NEW...blah"   } }]
                               Identifier(a) [49-50] [a]
                             IntLiteral(1) [53-54] [1]
               BracedConstructorField [59-93] [(bar.baz)..."blah"   }]
-                PathExpression [60-67] [bar.baz]
-                  Identifier(bar) [60-63] [bar]
-                  Identifier(baz) [64-67] [baz]
+                BracedConstructorLhs [59-68] [(bar.baz)]
+                  PathExpression [60-67] [bar.baz]
+                    Identifier(bar) [60-63] [bar]
+                    Identifier(baz) [64-67] [baz]
                 BracedConstructorFieldValue [69-93] [{     monkey: "blah"   }]
                   BracedConstructor [69-93] [{     monkey: "blah"   }]
                     BracedConstructorField [75-89] [monkey: "blah"]
-                      Identifier(monkey) [75-81] [monkey]
+                      BracedConstructorLhs [75-81] [monkey]
+                        PathExpression [75-81] [monkey]
+                          Identifier(monkey) [75-81] [monkey]
                       BracedConstructorFieldValue [81-89] [: "blah"]
                         StringLiteral [83-89] ["blah"]
                           StringLiteralComponent("blah") [83-89] ["blah"]
@@ -733,19 +837,24 @@ QueryStatement [0-67] [SELECT NEW...blah"   } }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-67] [{   foo: 3...blah"   } }]
               BracedConstructorField [17-27] [foo: 3 + 5]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-27] [: 3 + 5]
                   BinaryExpression(+) [22-27] [3 + 5]
                     IntLiteral(3) [22-23] [3]
                     IntLiteral(5) [26-27] [5]
               BracedConstructorField [31-65] [(bar.baz)..."blah"   }]
-                PathExpression [32-39] [bar.baz]
-                  Identifier(bar) [32-35] [bar]
-                  Identifier(baz) [36-39] [baz]
+                BracedConstructorLhs [31-40] [(bar.baz)]
+                  PathExpression [32-39] [bar.baz]
+                    Identifier(bar) [32-35] [bar]
+                    Identifier(baz) [36-39] [baz]
                 BracedConstructorFieldValue [41-65] [{     monkey: "blah"   }]
                   BracedConstructor [41-65] [{     monkey: "blah"   }]
                     BracedConstructorField [47-61] [monkey: "blah"]
-                      Identifier(monkey) [47-53] [monkey]
+                      BracedConstructorLhs [47-53] [monkey]
+                        PathExpression [47-53] [monkey]
+                          Identifier(monkey) [47-53] [monkey]
                       BracedConstructorFieldValue [53-61] [: "blah"]
                         StringLiteral [55-61] ["blah"]
                           StringLiteralComponent("blah") [55-61] ["blah"]
@@ -773,7 +882,9 @@ QueryStatement [0-82] [SELECT NEW...blah"   } }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-82] [{   foo: CONCAT...blah"   } }]
               BracedConstructorField [17-42] [foo: CONCAT("foo", "bar")]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-42] [: CONCAT("foo", "bar")]
                   FunctionCall [22-42] [CONCAT("foo", "bar")]
                     PathExpression [22-28] [CONCAT]
@@ -783,13 +894,16 @@ QueryStatement [0-82] [SELECT NEW...blah"   } }]
                     StringLiteral [36-41] ["bar"]
                       StringLiteralComponent("bar") [36-41] ["bar"]
               BracedConstructorField [46-80] [(bar.baz)..."blah"   }]
-                PathExpression [47-54] [bar.baz]
-                  Identifier(bar) [47-50] [bar]
-                  Identifier(baz) [51-54] [baz]
+                BracedConstructorLhs [46-55] [(bar.baz)]
+                  PathExpression [47-54] [bar.baz]
+                    Identifier(bar) [47-50] [bar]
+                    Identifier(baz) [51-54] [baz]
                 BracedConstructorFieldValue [56-80] [{     monkey: "blah"   }]
                   BracedConstructor [56-80] [{     monkey: "blah"   }]
                     BracedConstructorField [62-76] [monkey: "blah"]
-                      Identifier(monkey) [62-68] [monkey]
+                      BracedConstructorLhs [62-68] [monkey]
+                        PathExpression [62-68] [monkey]
+                          Identifier(monkey) [62-68] [monkey]
                       BracedConstructorFieldValue [68-76] [: "blah"]
                         StringLiteral [70-76] ["blah"]
                           StringLiteralComponent("blah") [70-76] ["blah"]
@@ -814,7 +928,9 @@ QueryStatement [0-56] [SELECT NEW...table_foo) }]
                 Identifier(x) [11-12] [x]
             BracedConstructor [13-56] [{   foo: (...table_foo) }]
               BracedConstructorField [17-54] [foo: (SELECT...table_foo)]
-                Identifier(foo) [17-20] [foo]
+                BracedConstructorLhs [17-20] [foo]
+                  PathExpression [17-20] [foo]
+                    Identifier(foo) [17-20] [foo]
                 BracedConstructorFieldValue [20-54] [: (SELECT...table_foo)]
                   ExpressionSubquery [22-54] [(SELECT count...table_foo)]
                     Query [23-53] [SELECT count(*) FROM table_foo]
@@ -852,12 +968,16 @@ UpdateStatement [0-39] [UPDATE t SET..." bar: 3 }]
           Identifier(x) [13-14] [x]
         BracedConstructor [17-39] [{ foo: "blah" bar: 3 }]
           BracedConstructorField [19-30] [foo: "blah"]
-            Identifier(foo) [19-22] [foo]
+            BracedConstructorLhs [19-22] [foo]
+              PathExpression [19-22] [foo]
+                Identifier(foo) [19-22] [foo]
             BracedConstructorFieldValue [22-30] [: "blah"]
               StringLiteral [24-30] ["blah"]
                 StringLiteralComponent("blah") [24-30] ["blah"]
           BracedConstructorField [31-37] [bar: 3]
-            Identifier(bar) [31-34] [bar]
+            BracedConstructorLhs [31-34] [bar]
+              PathExpression [31-34] [bar]
+                Identifier(bar) [31-34] [bar]
             BracedConstructorFieldValue [34-37] [: 3]
               IntLiteral(3) [36-37] [3]
 --
@@ -884,13 +1004,17 @@ UpdateStatement [0-71] [UPDATE t SET...: "baz" }]]
                 Identifier(ProtoType) [25-34] [ProtoType]
           BracedConstructor [36-52] [{ value: "bar" }]
             BracedConstructorField [38-50] [value: "bar"]
-              Identifier(value) [38-43] [value]
+              BracedConstructorLhs [38-43] [value]
+                PathExpression [38-43] [value]
+                  Identifier(value) [38-43] [value]
               BracedConstructorFieldValue [43-50] [: "bar"]
                 StringLiteral [45-50] ["bar"]
                   StringLiteralComponent("bar") [45-50] ["bar"]
           BracedConstructor [54-70] [{ value: "baz" }]
             BracedConstructorField [56-68] [value: "baz"]
-              Identifier(value) [56-61] [value]
+              BracedConstructorLhs [56-61] [value]
+                PathExpression [56-61] [value]
+                  Identifier(value) [56-61] [value]
               BracedConstructorFieldValue [61-68] [: "baz"]
                 StringLiteral [63-68] ["baz"]
                   StringLiteralComponent("baz") [63-68] ["baz"]
@@ -914,13 +1038,17 @@ UpdateStatement [0-55] [UPDATE t SET...: "baz" }]]
         ArrayConstructor [19-55] [[{ value:...: "baz" }]]
           BracedConstructor [20-36] [{ value: "bar" }]
             BracedConstructorField [22-34] [value: "bar"]
-              Identifier(value) [22-27] [value]
+              BracedConstructorLhs [22-27] [value]
+                PathExpression [22-27] [value]
+                  Identifier(value) [22-27] [value]
               BracedConstructorFieldValue [27-34] [: "bar"]
                 StringLiteral [29-34] ["bar"]
                   StringLiteralComponent("bar") [29-34] ["bar"]
           BracedConstructor [38-54] [{ value: "baz" }]
             BracedConstructorField [40-52] [value: "baz"]
-              Identifier(value) [40-45] [value]
+              BracedConstructorLhs [40-45] [value]
+                PathExpression [40-45] [value]
+                  Identifier(value) [40-45] [value]
               BracedConstructorFieldValue [45-52] [: "baz"]
                 StringLiteral [47-52] ["baz"]
                   StringLiteralComponent("baz") [47-52] ["baz"]
@@ -954,7 +1082,9 @@ UpdateStatement [0-64] [UPDATE t SET...bar" }, 1)]
           StructConstructorArg [44-60] [{ value: "bar" }]
             BracedConstructor [44-60] [{ value: "bar" }]
               BracedConstructorField [46-58] [value: "bar"]
-                Identifier(value) [46-51] [value]
+                BracedConstructorLhs [46-51] [value]
+                  PathExpression [46-51] [value]
+                    Identifier(value) [46-51] [value]
                 BracedConstructorFieldValue [51-58] [: "bar"]
                   StringLiteral [53-58] ["bar"]
                     StringLiteralComponent("bar") [53-58] ["bar"]
@@ -980,7 +1110,9 @@ UpdateStatement [0-40] [UPDATE t SET...bar" }, 1)]
         StructConstructorWithParens [19-40] [({ value: "bar" }, 1)]
           BracedConstructor [20-36] [{ value: "bar" }]
             BracedConstructorField [22-34] [value: "bar"]
-              Identifier(value) [22-27] [value]
+              BracedConstructorLhs [22-27] [value]
+                PathExpression [22-27] [value]
+                  Identifier(value) [22-27] [value]
               BracedConstructorFieldValue [27-34] [: "bar"]
                 StringLiteral [29-34] ["bar"]
                   StringLiteralComponent("bar") [29-34] ["bar"]
@@ -1018,7 +1150,9 @@ UpdateStatement [0-97] [UPDATE t SET...: "foo"}))]
           StructConstructorArg [56-72] [{ value: "bar" }]
             BracedConstructor [56-72] [{ value: "bar" }]
               BracedConstructorField [58-70] [value: "bar"]
-                Identifier(value) [58-63] [value]
+                BracedConstructorLhs [58-63] [value]
+                  PathExpression [58-63] [value]
+                    Identifier(value) [58-63] [value]
                 BracedConstructorFieldValue [63-70] [: "bar"]
                   StringLiteral [65-70] ["bar"]
                     StringLiteralComponent("bar") [65-70] ["bar"]
@@ -1027,7 +1161,9 @@ UpdateStatement [0-97] [UPDATE t SET...: "foo"}))]
               StructConstructorArg [81-95] [{value: "foo"}]
                 BracedConstructor [81-95] [{value: "foo"}]
                   BracedConstructorField [82-94] [value: "foo"]
-                    Identifier(value) [82-87] [value]
+                    BracedConstructorLhs [82-87] [value]
+                      PathExpression [82-87] [value]
+                        Identifier(value) [82-87] [value]
                     BracedConstructorFieldValue [87-94] [: "foo"]
                       StringLiteral [89-94] ["foo"]
                         StringLiteralComponent("foo") [89-94] ["foo"]
@@ -1068,7 +1204,9 @@ UpdateStatement [0-101] [UPDATE t SET...foo"}, 1))]
           StructConstructorArg [63-79] [{ value: "bar" }]
             BracedConstructor [63-79] [{ value: "bar" }]
               BracedConstructorField [65-77] [value: "bar"]
-                Identifier(value) [65-70] [value]
+                BracedConstructorLhs [65-70] [value]
+                  PathExpression [65-70] [value]
+                    Identifier(value) [65-70] [value]
                 BracedConstructorFieldValue [70-77] [: "bar"]
                   StringLiteral [72-77] ["bar"]
                     StringLiteralComponent("bar") [72-77] ["bar"]
@@ -1076,7 +1214,9 @@ UpdateStatement [0-101] [UPDATE t SET...foo"}, 1))]
             StructConstructorWithParens [81-100] [({value: "foo"}, 1)]
               BracedConstructor [82-96] [{value: "foo"}]
                 BracedConstructorField [83-95] [value: "foo"]
-                  Identifier(value) [83-88] [value]
+                  BracedConstructorLhs [83-88] [value]
+                    PathExpression [83-88] [value]
+                      Identifier(value) [83-88] [value]
                   BracedConstructorFieldValue [88-95] [: "foo"]
                     StringLiteral [90-95] ["foo"]
                       StringLiteralComponent("foo") [90-95] ["foo"]
@@ -1143,7 +1283,9 @@ QueryStatement [0-44] [SELECT NEW...: FORK() }]
                 Identifier(Message) [19-26] [Message]
             BracedConstructor [27-44] [{ field: FORK() }]
               BracedConstructorField [29-42] [field: FORK()]
-                Identifier(field) [29-34] [field]
+                BracedConstructorLhs [29-34] [field]
+                  PathExpression [29-34] [field]
+                    Identifier(field) [29-34] [field]
                 BracedConstructorFieldValue [34-42] [: FORK()]
                   FunctionCall [36-42] [FORK()]
                     PathExpression [36-40] [FORK]
diff --git a/zetasql/parser/testdata/struct_braced_constructors.test b/zetasql/parser/testdata/struct_braced_constructors.test
index d0c7b599a..84f7bbab3 100644
--- a/zetasql/parser/testdata/struct_braced_constructors.test
+++ b/zetasql/parser/testdata/struct_braced_constructors.test
@@ -26,12 +26,16 @@ QueryStatement [0-37] [SELECT STRUCT..., bar: 3 }]
           StructBracedConstructor [7-37] [STRUCT { foo: "blah", bar: 3 }]
             BracedConstructor [14-37] [{ foo: "blah", bar: 3 }]
               BracedConstructorField [16-27] [foo: "blah"]
-                Identifier(foo) [16-19] [foo]
+                BracedConstructorLhs [16-19] [foo]
+                  PathExpression [16-19] [foo]
+                    Identifier(foo) [16-19] [foo]
                 BracedConstructorFieldValue [19-27] [: "blah"]
                   StringLiteral [21-27] ["blah"]
                     StringLiteralComponent("blah") [21-27] ["blah"]
               BracedConstructorField [29-35] [bar: 3]
-                Identifier(bar) [29-32] [bar]
+                BracedConstructorLhs [29-32] [bar]
+                  PathExpression [29-32] [bar]
+                    Identifier(bar) [29-32] [bar]
                 BracedConstructorFieldValue [32-35] [: 3]
                   IntLiteral(3) [34-35] [3]
 --
@@ -58,12 +62,16 @@ QueryStatement [0-37] [SELECT STRUCT...bar: 3, }]
           StructBracedConstructor [7-37] [STRUCT {foo: "blah", bar: 3, }]
             BracedConstructor [14-37] [{foo: "blah", bar: 3, }]
               BracedConstructorField [15-26] [foo: "blah"]
-                Identifier(foo) [15-18] [foo]
+                BracedConstructorLhs [15-18] [foo]
+                  PathExpression [15-18] [foo]
+                    Identifier(foo) [15-18] [foo]
                 BracedConstructorFieldValue [18-26] [: "blah"]
                   StringLiteral [20-26] ["blah"]
                     StringLiteralComponent("blah") [20-26] ["blah"]
               BracedConstructorField [28-34] [bar: 3]
-                Identifier(bar) [28-31] [bar]
+                BracedConstructorLhs [28-31] [bar]
+                  PathExpression [28-31] [bar]
+                    Identifier(bar) [28-31] [bar]
                 BracedConstructorFieldValue [31-34] [: 3]
                   IntLiteral(3) [33-34] [3]
 --
@@ -83,11 +91,15 @@ QueryStatement [0-23] [SELECT STRUCT {a:1 b:2}]
           StructBracedConstructor [7-23] [STRUCT {a:1 b:2}]
             BracedConstructor [14-23] [{a:1 b:2}]
               BracedConstructorField [15-18] [a:1]
-                Identifier(a) [15-16] [a]
+                BracedConstructorLhs [15-16] [a]
+                  PathExpression [15-16] [a]
+                    Identifier(a) [15-16] [a]
                 BracedConstructorFieldValue [16-18] [:1]
                   IntLiteral(1) [17-18] [1]
               BracedConstructorField [19-22] [b:2]
-                Identifier(b) [19-20] [b]
+                BracedConstructorLhs [19-20] [b]
+                  PathExpression [19-20] [b]
+                    Identifier(b) [19-20] [b]
                 BracedConstructorFieldValue [20-22] [:2]
                   IntLiteral(2) [21-22] [2]
 --
@@ -107,15 +119,21 @@ QueryStatement [0-28] [SELECT STRUCT {a:1 b:2, c:3}]
           StructBracedConstructor [7-28] [STRUCT {a:1 b:2, c:3}]
             BracedConstructor [14-28] [{a:1 b:2, c:3}]
               BracedConstructorField [15-18] [a:1]
-                Identifier(a) [15-16] [a]
+                BracedConstructorLhs [15-16] [a]
+                  PathExpression [15-16] [a]
+                    Identifier(a) [15-16] [a]
                 BracedConstructorFieldValue [16-18] [:1]
                   IntLiteral(1) [17-18] [1]
               BracedConstructorField [19-22] [b:2]
-                Identifier(b) [19-20] [b]
+                BracedConstructorLhs [19-20] [b]
+                  PathExpression [19-20] [b]
+                    Identifier(b) [19-20] [b]
                 BracedConstructorFieldValue [20-22] [:2]
                   IntLiteral(2) [21-22] [2]
               BracedConstructorField [24-27] [c:3]
-                Identifier(c) [24-25] [c]
+                BracedConstructorLhs [24-25] [c]
+                  PathExpression [24-25] [c]
+                    Identifier(c) [24-25] [c]
                 BracedConstructorFieldValue [25-27] [:3]
                   IntLiteral(3) [26-27] [3]
 --
@@ -139,11 +157,15 @@ QueryStatement [0-38] [SELECT STRUCT...: 1    } }]
           StructBracedConstructor [7-38] [STRUCT {...: 1    } }]
             BracedConstructor [14-38] [{    a {     b: 1    } }]
               BracedConstructorField [19-36] [a {     b: 1    }]
-                Identifier(a) [19-20] [a]
+                BracedConstructorLhs [19-20] [a]
+                  PathExpression [19-20] [a]
+                    Identifier(a) [19-20] [a]
                 BracedConstructorFieldValue [21-36] [{     b: 1    }]
                   BracedConstructor [21-36] [{     b: 1    }]
                     BracedConstructorField [27-31] [b: 1]
-                      Identifier(b) [27-28] [b]
+                      BracedConstructorLhs [27-28] [b]
+                        PathExpression [27-28] [b]
+                          Identifier(b) [27-28] [b]
                       BracedConstructorFieldValue [28-31] [: 1]
                         IntLiteral(1) [30-31] [1]
 --
@@ -170,19 +192,27 @@ QueryStatement [0-59] [SELECT STRUCT...: 1    } }]
           StructBracedConstructor [7-59] [STRUCT {...: 1    } }]
             BracedConstructor [14-59] [{    a {...: 1    } }]
               BracedConstructorField [19-36] [a {     b: 1    }]
-                Identifier(a) [19-20] [a]
+                BracedConstructorLhs [19-20] [a]
+                  PathExpression [19-20] [a]
+                    Identifier(a) [19-20] [a]
                 BracedConstructorFieldValue [21-36] [{     b: 1    }]
                   BracedConstructor [21-36] [{     b: 1    }]
                     BracedConstructorField [27-31] [b: 1]
-                      Identifier(b) [27-28] [b]
+                      BracedConstructorLhs [27-28] [b]
+                        PathExpression [27-28] [b]
+                          Identifier(b) [27-28] [b]
                       BracedConstructorFieldValue [28-31] [: 1]
                         IntLiteral(1) [30-31] [1]
               BracedConstructorField [40-57] [c: {    d: 1    }]
-                Identifier(c) [40-41] [c]
+                BracedConstructorLhs [40-41] [c]
+                  PathExpression [40-41] [c]
+                    Identifier(c) [40-41] [c]
                 BracedConstructorFieldValue [41-57] [: {    d: 1    }]
                   BracedConstructor [43-57] [{    d: 1    }]
                     BracedConstructorField [48-52] [d: 1]
-                      Identifier(d) [48-49] [d]
+                      BracedConstructorLhs [48-49] [d]
+                        PathExpression [48-49] [d]
+                          Identifier(d) [48-49] [d]
                       BracedConstructorFieldValue [49-52] [: 1]
                         IntLiteral(1) [51-52] [1]
 --
@@ -207,20 +237,28 @@ QueryStatement [0-79] [SELECT STRUCT...[1,2,3] }]
           StructBracedConstructor [7-79] [STRUCT {...[1,2,3] }]
             BracedConstructor [14-79] [{   foo: {...[1,2,3] }]
               BracedConstructorField [18-47] [foo: {     monkey: "blah"   }]
-                Identifier(foo) [18-21] [foo]
+                BracedConstructorLhs [18-21] [foo]
+                  PathExpression [18-21] [foo]
+                    Identifier(foo) [18-21] [foo]
                 BracedConstructorFieldValue [21-47] [: {     monkey: "blah"   }]
                   BracedConstructor [23-47] [{     monkey: "blah"   }]
                     BracedConstructorField [29-43] [monkey: "blah"]
-                      Identifier(monkey) [29-35] [monkey]
+                      BracedConstructorLhs [29-35] [monkey]
+                        PathExpression [29-35] [monkey]
+                          Identifier(monkey) [29-35] [monkey]
                       BracedConstructorFieldValue [35-43] [: "blah"]
                         StringLiteral [37-43] ["blah"]
                           StringLiteralComponent("blah") [37-43] ["blah"]
               BracedConstructorField [50-56] [bar: 3]
-                Identifier(bar) [50-53] [bar]
+                BracedConstructorLhs [50-53] [bar]
+                  PathExpression [50-53] [bar]
+                    Identifier(bar) [50-53] [bar]
                 BracedConstructorFieldValue [53-56] [: 3]
                   IntLiteral(3) [55-56] [3]
               BracedConstructorField [59-77] [int_array: [1,2,3]]
-                Identifier(int_array) [59-68] [int_array]
+                BracedConstructorLhs [59-68] [int_array]
+                  PathExpression [59-68] [int_array]
+                    Identifier(int_array) [59-68] [int_array]
                 BracedConstructorFieldValue [68-77] [: [1,2,3]]
                   ArrayConstructor [70-77] [[1,2,3]]
                     IntLiteral(1) [71-72] [1]
@@ -249,22 +287,30 @@ QueryStatement [0-101] [SELECT STRUCT...abc"   }] }]
           StructBracedConstructor [7-101] [STRUCT {...abc"   }] }]
             BracedConstructor [14-101] [{   int_field...abc"   }] }]
               BracedConstructorField [18-30] [int_field: 1]
-                Identifier(int_field) [18-27] [int_field]
+                BracedConstructorLhs [18-27] [int_field]
+                  PathExpression [18-27] [int_field]
+                    Identifier(int_field) [18-27] [int_field]
                 BracedConstructorFieldValue [27-30] [: 1]
                   IntLiteral(1) [29-30] [1]
               BracedConstructorField [33-99] [submessage_array..."abc"   }]]
-                Identifier(submessage_array) [33-49] [submessage_array]
+                BracedConstructorLhs [33-49] [submessage_array]
+                  PathExpression [33-49] [submessage_array]
+                    Identifier(submessage_array) [33-49] [submessage_array]
                 BracedConstructorFieldValue [49-99] [: [{     monkey..."abc"   }]]
                   ArrayConstructor [51-99] [[{     monkey..."abc"   }]]
                     BracedConstructor [52-76] [{     monkey: "blah"   }]
                       BracedConstructorField [58-72] [monkey: "blah"]
-                        Identifier(monkey) [58-64] [monkey]
+                        BracedConstructorLhs [58-64] [monkey]
+                          PathExpression [58-64] [monkey]
+                            Identifier(monkey) [58-64] [monkey]
                         BracedConstructorFieldValue [64-72] [: "blah"]
                           StringLiteral [66-72] ["blah"]
                             StringLiteralComponent("blah") [66-72] ["blah"]
                     BracedConstructor [78-98] [{     baz: "abc"   }]
                       BracedConstructorField [84-94] [baz: "abc"]
-                        Identifier(baz) [84-87] [baz]
+                        BracedConstructorLhs [84-87] [baz]
+                          PathExpression [84-87] [baz]
+                            Identifier(baz) [84-87] [baz]
                         BracedConstructorFieldValue [87-94] [: "abc"]
                           StringLiteral [89-94] ["abc"]
                             StringLiteralComponent("abc") [89-94] ["abc"]
@@ -293,31 +339,43 @@ QueryStatement [0-117] [SELECT STRUCT...: 2   }] }]
           StructBracedConstructor [7-117] [STRUCT {...: 2   }] }]
             BracedConstructor [14-117] [{   int_field...: 2   }] }]
               BracedConstructorField [18-30] [int_field: 1]
-                Identifier(int_field) [18-27] [int_field]
+                BracedConstructorLhs [18-27] [int_field]
+                  PathExpression [18-27] [int_field]
+                    Identifier(int_field) [18-27] [int_field]
                 BracedConstructorFieldValue [27-30] [: 1]
                   IntLiteral(1) [29-30] [1]
               BracedConstructorField [33-115] [map_field:...value: 2   }]]
-                Identifier(map_field) [33-42] [map_field]
+                BracedConstructorLhs [33-42] [map_field]
+                  PathExpression [33-42] [map_field]
+                    Identifier(map_field) [33-42] [map_field]
                 BracedConstructorFieldValue [42-115] [: [{     key...value: 2   }]]
                   ArrayConstructor [44-115] [[{     key...value: 2   }]]
                     BracedConstructor [45-79] [{     key:...value: 1   }]
                       BracedConstructorField [51-62] [key: "blah"]
-                        Identifier(key) [51-54] [key]
+                        BracedConstructorLhs [51-54] [key]
+                          PathExpression [51-54] [key]
+                            Identifier(key) [51-54] [key]
                         BracedConstructorFieldValue [54-62] [: "blah"]
                           StringLiteral [56-62] ["blah"]
                             StringLiteralComponent("blah") [56-62] ["blah"]
                       BracedConstructorField [67-75] [value: 1]
-                        Identifier(value) [67-72] [value]
+                        BracedConstructorLhs [67-72] [value]
+                          PathExpression [67-72] [value]
+                            Identifier(value) [67-72] [value]
                         BracedConstructorFieldValue [72-75] [: 1]
                           IntLiteral(1) [74-75] [1]
                     BracedConstructor [81-114] [{     key:...value: 2   }]
                       BracedConstructorField [87-97] [key: "abc"]
-                        Identifier(key) [87-90] [key]
+                        BracedConstructorLhs [87-90] [key]
+                          PathExpression [87-90] [key]
+                            Identifier(key) [87-90] [key]
                         BracedConstructorFieldValue [90-97] [: "abc"]
                           StringLiteral [92-97] ["abc"]
                             StringLiteralComponent("abc") [92-97] ["abc"]
                       BracedConstructorField [102-110] [value: 2]
-                        Identifier(value) [102-107] [value]
+                        BracedConstructorLhs [102-107] [value]
+                          PathExpression [102-107] [value]
+                            Identifier(value) [102-107] [value]
                         BracedConstructorFieldValue [107-110] [: 2]
                           IntLiteral(2) [109-110] [2]
 --
@@ -341,17 +399,23 @@ QueryStatement [0-65] [SELECT STRUCT...bar: 3 }]
           StructBracedConstructor [7-65] [STRUCT {...bar: 3 }]
             BracedConstructor [14-65] [{   foo: STRUCT...bar: 3 }]
               BracedConstructorField [18-54] [foo: STRUCT..."blah"   }]
-                Identifier(foo) [18-21] [foo]
+                BracedConstructorLhs [18-21] [foo]
+                  PathExpression [18-21] [foo]
+                    Identifier(foo) [18-21] [foo]
                 BracedConstructorFieldValue [21-54] [: STRUCT {..."blah"   }]
                   StructBracedConstructor [23-54] [STRUCT {..."blah"   }]
                     BracedConstructor [30-54] [{     monkey: "blah"   }]
                       BracedConstructorField [36-50] [monkey: "blah"]
-                        Identifier(monkey) [36-42] [monkey]
+                        BracedConstructorLhs [36-42] [monkey]
+                          PathExpression [36-42] [monkey]
+                            Identifier(monkey) [36-42] [monkey]
                         BracedConstructorFieldValue [42-50] [: "blah"]
                           StringLiteral [44-50] ["blah"]
                             StringLiteralComponent("blah") [44-50] ["blah"]
               BracedConstructorField [57-63] [bar: 3]
-                Identifier(bar) [57-60] [bar]
+                BracedConstructorLhs [57-60] [bar]
+                  PathExpression [57-60] [bar]
+                    Identifier(bar) [57-60] [bar]
                 BracedConstructorFieldValue [60-63] [: 3]
                   IntLiteral(3) [62-63] [3]
 --
@@ -371,7 +435,9 @@ QueryStatement [0-58] [SELECT STRUCT...t.a = 1) }]
           StructBracedConstructor [7-58] [STRUCT {...t.a = 1) }]
             BracedConstructor [14-58] [{   foo: (...t.a = 1) }]
               BracedConstructorField [18-56] [foo: (SELECT...WHERE t.a = 1)]
-                Identifier(foo) [18-21] [foo]
+                BracedConstructorLhs [18-21] [foo]
+                  PathExpression [18-21] [foo]
+                    Identifier(foo) [18-21] [foo]
                 BracedConstructorFieldValue [21-56] [: (SELECT...WHERE t.a = 1)]
                   ExpressionSubquery [23-56] [(SELECT t....WHERE t.a = 1)]
                     Query [24-55] [SELECT t.*...WHERE t.a = 1]
@@ -415,7 +481,9 @@ QueryStatement [0-30] [SELECT STRUCT {   foo: 3 + 5 }]
           StructBracedConstructor [7-30] [STRUCT {   foo: 3 + 5 }]
             BracedConstructor [14-30] [{   foo: 3 + 5 }]
               BracedConstructorField [18-28] [foo: 3 + 5]
-                Identifier(foo) [18-21] [foo]
+                BracedConstructorLhs [18-21] [foo]
+                  PathExpression [18-21] [foo]
+                    Identifier(foo) [18-21] [foo]
                 BracedConstructorFieldValue [21-28] [: 3 + 5]
                   BinaryExpression(+) [23-28] [3 + 5]
                     IntLiteral(3) [23-24] [3]
@@ -441,14 +509,17 @@ QueryStatement [0-58] [SELECT STRUCT...value: 1   } }]
           StructBracedConstructor [7-58] [STRUCT {...value: 1   } }]
             BracedConstructor [14-58] [{   (path....value: 1   } }]
               BracedConstructorField [18-56] [(path.to.extensio...lue: 1   }]
-                PathExpression [19-36] [path.to.extension]
-                  Identifier(path) [19-23] [path]
-                  Identifier(`to`) [24-26] [to]
-                  Identifier(extension) [27-36] [extension]
+                BracedConstructorLhs [18-37] [(path.to.extension)]
+                  PathExpression [19-36] [path.to.extension]
+                    Identifier(path) [19-23] [path]
+                    Identifier(`to`) [24-26] [to]
+                    Identifier(extension) [27-36] [extension]
                 BracedConstructorFieldValue [38-56] [{     value: 1   }]
                   BracedConstructor [38-56] [{     value: 1   }]
                     BracedConstructorField [44-52] [value: 1]
-                      Identifier(value) [44-49] [value]
+                      BracedConstructorLhs [44-49] [value]
+                        PathExpression [44-49] [value]
+                          Identifier(value) [44-49] [value]
                       BracedConstructorFieldValue [49-52] [: 1]
                         IntLiteral(1) [51-52] [1]
 --
@@ -469,7 +540,9 @@ QueryStatement [0-46] [SELECT STRUCT..."bar"), }]
           StructBracedConstructor [7-46] [STRUCT {..."bar"), }]
             BracedConstructor [14-46] [{   foo: CONCAT..."bar"), }]
               BracedConstructorField [18-43] [foo: CONCAT("foo", "bar")]
-                Identifier(foo) [18-21] [foo]
+                BracedConstructorLhs [18-21] [foo]
+                  PathExpression [18-21] [foo]
+                    Identifier(foo) [18-21] [foo]
                 BracedConstructorFieldValue [21-43] [: CONCAT("foo", "bar")]
                   FunctionCall [23-43] [CONCAT("foo", "bar")]
                     PathExpression [23-29] [CONCAT]
@@ -496,7 +569,9 @@ QueryStatement [0-57] [SELECT STRUCT...table_foo) }]
           StructBracedConstructor [7-57] [STRUCT {...table_foo) }]
             BracedConstructor [14-57] [{   foo: (...table_foo) }]
               BracedConstructorField [18-55] [foo: (SELECT...table_foo)]
-                Identifier(foo) [18-21] [foo]
+                BracedConstructorLhs [18-21] [foo]
+                  PathExpression [18-21] [foo]
+                    Identifier(foo) [18-21] [foo]
                 BracedConstructorFieldValue [21-55] [: (SELECT...table_foo)]
                   ExpressionSubquery [23-55] [(SELECT count...table_foo)]
                     Query [24-54] [SELECT count(*) FROM table_foo]
@@ -572,7 +647,9 @@ QueryStatement [0-31] [SELECT STRUCT...: FORK() }]
           StructBracedConstructor [7-31] [STRUCT { field: FORK() }]
             BracedConstructor [14-31] [{ field: FORK() }]
               BracedConstructorField [16-29] [field: FORK()]
-                Identifier(field) [16-21] [field]
+                BracedConstructorLhs [16-21] [field]
+                  PathExpression [16-21] [field]
+                    Identifier(field) [16-21] [field]
                 BracedConstructorFieldValue [21-29] [: FORK()]
                   FunctionCall [23-29] [FORK()]
                     PathExpression [23-27] [FORK]
@@ -599,7 +676,9 @@ QueryStatement [0-29] [SELECT STRUCT {a: 1}]
                     Identifier(INT64) [16-21] [INT64]
             BracedConstructor [23-29] [{a: 1}]
               BracedConstructorField [24-28] [a: 1]
-                Identifier(a) [24-25] [a]
+                BracedConstructorLhs [24-25] [a]
+                  PathExpression [24-25] [a]
+                    Identifier(a) [24-25] [a]
                 BracedConstructorFieldValue [25-28] [: 1]
                   IntLiteral(1) [27-28] [1]
 --
diff --git a/zetasql/parser/testdata/tablesample.test b/zetasql/parser/testdata/tablesample.test
index 59c4225a2..19ac160af 100644
--- a/zetasql/parser/testdata/tablesample.test
+++ b/zetasql/parser/testdata/tablesample.test
@@ -1627,3 +1627,32 @@ select * from KeyValue TABLESAMPLE RESERVOIR (100 ROWS PARTITION BY)
 ERROR: Syntax error: Unexpected ")" [at 1:68]
 select * from KeyValue TABLESAMPLE RESERVOIR (100 ROWS PARTITION BY)
                                                                    ^
+==
+
+# Multiple table operators, this becomes an analyzer error
+select a FROM b TABLESAMPLE SYSTEM (1 ROWS) TABLESAMPLE SYSTEM (2 ROWS) 
+--
+QueryStatement [0-71] [select a FROM...YSTEM (2 ROWS)]
+  Query [0-71] [select a FROM...YSTEM (2 ROWS)]
+    Select [0-71] [select a FROM...YSTEM (2 ROWS)]
+      SelectList [7-8] [a]
+        SelectColumn [7-8] [a]
+          PathExpression [7-8] [a]
+            Identifier(a) [7-8] [a]
+      FromClause [9-71] [FROM b TABLESAMPL...M (2 ROWS)]
+        TablePathExpression [14-71] [b TABLESAMPLE...YSTEM (2 ROWS)]
+          PathExpression [14-15] [b]
+            Identifier(b) [14-15] [b]
+          SampleClause [16-43] [TABLESAMPLE SYSTEM (1 ROWS)]
+            Identifier(SYSTEM) [28-34] [SYSTEM]
+            SampleSize [36-42] [1 ROWS]
+              IntLiteral(1) [36-37] [1]
+          SampleClause [44-71] [TABLESAMPLE SYSTEM (2 ROWS)]
+            Identifier(SYSTEM) [56-62] [SYSTEM]
+            SampleSize [64-70] [2 ROWS]
+              IntLiteral(2) [64-65] [2]
+--
+SELECT
+  a
+FROM
+  b TABLESAMPLE SYSTEM(1 ROWS) TABLESAMPLE SYSTEM(2 ROWS)
diff --git a/zetasql/parser/unparser.cc b/zetasql/parser/unparser.cc
index 934b8ebb6..78b1e9547 100644
--- a/zetasql/parser/unparser.cc
+++ b/zetasql/parser/unparser.cc
@@ -362,17 +362,9 @@ void Unparser::visitASTTVF(const ASTTVF* node, void* data) {
   if (node->alias() != nullptr) {
     node->alias()->Accept(this, data);
   }
-  if (node->pivot_clause() != nullptr) {
-    node->pivot_clause()->Accept(this, data);
-  }
-  if (node->unpivot_clause() != nullptr) {
-    node->unpivot_clause()->Accept(this, data);
-  }
-  if (node->match_recognize_clause()) {
-    node->match_recognize_clause()->Accept(this, data);
-  }
-  if (node->sample() != nullptr) {
-    node->sample()->Accept(this, data);
+
+  for (const auto* op : node->postfix_operators()) {
+    op->Accept(this, data);
   }
 }
 
@@ -1923,11 +1915,8 @@ void Unparser::visitASTParenthesizedJoin(const ASTParenthesizedJoin* node,
   println();
   print(")");
 
-  if (node->match_recognize_clause()) {
-    node->match_recognize_clause()->Accept(this, data);
-  }
-  if (node->sample_clause() != nullptr) {
-    node->sample_clause()->Accept(this, data);
+  for (const auto* op : node->postfix_operators()) {
+    op->Accept(this, data);
   }
 }
 
@@ -2225,13 +2214,8 @@ void Unparser::visitASTBracedConstructorFieldValue(
 
 void Unparser::visitASTBracedConstructorField(
     const ASTBracedConstructorField* node, void* data) {
-  if (node->identifier()) {
-    node->identifier()->Accept(this, data);
-  }
-  if (node->parenthesized_path()) {
-    print("(");
-    node->parenthesized_path()->Accept(this, data);
-    print(")");
+  if (node->braced_constructor_lhs()) {
+    node->braced_constructor_lhs()->Accept(this, data);
   }
   node->value()->Accept(this, data);
 }
@@ -2265,6 +2249,11 @@ void Unparser::visitASTStructBracedConstructor(
   node->braced_constructor()->Accept(this, data);
 }
 
+void Unparser::visitASTBracedConstructorLhs(const ASTBracedConstructorLhs* node,
+                                            void* data) {
+  node->extended_path_expr()->Accept(this, data);
+}
+
 void Unparser::visitASTInferredTypeColumnSchema(
     const ASTInferredTypeColumnSchema* node, void* data) {
   UnparseColumnSchema(node, data);
@@ -2597,6 +2586,11 @@ void Unparser::visitASTExtractExpression(const ASTExtractExpression* node,
 
 void Unparser::visitASTCaseNoValueExpression(
     const ASTCaseNoValueExpression* node, void* data) {
+  if (!ThreadHasEnoughStack()) {
+    println("");
+    return;
+  }
+
   println();
   print("CASE");
   int i;
@@ -2815,6 +2809,10 @@ void Unparser::visitASTWithGroupRows(const ASTWithGroupRows* node, void* data) {
 }
 
 void Unparser::visitASTArrayElement(const ASTArrayElement* node, void* data) {
+  if (!ThreadHasEnoughStack()) {
+    println("");
+    return;
+  }
   PrintOpenParenIfNeeded(node);
   node->array()->Accept(this, data);
   print("[");
diff --git a/zetasql/parser/unparser.h b/zetasql/parser/unparser.h
index a667f3188..a99571dc8 100644
--- a/zetasql/parser/unparser.h
+++ b/zetasql/parser/unparser.h
@@ -434,6 +434,8 @@ class Unparser : public ParseTreeVisitor {
                                  void* data) override;
   void visitASTNewConstructor(const ASTNewConstructor* node,
                               void* data) override;
+  void visitASTBracedConstructorLhs(const ASTBracedConstructorLhs* node,
+                                    void* data) override;
   void visitASTBracedConstructorFieldValue(
       const ASTBracedConstructorFieldValue* node, void* data) override;
   void visitASTBracedConstructorField(const ASTBracedConstructorField* node,
diff --git a/zetasql/public/BUILD b/zetasql/public/BUILD
index 8225f1d5f..2aede85fe 100644
--- a/zetasql/public/BUILD
+++ b/zetasql/public/BUILD
@@ -1927,6 +1927,7 @@ cc_library(
     hdrs = ["analyzer_options.h"],
     deps = [
         ":catalog",
+        ":cycle_detector",
         ":error_helpers",
         ":id_string",
         ":options_cc_proto",
diff --git a/zetasql/public/analyzer.cc b/zetasql/public/analyzer.cc
index de081ec2f..1cb8ec3fc 100644
--- a/zetasql/public/analyzer.cc
+++ b/zetasql/public/analyzer.cc
@@ -319,11 +319,7 @@ static absl::Status AnalyzeStatementFromParserOutputImpl(
     local_options->set_id_string_pool(
         (*statement_parser_output)->id_string_pool());
   }
-  CycleDetector owned_cycle_detector;
-  if (local_options->find_options().cycle_detector() == nullptr) {
-    local_options->mutable_find_options()->set_cycle_detector(
-        &owned_cycle_detector);
-  }
+  ZETASQL_RET_CHECK(local_options->find_options().cycle_detector() != nullptr);
 
   const ASTStatement* ast_statement = (*statement_parser_output)->statement();
   return AnalyzeStatementHelper(*ast_statement, *local_options, sql, catalog,
@@ -627,9 +623,12 @@ absl::Status ExtractTableNamesFromScript(absl::string_view sql,
   std::unique_ptr copy;
   const AnalyzerOptions& options = GetOptionsWithArenas(&options_in, ©);
   std::unique_ptr parser_output;
-  ZETASQL_RETURN_IF_ERROR(
-      ParseScript(sql, options.GetParserOptions(), options.error_message_mode(),
-                  options.attach_error_location_payload(), &parser_output));
+  ZETASQL_RETURN_IF_ERROR(ParseScript(
+      sql, options.GetParserOptions(),
+      {.mode = options.error_message_mode(),
+       .attach_error_location_payload = options.attach_error_location_payload(),
+       .stability = GetDefaultErrorMessageStability()},
+      &parser_output));
   ZETASQL_VLOG(5) << "Parsed AST:\n" << parser_output->script()->DebugString();
 
   absl::Status status = table_name_resolver::FindTableNamesInScript(
diff --git a/zetasql/public/analyzer_options.cc b/zetasql/public/analyzer_options.cc
index 5d55ec499..011652ddd 100644
--- a/zetasql/public/analyzer_options.cc
+++ b/zetasql/public/analyzer_options.cc
@@ -25,6 +25,7 @@
 
 #include "zetasql/common/errors.h"
 #include "zetasql/public/catalog.h"
+#include "zetasql/public/cycle_detector.h"
 #include "zetasql/public/options.pb.h"
 #include "zetasql/public/rewriter_interface.h"
 #include "zetasql/public/time_zone_util.h"
@@ -305,9 +306,12 @@ AnalyzerOptions::AnalyzerOptions() : AnalyzerOptions(LanguageOptions()) {}
 AnalyzerOptions::AnalyzerOptions(const LanguageOptions& language_options)
     : data_(new Data{
           .language_options = language_options,
+          .owned_cycle_detector = std::make_shared(),
           .validate_resolved_ast =
               absl::GetFlag(FLAGS_zetasql_validate_resolved_ast),
           .error_message_stability = GetDefaultErrorMessageStability()}) {
+  data_->find_options.set_cycle_detector(data_->owned_cycle_detector.get());
+
   ZETASQL_CHECK_OK(FindTimeZoneByName("America/Los_Angeles",  // Crash OK
                               &data_->default_timezone))
       << "Did you need to install the tzdata package?";
diff --git a/zetasql/public/analyzer_options.h b/zetasql/public/analyzer_options.h
index 47e2af76c..010677637 100644
--- a/zetasql/public/analyzer_options.h
+++ b/zetasql/public/analyzer_options.h
@@ -835,6 +835,12 @@ class AnalyzerOptions {
     // These options determine the language that is accepted.
     LanguageOptions language_options;
 
+    // This stores a default CycleDetector that will be installed into
+    // find_options.cycle_detector by default.  Analyzer code expects
+    // that field to be set under FindOptions, but it's meant primarily
+    // for internal use rather than user configuration.
+    std::shared_ptr owned_cycle_detector;
+
     // These options are used for name lookups into the catalog, i.e., for
     // Catalog::Find*() calls.
     Catalog::FindOptions find_options;
diff --git a/zetasql/public/builtin_function.cc b/zetasql/public/builtin_function.cc
index 6f3224425..5ee4d5665 100644
--- a/zetasql/public/builtin_function.cc
+++ b/zetasql/public/builtin_function.cc
@@ -207,7 +207,7 @@ absl::Status GetBuiltinFunctionsAndTypes(const BuiltinFunctionOptions& options,
   GetArrayMiscFunctions(&type_factory, options, &functions);
   GetArrayAggregationFunctions(&type_factory, options, &functions);
   GetSubscriptFunctions(&type_factory, options, &functions);
-  GetJSONFunctions(&type_factory, options, &functions);
+  ZETASQL_RETURN_IF_ERROR(GetJSONFunctions(&type_factory, options, &functions, &types));
   ZETASQL_RETURN_IF_ERROR(GetMathFunctions(&type_factory, options, &functions, &types));
   GetHllCountFunctions(&type_factory, options, &functions);
   GetD3ACountFunctions(&type_factory, options, &functions);
@@ -255,10 +255,6 @@ absl::Status GetBuiltinFunctionsAndTypes(const BuiltinFunctionOptions& options,
     ZETASQL_RETURN_IF_ERROR(
         GetArrayZipFunctions(&type_factory, options, &functions, &types));
   }
-  if (options.language_options.LanguageFeatureEnabled(
-          FEATURE_TO_JSON_UNSUPPORTED_FIELDS)) {
-    ZETASQL_RETURN_IF_ERROR(GetToJsonBuiltinEnumTypes(&type_factory, options, &types));
-  }
   ZETASQL_RETURN_IF_ERROR(
       GetStandaloneBuiltinEnumTypes(&type_factory, options, &types));
   GetMapCoreFunctions(&type_factory, options, &functions);
diff --git a/zetasql/public/builtin_function.proto b/zetasql/public/builtin_function.proto
index ac74723bb..2eb3b028a 100644
--- a/zetasql/public/builtin_function.proto
+++ b/zetasql/public/builtin_function.proto
@@ -26,7 +26,7 @@ option java_outer_classname = "ZetaSQLFunction";
 // A unique ID for ZetaSQL function signatures.  Resolved ZetaSQL functions
 // will provide one of these enums, and ZetaSQL implementations should map
 // them to something they can evaluate.
-// Next id: 1948
+// Next id: 1949
 enum FunctionSignatureId {
   // User code that switches on this enum must have a default case so
   // builds won't break if new enums get added.
@@ -829,11 +829,13 @@ enum FunctionSignatureId {
   FN_JSON_SUBSCRIPT_STRING = 1690;  // $subscript(json,string)-> json
   FN_TO_JSON_STRING = 1622;         // to_json_string(any[, bool]) -> string
   FN_TO_JSON = 1697;                // to_json(any[, bool]) -> json
-  FN_JSON_QUERY = 1623;             // json_query(string, string) -> string
-  FN_JSON_QUERY_JSON = 1686;        // json_query(json, string) -> json
-  FN_JSON_VALUE = 1624;             // json_value(string[, string]) -> string
-  FN_JSON_VALUE_JSON = 1687;        // json_value(json[, string]) -> string
-  FN_PARSE_JSON = 1698;             // parse_json(string[, string]) -> json
+  FN_TO_JSON_UNSUPPORTED_FIELDS =
+      1948;              // to_json(any[, bool][, unsupported_fields]) -> json
+  FN_JSON_QUERY = 1623;  // json_query(string, string) -> string
+  FN_JSON_QUERY_JSON = 1686;  // json_query(json, string) -> json
+  FN_JSON_VALUE = 1624;       // json_value(string[, string]) -> string
+  FN_JSON_VALUE_JSON = 1687;  // json_value(json[, string]) -> string
+  FN_PARSE_JSON = 1698;       // parse_json(string[, string]) -> json
   FN_FROM_PROTO_TIMESTAMP =
       1626;  // from_proto(google.protobuf.Timestamp) -> timestamp
   FN_FROM_PROTO_DATE = 1627;  // from_proto(google.type.Date) -> date
@@ -1534,7 +1536,13 @@ enum FunctionSignatureId {
   FN_MAP_VALUES_SORTED_BY_KEY = 3011;  // map_values_sorted_by_key(map)
                                        // -> array
   FN_MAP_EMPTY = 3012;                 // map_empty(map) -> bool
-  FN_MAP_INSERT = 3013;  // map_insert(map, K, V[, K, V, ...]) -> bool
-  // map_insert_or_replace(map, K, V[, K, V, ...]) -> bool
+
+  // map_insert(map, K, V[, K, V, ...]) -> map
+  FN_MAP_INSERT = 3013;
+  // map_insert_or_replace(map, K, V[, K, V, ...]) -> map
   FN_MAP_INSERT_OR_REPLACE = 3014;
+  // map_replace(map, K, V[, K, V, ...]) -> map
+  FN_MAP_REPLACE_KV_PAIRS = 3015;
+  // map_replace(map, K[, ...], (V)->V) -> map
+  FN_MAP_REPLACE_K_REPEATED_V_LAMBDA = 3016;
 }
diff --git a/zetasql/public/functions/BUILD b/zetasql/public/functions/BUILD
index 1b17e9f19..37093dc30 100644
--- a/zetasql/public/functions/BUILD
+++ b/zetasql/public/functions/BUILD
@@ -845,6 +845,7 @@ cc_proto_library(
 proto_library(
     name = "unsupported_fields_proto",
     srcs = ["unsupported_fields.proto"],
+    deps = ["//zetasql/public:type_proto"],
 )
 
 cc_proto_library(
diff --git a/zetasql/public/functions/to_json.cc b/zetasql/public/functions/to_json.cc
index fd3243972..4b7c1d2f3 100644
--- a/zetasql/public/functions/to_json.cc
+++ b/zetasql/public/functions/to_json.cc
@@ -83,7 +83,8 @@ template 
 absl::StatusOr ToJsonFromNumeric(
     const T& value, bool stringify_wide_number,
     const LanguageOptions& language_options, const std::string_view type_name,
-    bool canonicalize_zero, UnsupportedFields unsupported_fields) {
+    bool canonicalize_zero,
+    UnsupportedFieldsEnum::UnsupportedFields unsupported_fields) {
   if (!value.HasFractionalPart()) {
     // Check whether the value is int64_t
     if (value >= T(kInt64Min) && value <= T(kInt64Max)) {
@@ -138,12 +139,11 @@ JSONValue ToJsonFromFloat(FloatType value, bool canonicalize_zero) {
 // space when  not less than
 // kNestingLevelStackCheckThreshold. Returns StatusCode::kResourceExhausted when
 // stack overflows.
-absl::StatusOr ToJsonHelper(const Value& value,
-                                       bool stringify_wide_numbers,
-                                       const LanguageOptions& language_options,
-                                       int current_nesting_level,
-                                       bool canonicalize_zero,
-                                       UnsupportedFields unsupported_fields) {
+absl::StatusOr ToJsonHelper(
+    const Value& value, bool stringify_wide_numbers,
+    const LanguageOptions& language_options, int current_nesting_level,
+    bool canonicalize_zero,
+    UnsupportedFieldsEnum::UnsupportedFields unsupported_fields) {
   // Check the stack usage iff the  not less than
   // kNestingLevelStackCheckThreshold.
   if (current_nesting_level >= kNestingLevelStackCheckThreshold) {
@@ -276,19 +276,20 @@ absl::StatusOr ToJsonHelper(const Value& value,
           auto json_element =
               ToJsonHelper(element_value, stringify_wide_numbers,
                            language_options, current_nesting_level + 1,
-                           canonicalize_zero, UnsupportedFields::FAIL);
+                           canonicalize_zero, UnsupportedFieldsEnum::FAIL);
           if (json_element.status().code() ==
               absl::StatusCode::kUnimplemented) {
             // The value type is not supported by TO_JSON, and we should
             // handle this entire array field as a whole, e.g. for IGNORE we
             // return just one `NULL`, instead of an `ARRAY(NULL, ...)`.
-            if (unsupported_fields == UnsupportedFields::FAIL) {
+            if (unsupported_fields == UnsupportedFieldsEnum::FAIL) {
               return json_element.status();
             }
-            if (unsupported_fields == UnsupportedFields::IGNORE) {
+            if (unsupported_fields == UnsupportedFieldsEnum::IGNORE) {
               return JSONValue();
             }
-            ZETASQL_RET_CHECK_EQ(unsupported_fields, UnsupportedFields::PLACEHOLDER);
+            ZETASQL_RET_CHECK_EQ(unsupported_fields,
+                         UnsupportedFieldsEnum::PLACEHOLDER);
             return JSONValue(
                 absl::StrCat("Unsupported: array of ",
                              element_value.type()->ShortTypeName(
@@ -331,16 +332,16 @@ absl::StatusOr ToJsonHelper(const Value& value,
       return json_value;
     }
     default: {
-      if (unsupported_fields == UnsupportedFields::FAIL) {
+      if (unsupported_fields == UnsupportedFieldsEnum::FAIL) {
         return ::zetasql_base::UnimplementedErrorBuilder()
                << "Unsupported argument type "
                << value.type()->ShortTypeName(language_options.product_mode())
                << " for TO_JSON";
       }
-      if (unsupported_fields == UnsupportedFields::IGNORE) {
+      if (unsupported_fields == UnsupportedFieldsEnum::IGNORE) {
         return JSONValue();
       }
-      ZETASQL_RET_CHECK_EQ(unsupported_fields, UnsupportedFields::PLACEHOLDER);
+      ZETASQL_RET_CHECK_EQ(unsupported_fields, UnsupportedFieldsEnum::PLACEHOLDER);
       // The object of unsupported type will be replaced with a json string,
       // and no error will be triggered.
       return JSONValue(absl::StrCat(
@@ -352,11 +353,10 @@ absl::StatusOr ToJsonHelper(const Value& value,
 
 }  // namespace
 
-absl::StatusOr ToJson(const Value& value,
-                                 bool stringify_wide_numbers,
-                                 const LanguageOptions& language_options,
-                                 bool canonicalize_zero,
-                                 UnsupportedFields unsupported_fields) {
+absl::StatusOr ToJson(
+    const Value& value, bool stringify_wide_numbers,
+    const LanguageOptions& language_options, bool canonicalize_zero,
+    UnsupportedFieldsEnum::UnsupportedFields unsupported_fields) {
   return ToJsonHelper(value, stringify_wide_numbers, language_options,
                       /*current_nesting_level=*/0, canonicalize_zero,
                       unsupported_fields);
diff --git a/zetasql/public/functions/to_json.h b/zetasql/public/functions/to_json.h
index ef3253c52..6c5247e43 100644
--- a/zetasql/public/functions/to_json.h
+++ b/zetasql/public/functions/to_json.h
@@ -51,7 +51,8 @@ const int kNestingLevelStackCheckThreshold = 10;
 absl::StatusOr ToJson(
     const Value& value, bool stringify_wide_numbers,
     const LanguageOptions& language_options, bool canonicalize_zero = false,
-    UnsupportedFields unsupported_fields = UnsupportedFields::FAIL);
+    UnsupportedFieldsEnum::UnsupportedFields unsupported_fields =
+        UnsupportedFieldsEnum::FAIL);
 
 }  // namespace functions
 }  // namespace zetasql
diff --git a/zetasql/public/functions/to_json_test.cc b/zetasql/public/functions/to_json_test.cc
index 847075f29..b75fa336b 100644
--- a/zetasql/public/functions/to_json_test.cc
+++ b/zetasql/public/functions/to_json_test.cc
@@ -48,7 +48,8 @@ namespace zetasql {
 namespace functions {
 namespace {
 
-constexpr UnsupportedFields kUnsupportFieldsDefault = UnsupportedFields::FAIL;
+constexpr UnsupportedFieldsEnum::UnsupportedFields kUnsupportFieldsDefault =
+    UnsupportedFieldsEnum::FAIL;
 constexpr absl::StatusCode kUnimplemented = absl::StatusCode::kUnimplemented;
 
 TEST(ToJsonTest, Compliance) {
@@ -109,55 +110,6 @@ TEST(ToJsonTest, Compliance) {
   }
 }
 
-TEST(ToJsonTest, UnsupportedFieldsArg) {
-  std::vector tests;
-
-  const QueryParamsWithResult::FeatureSet default_feature_set = {
-      FEATURE_NAMED_ARGUMENTS, FEATURE_JSON_TYPE,
-      FEATURE_TO_JSON_UNSUPPORTED_FIELDS};
-
-  for (const FunctionTestCall& test : tests) {
-    if (std::any_of(test.params.params().begin(), test.params.params().end(),
-                    [](const Value& param) { return param.is_null(); })) {
-      continue;
-    }
-    const Value& input_value = test.params.param(0);
-    const bool stringify_wide_numbers = test.params.params().size() >= 2
-                                            ? test.params.param(1).bool_value()
-                                            : false;
-    UnsupportedFields unsupported_fields =
-        static_cast(test.params.param(2).enum_value());
-    SCOPED_TRACE(absl::Substitute("$0('$1', '$2')", test.function_name,
-                                  input_value.ShortDebugString(),
-                                  stringify_wide_numbers));
-    zetasql::LanguageOptions language_options;
-    if (test.params.results().size() == 1 &&
-        zetasql_base::ContainsKey(test.params.results().begin()->first,
-                         FEATURE_JSON_STRICT_NUMBER_PARSING)) {
-      language_options.EnableLanguageFeature(
-          FEATURE_JSON_STRICT_NUMBER_PARSING);
-    }
-    absl::StatusOr output =
-        ToJson(input_value, stringify_wide_numbers, language_options,
-               /*canonicalize_zero=*/true, unsupported_fields);
-
-    const QueryParamsWithResult::Result* result =
-        zetasql_base::FindOrNull(test.params.results(), default_feature_set);
-    const Value expected_result_value =
-        result == nullptr ? test.params.results().begin()->second.result
-                          : result->result;
-    const absl::Status expected_status =
-        result == nullptr ? test.params.results().begin()->second.status
-                          : result->status;
-
-    if (expected_status.ok()) {
-      EXPECT_EQ(expected_result_value, values::Json(std::move(output.value())));
-    } else {
-      EXPECT_EQ(output.status().code(), expected_status.code());
-    }
-  }
-}
-
 TEST(ToJsonTest, LegacyCanonicalizeZeroDouble) {
   zetasql::LanguageOptions language_options;
 
diff --git a/zetasql/public/functions/unsupported_fields.proto b/zetasql/public/functions/unsupported_fields.proto
index 41ebd68a9..f121b479f 100644
--- a/zetasql/public/functions/unsupported_fields.proto
+++ b/zetasql/public/functions/unsupported_fields.proto
@@ -18,18 +18,29 @@ syntax = "proto2";
 
 package zetasql.functions;
 
+import "zetasql/public/type.proto";
+
 option java_package = "com.google.zetasql.functions";
 option java_outer_classname = "ZetaSQLUnsupportedFields";
 
 // Enum that defines how TO_JSON() handles fields of unsupported types.
 // See (broken link):safe-to-json for details.
-enum UnsupportedFields {
-  // Return error if unsupported type is encountered.
-  FAIL = 0;
+message UnsupportedFieldsEnum {
+  enum UnsupportedFields {
+    option (opaque_enum_type_options).sql_opaque_enum_name =
+        "UNSUPPORTED_FIELDS";
+
+    // Invalid in ZetaSQL.
+    UNSUPPORTED_FIELDS_INVALID = 0
+        [(opaque_enum_value_options).invalid_enum_value = true];
+
+    // Return error if unsupported type is encountered.
+    FAIL = 1;
 
-  // Ignore all fields of unsupported types.
-  IGNORE = 1;
+    // Ignore all fields of unsupported types.
+    IGNORE = 2;
 
-  // Render a human-readable string to replace the unsupported field.
-  PLACEHOLDER = 2;
+    // Render a human-readable string to replace the unsupported field.
+    PLACEHOLDER = 3;
+  }
 }
diff --git a/zetasql/public/non_sql_function.cc b/zetasql/public/non_sql_function.cc
index c42cdedef..a27e0ca2a 100644
--- a/zetasql/public/non_sql_function.cc
+++ b/zetasql/public/non_sql_function.cc
@@ -47,7 +47,7 @@ NonSqlFunction::NonSqlFunction(
       parse_resume_location_(parse_resume_location) {}
 
 absl::Status NonSqlFunction::Create(
-    const std::string& name, Mode mode,
+    absl::string_view name, Mode mode,
     const std::vector& function_signatures,
     const FunctionOptions& function_options,
     const ResolvedCreateFunctionStmt* resolved_create_function_statement,
diff --git a/zetasql/public/non_sql_function.h b/zetasql/public/non_sql_function.h
index fe6d4dba9..075ba3d42 100644
--- a/zetasql/public/non_sql_function.h
+++ b/zetasql/public/non_sql_function.h
@@ -59,7 +59,7 @@ class NonSqlFunction : public Function {
   // CREATE statement associated with this NonSqlFunction, if applicable.  Must
   // only be set if there is a single FunctionSignature.
   static absl::Status Create(
-      const std::string& name, Mode mode,
+      absl::string_view name, Mode mode,
       const std::vector& function_signatures,
       const FunctionOptions& function_options,
       const ResolvedCreateFunctionStmt* resolved_create_function_statement,
diff --git a/zetasql/public/options.proto b/zetasql/public/options.proto
index 4e6d27788..de7350851 100644
--- a/zetasql/public/options.proto
+++ b/zetasql/public/options.proto
@@ -78,7 +78,7 @@ extend google.protobuf.EnumValueOptions {
 //
 // LanguageOptions::EnableMaximumLanguageFeaturesForDevelopment() enables all
 // features with 'ideally_enabled == true'.
-// Next id: 14065
+// Next id: 14068
 message LanguageFeatureOptions {
   // Indicates whether a feature is enabled in the idealized ZetaSQL. (One
   // reason to disable a feature is if it exists only to support backwards
diff --git a/zetasql/public/table_name_resolver.cc b/zetasql/public/table_name_resolver.cc
index 782d36882..6b5974985 100644
--- a/zetasql/public/table_name_resolver.cc
+++ b/zetasql/public/table_name_resolver.cc
@@ -1528,9 +1528,8 @@ absl::Status TableNameResolver::FindInTVF(
     }
   }
 
-  if (tvf->pivot_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(
-        FindInExpressionsUnder(tvf->pivot_clause(), external_visible_aliases));
+  for (const auto* op : tvf->postfix_operators()) {
+    ZETASQL_RETURN_IF_ERROR(FindInExpressionsUnder(op, external_visible_aliases));
   }
 
   if (tvf->alias() != nullptr) {
@@ -1551,9 +1550,8 @@ absl::Status TableNameResolver::FindInTableSubquery(
   ZETASQL_RETURN_IF_ERROR(FindInQuery(table_subquery->subquery(),
                               external_visible_aliases, &new_aliases));
 
-  if (table_subquery->pivot_clause() != nullptr) {
-    ZETASQL_RETURN_IF_ERROR(FindInExpressionsUnder(table_subquery->pivot_clause(),
-                                           external_visible_aliases));
+  for (const auto* op : table_subquery->postfix_operators()) {
+    ZETASQL_RETURN_IF_ERROR(FindInExpressionsUnder(op, external_visible_aliases));
   }
 
   if (table_subquery->alias() != nullptr) {
@@ -1652,9 +1650,8 @@ absl::Status TableNameResolver::FindInTablePathExpression(
       ZETASQL_RETURN_IF_ERROR(ResolveTablePath(path, table_ref->for_system_time()));
     }
 
-    if (table_ref->pivot_clause() != nullptr) {
-      ZETASQL_RETURN_IF_ERROR(
-          FindInExpressionsUnder(table_ref->pivot_clause(), *visible_aliases));
+    for (const auto* op : table_ref->postfix_operators()) {
+      ZETASQL_RETURN_IF_ERROR(FindInExpressionsUnder(op, *visible_aliases));
     }
 
     if (alias.empty()) {
diff --git a/zetasql/public/table_name_resolver_test.cc b/zetasql/public/table_name_resolver_test.cc
index 4f778092f..7a332c00d 100644
--- a/zetasql/public/table_name_resolver_test.cc
+++ b/zetasql/public/table_name_resolver_test.cc
@@ -75,9 +75,11 @@ TEST(TableNameResolver, ScriptStatementsWithFindTables) {
   // Parse the script and compare actual table references against expected.
   std::unique_ptr parser_output;
   ZETASQL_ASSERT_OK(ParseScript(
-      script, ParserOptions(), ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
-      /*keep_error_location_payload=*/
-      ERROR_MESSAGE_MULTI_LINE_WITH_CARET == ERROR_MESSAGE_WITH_PAYLOAD,
+      script, ParserOptions(),
+      {.mode = ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
+       .attach_error_location_payload =
+           (ERROR_MESSAGE_MULTI_LINE_WITH_CARET == ERROR_MESSAGE_WITH_PAYLOAD),
+       .stability = GetDefaultErrorMessageStability()},
       &parser_output));
 
   AnalyzerOptions analyzer_options;
@@ -110,9 +112,11 @@ TEST(TableNameResolver, ScriptStatementWithTimeTravel) {
   // Parse the script and compare actual table references against expected.
   std::unique_ptr parser_output;
   ZETASQL_ASSERT_OK(ParseScript(
-      script, ParserOptions(), ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
-      /*keep_error_location_payload=*/
-      ERROR_MESSAGE_MULTI_LINE_WITH_CARET == ERROR_MESSAGE_WITH_PAYLOAD,
+      script, ParserOptions(),
+      {.mode = ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
+       .attach_error_location_payload =
+           (ERROR_MESSAGE_MULTI_LINE_WITH_CARET == ERROR_MESSAGE_WITH_PAYLOAD),
+       .stability = GetDefaultErrorMessageStability()},
       &parser_output));
 
   AnalyzerOptions analyzer_options;
@@ -159,9 +163,11 @@ TEST(TableNameResolver, UnsupportedAssignmentStatements) {
   // Parse the script and compare actual table references against expected.
   std::unique_ptr parser_output;
   ZETASQL_ASSERT_OK(ParseScript(
-      script, ParserOptions(), ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
-      /*keep_error_location_payload=*/
-      ERROR_MESSAGE_MULTI_LINE_WITH_CARET == ERROR_MESSAGE_WITH_PAYLOAD,
+      script, ParserOptions(),
+      {.mode = ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
+       .attach_error_location_payload =
+           (ERROR_MESSAGE_MULTI_LINE_WITH_CARET == ERROR_MESSAGE_WITH_PAYLOAD),
+       .stability = GetDefaultErrorMessageStability()},
       &parser_output));
 
   AnalyzerOptions analyzer_options;
@@ -244,10 +250,11 @@ TEST(TableNameResolver, ExtractTableNamesFromASTStatement) {
   // Extract TVF names from AST statement
   std::unique_ptr parser_output;
   ZETASQL_ASSERT_OK(ParseScript(sql, zetasql::ParserOptions(),
-                        zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
-                        /*keep_error_location_payload=*/
-                        zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET ==
-                            ERROR_MESSAGE_WITH_PAYLOAD,
+                        {.mode = zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
+                         .attach_error_location_payload =
+                             (zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET ==
+                              ERROR_MESSAGE_WITH_PAYLOAD),
+                         .stability = GetDefaultErrorMessageStability()},
                         &parser_output));
 
   std::vector>> actual;
@@ -317,10 +324,11 @@ TEST(TableNameResolver, ExtractTableNamesFromASTScript) {
   // Extract TVF names from AST script
   std::unique_ptr parser_output;
   ZETASQL_ASSERT_OK(ParseScript(sql, zetasql::ParserOptions(),
-                        zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
-                        /*keep_error_location_payload=*/
-                        zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET ==
-                            ERROR_MESSAGE_WITH_PAYLOAD,
+                        {.mode = zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET,
+                         .attach_error_location_payload =
+                             (zetasql::ERROR_MESSAGE_MULTI_LINE_WITH_CARET ==
+                              ERROR_MESSAGE_WITH_PAYLOAD),
+                         .stability = GetDefaultErrorMessageStability()},
                         &parser_output));
 
   const zetasql::ASTScript* script = parser_output->script();
diff --git a/zetasql/public/testing/error_matchers.h b/zetasql/public/testing/error_matchers.h
index f53367fb0..637748d47 100644
--- a/zetasql/public/testing/error_matchers.h
+++ b/zetasql/public/testing/error_matchers.h
@@ -253,6 +253,33 @@ MATCHER_P(IsStatementNotSupportedError, statement_kind_name,
          ExplainMatchResult(status_matcher, arg, result_listener);
 }
 
+// Matches an error message indicating that a table is not found.
+//
+// The argument is the name of the table. If the error matched reports a
+// table name then the matcher will assert that the reported name matches
+// the argument in a case insensitive manner.
+MATCHER_P(IsTableNotFoundErrorMessage, table_name,
+          "is an error message indicating the table is not found") {
+  auto name_matcher = ::testing::HasSubstr(absl::AsciiStrToUpper(table_name));
+  auto message_matcher = ::testing::HasSubstr("Table not found");
+  return ExplainMatchResult(name_matcher, absl::AsciiStrToUpper(arg),
+                            result_listener) &&
+         ExplainMatchResult(message_matcher, arg, result_listener);
+}
+
+// Matches an error status indicating that a table is not found.
+//
+// The argument is the name of the table. If the error matched reports a
+// table name then the matcher will assert that the reported name matches
+// the argument in a case insensitive manner.
+MATCHER_P(IsTableNotFoundError, table_name,
+          "is an error indicating the table is not found") {
+  return ExplainMatchResult(
+      absl_testing::StatusIs(absl::StatusCode::kNotFound,
+                             IsTableNotFoundErrorMessage(table_name)),
+      arg, result_listener);
+}
+
 }  // namespace zetasql
 
 #endif  // ZETASQL_PUBLIC_TESTING_ERROR_MATCHERS_H_
diff --git a/zetasql/public/testing/error_matchers_test.cc b/zetasql/public/testing/error_matchers_test.cc
index ee343f746..dbf8fa80d 100644
--- a/zetasql/public/testing/error_matchers_test.cc
+++ b/zetasql/public/testing/error_matchers_test.cc
@@ -109,4 +109,10 @@ TEST(ErrorMatchersTest, IsNamedArgumentNotFoundErrorMessageOld) {
               IsNamedArgumentNotFoundErrorMessage("bounding_algorithm_type"));
 }
 
+TEST(ErrorMatchersTest, IsTableNotFoundError) {
+  absl::Status error = absl::NotFoundError("Table not found: foo");
+  EXPECT_THAT(error.message(), IsTableNotFoundErrorMessage("Foo"));
+  EXPECT_THAT(error, IsTableNotFoundError("fOo"));
+}
+
 }  // namespace zetasql
diff --git a/zetasql/public/type.proto b/zetasql/public/type.proto
index 5b240a5e2..ae0c45bcf 100644
--- a/zetasql/public/type.proto
+++ b/zetasql/public/type.proto
@@ -199,3 +199,6 @@ extend google.protobuf.EnumValueOptions {
   // they are used as opaque enums.
   optional OpaqueEnumValueOptions opaque_enum_value_options = 474580774;
 }
+
+message MeasureTypeProto {
+}
diff --git a/zetasql/public/types/enum_type.cc b/zetasql/public/types/enum_type.cc
index fd5a3c6e7..6ab5164ce 100644
--- a/zetasql/public/types/enum_type.cc
+++ b/zetasql/public/types/enum_type.cc
@@ -240,9 +240,10 @@ bool EnumType::IsSupportedType(const LanguageOptions& language_options) const {
     return language_options.LanguageFeatureEnabled(
         FEATURE_DIFFERENTIAL_PRIVACY_REPORT_FUNCTIONS);
   }
-  // If ARRAY_ZIP_MODE enum is not created as a builtin type, falls through to
+  // If below enums were not created as a builtin type, falls through to
   // the generic logic below.
-  if (is_opaque_ && Equivalent(types::ArrayZipModeEnumType())) {
+  if (is_opaque_ && (Equivalent(types::ArrayZipModeEnumType()) ||
+                     Equivalent(types::UnsupportedFieldsEnumType()))) {
     return true;
   }
 
@@ -257,8 +258,7 @@ bool EnumType::IsSupportedType(const LanguageOptions& language_options) const {
   if (language_options.product_mode() == ProductMode::PRODUCT_EXTERNAL &&
       !Equivalent(types::DatePartEnumType()) &&
       !Equivalent(types::NormalizeModeEnumType()) &&
-      !Equivalent(types::RoundingModeEnumType()) &&
-      !Equivalent(types::UnsupportedFieldsEnumType())) {
+      !Equivalent(types::RoundingModeEnumType())) {
     return false;
   }
 
diff --git a/zetasql/public/types/type_factory.cc b/zetasql/public/types/type_factory.cc
index d3d373518..23870074c 100644
--- a/zetasql/public/types/type_factory.cc
+++ b/zetasql/public/types/type_factory.cc
@@ -1093,7 +1093,9 @@ static const EnumType* s_unsupported_fields_enum_type() {
   static const EnumType* s_unsupported_fields_enum_type = [] {
     const EnumType* enum_type;
     ZETASQL_CHECK_OK(internal::TypeFactoryHelper::MakeOpaqueEnumType(  // Crash OK
-        s_type_factory(), functions::UnsupportedFields_descriptor(), &enum_type,
+        s_type_factory(),
+        functions::UnsupportedFieldsEnum::UnsupportedFields_descriptor(),
+        &enum_type,
         /*catalog_name_path=*/{}));
     return enum_type;
   }();
diff --git a/zetasql/reference_impl/BUILD b/zetasql/reference_impl/BUILD
index 198e8a543..7fe247cfa 100644
--- a/zetasql/reference_impl/BUILD
+++ b/zetasql/reference_impl/BUILD
@@ -224,6 +224,7 @@ cc_library(
         "//zetasql/resolved_ast:resolved_ast_enums_cc_proto",
         "//zetasql/resolved_ast:resolved_node_kind_cc_proto",
         "//zetasql/base:strings",
+        "@com_google_googletest//:gtest",
         "@com_google_absl//absl/algorithm:container",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/cleanup",
@@ -260,6 +261,7 @@ cc_library(
         "@com_google_cc_differential_privacy//algorithms:quantiles",
         "//zetasql/base:flat_set",
         "//zetasql/base:map_util",
+        "//zetasql/base:optional_ref",
         "//zetasql/base:stl_util",
         "//zetasql/base:exactfloat",
         "@com_googlesource_code_re2//:re2",
@@ -303,6 +305,7 @@ cc_test(
     deps = [
         ":common",
         ":evaluation",
+        "//zetasql/base:optional_ref",
         "//zetasql/base/testing:status_matchers",
         "//zetasql/base/testing:zetasql_gtest_main",
         "//zetasql/common:evaluator_registration_utils",
@@ -315,7 +318,10 @@ cc_test(
         "//zetasql/testdata:test_schema_cc_proto",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:cord",
+        "@com_google_absl//absl/types:span",
     ],
 )
 
@@ -359,6 +365,7 @@ cc_library(
         "//zetasql/base:flat_set",
         "//zetasql/base:map_util",
         "//zetasql/base:no_destructor",
+        "//zetasql/base:optional_ref",
         "//zetasql/base:ret_check",
         "//zetasql/base:status",
         "//zetasql/base:stl_util",
@@ -391,6 +398,7 @@ cc_library(
         "//zetasql/resolved_ast:resolved_node_kind_cc_proto",
         "//zetasql/resolved_ast:serialization_cc_proto",
         "@com_google_absl//absl/algorithm:container",
+        "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/cleanup",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/container:flat_hash_set",
diff --git a/zetasql/reference_impl/algebrizer.cc b/zetasql/reference_impl/algebrizer.cc
index ece8467c3..85fedab20 100644
--- a/zetasql/reference_impl/algebrizer.cc
+++ b/zetasql/reference_impl/algebrizer.cc
@@ -311,75 +311,17 @@ absl::StatusOr> Algebrizer::AlgebrizeLambda(
   return InlineLambdaExpr::Create(lambda_arg_vars, std::move(lambda_body));
 }
 
-absl::StatusOr>
-Algebrizer::AlgebrizeFunctionCallWithLambda(
-    const ResolvedFunctionCall* function_call) {
-  if (!function_call->function()->IsZetaSQLBuiltin()) {
-    return absl::UnimplementedError(
-        "User-defined functions with lambda arguments are not supported");
-  }
-
-  std::vector> args;
-  for (const auto& arg : function_call->generic_argument_list()) {
-    if (arg->expr() != nullptr) {
-      ZETASQL_ASSIGN_OR_RETURN(auto expr, AlgebrizeExpression(arg->expr()));
-      args.push_back(std::make_unique(std::move(expr)));
-    } else if (arg->inline_lambda() != nullptr) {
-      ZETASQL_ASSIGN_OR_RETURN(auto lambda, AlgebrizeLambda(arg->inline_lambda()));
-      args.push_back(std::make_unique(std::move(lambda)));
-    } else if (arg->sequence() != nullptr) {
-      return absl::UnimplementedError(
-          "Functions with both sequence and lambda arguments "
-          "are not supported");
-    } else {
-      return zetasql_base::InternalErrorBuilder()
-             << "Unexpected argument: " << arg->DebugString()
-             << " for function call: " << function_call->DebugString();
-    }
-  }
-
-  std::string name =
-      function_call->function()->FullName(/*include_group=*/false);
-  ZETASQL_ASSIGN_OR_RETURN(FunctionKind kind,
-                   BuiltinFunctionCatalog::GetKindByName(name));
-
-  static const auto* const kScalarArrayFunctions =
-      new absl::flat_hash_set{
-          "array_offset", "array_find", "array_offsets", "array_find_all"};
-  if (kScalarArrayFunctions->contains(name)) {
-    return AlgebrizeScalarArrayFunctionWithCollation(
-        kind, function_call->type(), name, std::move(args),
-        function_call->collation_list());
-  }
-
-  ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr function_call_expr,
-                   BuiltinScalarFunction::CreateCall(
-                       kind, language_options_, function_call->type(),
-                       std::move(args), function_call->error_mode()));
-  return function_call_expr;
-}
-
-// Returns if `function_call` has any lambda argument.
-static bool HasLambdaArgument(const ResolvedFunctionCall* function_call) {
-  return std::any_of(
-      function_call->generic_argument_list().begin(),
-      function_call->generic_argument_list().end(),
-      [](const std::unique_ptr& arg) {
-        return arg->inline_lambda() != nullptr;
-      });
-}
-
 namespace {
 constexpr absl::string_view kCollatedFunctionNamePostfix = "_with_collation";
 // Creates function name and arguments with respect to the collations in
 //  given original function name  and .
 absl::Status GetCollatedFunctionNameAndArguments(
     absl::string_view function_name,
-    std::vector> arguments,
+    std::vector> arguments,
     absl::Span collation_list,
     const LanguageOptions& language_options,
     std::string* collated_function_name,
-    std::vector>* collated_arguments) {
+    std::vector>* collated_arguments) {
   // So far we only support functions that takes a single collator.
   ZETASQL_RET_CHECK(collation_list.size() == 1)
       << "The collation_list can only contain one element for function "
@@ -399,17 +341,19 @@ absl::Status GetCollatedFunctionNameAndArguments(
     //  is the same as .
     *collated_function_name = function_name;
     for (int i = 0; i < arguments.size(); ++i) {
-      std::vector> collation_key_args;
+      std::vector> collation_key_args;
       collation_key_args.reserve(2);
       collation_key_args.push_back(std::move(arguments[i]));
       ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr collation_name_expr,
                        ConstExpr::Create(Value::String(collation_name)));
-      collation_key_args.push_back(std::move(collation_name_expr));
+      collation_key_args.push_back(
+          std::make_unique(std::move(collation_name_expr)));
       ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr collation_key,
                        BuiltinScalarFunction::CreateCall(
                            FunctionKind::kCollationKey, language_options,
                            types::StringType(), std::move(collation_key_args)));
-      collated_arguments->push_back(std::move(collation_key));
+      collated_arguments->push_back(
+          std::make_unique(std::move(collation_key)));
     }
   } else if (function_name == "replace" || function_name == "split" ||
              function_name == "strpos" || function_name == "instr" ||
@@ -434,8 +378,9 @@ absl::Status GetCollatedFunctionNameAndArguments(
     *collated_arguments = std::move(arguments);
     ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr collation_name_expr,
                      ConstExpr::Create(Value::String(collation_name)));
-    collated_arguments->insert(collated_arguments->cbegin(),
-                               std::move(collation_name_expr));
+    collated_arguments->insert(
+        collated_arguments->cbegin(),
+        std::make_unique(std::move(collation_name_expr)));
   } else if (function_name == "$case_with_value" ||
              function_name == "$in_array" || function_name == "array_min" ||
              function_name == "array_max" || function_name == "array_offset" ||
@@ -491,10 +436,28 @@ GetBuiltinFunctionSignatureIdToKindMap() {
               {FN_JSON_SUBSCRIPT_INT64, FunctionKind::kJsonSubscript},
               {FN_MAP_SUBSCRIPT, FunctionKind::kMapSubscript},
               {FN_MAP_SUBSCRIPT_WITH_KEY, FunctionKind::kMapSubscriptWithKey},
+              {FN_MAP_REPLACE_KV_PAIRS, FunctionKind::kMapReplaceKeyValuePairs},
           }));
   return *kBuiltinFunctionSignatureIdToKindMap;
 };
 
+// Helper function to convert a series of AlgebraArg to ValueExpr.
+// TODO: b/359716173: Remove this function once all usages of ValueExpr to
+// represent function arguments are migrated to AlgebraArg.
+static absl::StatusOr>>
+ConvertAlgebraArgsToValueExprs(
+    std::vector>&& arguments) {
+  std::vector> converted_arguments;
+  converted_arguments.reserve(arguments.size());
+  for (auto& e : arguments) {
+    ZETASQL_RET_CHECK(e->value_expr() != nullptr) << absl::StrFormat(
+        "The argument %s is not a value expression.", e->DebugString());
+
+    converted_arguments.push_back(e->release_value_expr());
+  }
+  return converted_arguments;
+}
+
 }  // namespace
 
 absl::StatusOr> Algebrizer::AlgebrizeFunctionCall(
@@ -511,15 +474,11 @@ absl::StatusOr> Algebrizer::AlgebrizeFunctionCall(
            << "Function " << name << "does not support SAFE error mode";
   }
 
-  if (HasLambdaArgument(function_call)) {
-    return AlgebrizeFunctionCallWithLambda(function_call);
-  }
-
   bool use_generic_args = function_call->generic_argument_list_size() > 0;
   int num_arguments = use_generic_args
                           ? function_call->generic_argument_list_size()
                           : function_call->argument_list_size();
-  std::vector> arguments;
+  std::vector> arguments;
   for (int i = 0; i < num_arguments; ++i) {
     const ResolvedExpr* argument_expr = nullptr;
 
@@ -536,7 +495,13 @@ absl::StatusOr> Algebrizer::AlgebrizeFunctionCall(
         ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr sequence_name,
                          ConstExpr::Create(Value::StringValue(absl::StrCat(
                              "_sequence_", sequence->sequence()->FullName()))));
-        arguments.push_back(std::move(sequence_name));
+        arguments.push_back(
+            std::make_unique(std::move(sequence_name)));
+      } else if (function_arg->inline_lambda() != nullptr) {
+        ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr lambda,
+                         AlgebrizeLambda(function_arg->inline_lambda()));
+        arguments.push_back(
+            std::make_unique(std::move(lambda)));
       } else {
         return ::zetasql_base::UnimplementedErrorBuilder()
                << "Unimplemented generic function argument: "
@@ -552,7 +517,7 @@ absl::StatusOr> Algebrizer::AlgebrizeFunctionCall(
 
     if (argument_expr != nullptr) {
       ZETASQL_ASSIGN_OR_RETURN(auto argument, AlgebrizeExpression(argument_expr));
-      arguments.push_back(std::move(argument));
+      arguments.push_back(std::make_unique(std::move(argument)));
     }
   }
   // User-defined functions.
@@ -663,7 +628,7 @@ absl::StatusOr> Algebrizer::AlgebrizeFunctionCall(
   // new arguments with respect to the collations.
   if (!function_call->collation_list().empty()) {
     std::string collated_name;
-    std::vector> collated_arguments;
+    std::vector> collated_arguments;
     ZETASQL_RETURN_IF_ERROR(GetCollatedFunctionNameAndArguments(
         name, std::move(arguments), function_call->collation_list(),
         language_options_, &collated_name, &collated_arguments));
@@ -675,7 +640,9 @@ absl::StatusOr> Algebrizer::AlgebrizeFunctionCall(
 
   FunctionKind kind;
   if (name == "$not_equal") {
-    return AlgebrizeNotEqual(std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeNotEqual(std::move(argument_exprs));
   } else if (name == "$greater") {
     kind = FunctionKind::kLess;
     std::swap(arguments[0], arguments[1]);
@@ -683,43 +650,75 @@ absl::StatusOr> Algebrizer::AlgebrizeFunctionCall(
     kind = FunctionKind::kLessOrEqual;
     std::swap(arguments[0], arguments[1]);
   } else if (name == "typeof") {
-    return CreateTypeofExpr(std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return CreateTypeofExpr(std::move(argument_exprs));
   } else if (name == "if") {
-    return AlgebrizeIf(function_call->type(), std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeIf(function_call->type(), std::move(argument_exprs));
   } else if (name == "iferror") {
-    return AlgebrizeIfError(std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeIfError(std::move(argument_exprs));
   } else if (name == "iserror") {
-    return AlgebrizeIsError(std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeIsError(std::move(argument_exprs));
   } else if (name == "ifnull" || name == "zeroifnull") {
-    return AlgebrizeIfNull(function_call->type(), std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeIfNull(function_call->type(), std::move(argument_exprs));
   } else if (name == "nullif" || name == "nullifzero") {
-    return AlgebrizeNullIf(function_call->type(), std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeNullIf(function_call->type(), std::move(argument_exprs));
   } else if (name == "nulliferror") {
-    return CreateNullIfErrorExpr(std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return CreateNullIfErrorExpr(std::move(argument_exprs));
   } else if (name == "coalesce") {
-    return AlgebrizeCoalesce(function_call->type(), std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeCoalesce(function_call->type(), std::move(argument_exprs));
   } else if (name == "$case_no_value") {
-    return AlgebrizeCaseNoValue(function_call->type(), std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeCaseNoValue(function_call->type(),
+                                std::move(argument_exprs));
   } else if (name == "$case_with_value") {
-    return AlgebrizeCaseWithValue(function_call->type(), std::move(arguments),
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeCaseWithValue(function_call->type(),
+                                  std::move(argument_exprs),
                                   function_call->collation_list());
   } else if (name == "$with_side_effects") {
-    return AlgebrizeWithSideEffects(std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeWithSideEffects(std::move(argument_exprs));
   } else if (name == "$in") {
-    return AlgebrizeIn(function_call->type(), std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeIn(function_call->type(), std::move(argument_exprs));
   } else if (name == "$between") {
-    return AlgebrizeBetween(function_call->type(), std::move(arguments));
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
+    return AlgebrizeBetween(function_call->type(), std::move(argument_exprs));
   } else if (name == "$make_array") {
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
     ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr new_array_expr,
                      NewArrayExpr::Create(function_call->type()->AsArray(),
-                                          std::move(arguments)));
+                                          std::move(argument_exprs)));
     return new_array_expr;
   } else if (name == "$in_array") {
     const std::vector& collation_list =
         function_call->collation_list();
     ZETASQL_RET_CHECK_LE(collation_list.size(), 1);
+    ZETASQL_ASSIGN_OR_RETURN(std::vector> argument_exprs,
+                     ConvertAlgebraArgsToValueExprs(std::move(arguments)));
     return AlgebrizeInArray(
-        std::move(arguments[0]), std::move(arguments[1]),
+        std::move(argument_exprs[0]), std::move(argument_exprs[1]),
         collation_list.empty() ? ResolvedCollation() : collation_list[0]);
   } else if (name == "float64") {
     kind = FunctionKind::kDouble;
@@ -812,12 +811,14 @@ absl::StatusOr> Algebrizer::AlgebrizeCaseWithValue(
 
     if (!collation_list.empty()) {
       std::string collated_name;
-      std::vector> collated_cond_arguments;
+      std::vector> collated_cond_arguments;
       ZETASQL_RETURN_IF_ERROR(GetCollatedFunctionNameAndArguments(
-          "$equal", std::move(cond_args), collation_list, language_options_,
-          &collated_name, &collated_cond_arguments));
+          "$equal", ConvertValueExprsToAlgebraArgs(std::move(cond_args)),
+          collation_list, language_options_, &collated_name,
+          &collated_cond_arguments));
       ZETASQL_RET_CHECK_EQ(collated_name, "$equal");
-      cond_args = std::move(collated_cond_arguments);
+      ZETASQL_ASSIGN_OR_RETURN(cond_args, ConvertAlgebraArgsToValueExprs(
+                                      std::move(collated_cond_arguments)));
     }
 
     ZETASQL_ASSIGN_OR_RETURN(auto cond, BuiltinScalarFunction::CreateCall(
@@ -1977,12 +1978,14 @@ Algebrizer::AlgebrizeInLikeAnyLikeAllRelation(
   if (!collation.Empty()) {
     ZETASQL_RET_CHECK(compare_fn == FunctionKind::kEqual);
     std::string collated_fn_name;
-    std::vector> collated_equal_args;
+    std::vector> collated_equal_args;
     ZETASQL_RETURN_IF_ERROR(GetCollatedFunctionNameAndArguments(
-        "$equal", std::move(equal_args), {collation}, language_options_,
-        &collated_fn_name, &collated_equal_args));
+        "$equal", ConvertValueExprsToAlgebraArgs(std::move(equal_args)),
+        {collation}, language_options_, &collated_fn_name,
+        &collated_equal_args));
     ZETASQL_RET_CHECK_EQ(collated_fn_name, "$equal");
-    equal_args = std::move(collated_equal_args);
+    ZETASQL_ASSIGN_OR_RETURN(equal_args, ConvertAlgebraArgsToValueExprs(
+                                     std::move(collated_equal_args)));
   }
 
   ZETASQL_ASSIGN_OR_RETURN(
diff --git a/zetasql/reference_impl/function.cc b/zetasql/reference_impl/function.cc
index 9c1417ae9..b07a3c116 100644
--- a/zetasql/reference_impl/function.cc
+++ b/zetasql/reference_impl/function.cc
@@ -1131,6 +1131,8 @@ FunctionMap::FunctionMap() {
     RegisterFunction(FunctionKind::kMapInsert, "map_insert", "MapInsert");
     RegisterFunction(FunctionKind::kMapInsertOrReplace, "map_insert_or_replace",
                      "MapInsertOrReplace");
+    RegisterFunction(FunctionKind::kMapReplaceKeyValuePairs,
+                     "map_replace_key_value_pairs", "MapReplaceKeyValuePairs");
   }();
 }  // NOLINT(readability/fn_size)
 
@@ -1700,18 +1702,22 @@ functions::TimestampScale GetTimestampScale(const LanguageOptions& options) {
 ABSL_CONST_INIT absl::Mutex BuiltinFunctionRegistry::mu_(absl::kConstInit);
 
 /* static */ absl::StatusOr
-BuiltinFunctionRegistry::GetScalarFunction(FunctionKind kind,
-                                           const Type* output_type) {
+BuiltinFunctionRegistry::GetScalarFunction(
+    FunctionKind kind, const Type* output_type,
+    absl::Span> arguments) {
   absl::MutexLock lock(&mu_);
   auto it = GetFunctionMap().find(kind);
-  if (it != GetFunctionMap().end()) {
-    return it->second(output_type);
-  } else {
+  if (it == GetFunctionMap().end()) {
     return zetasql_base::UnimplementedErrorBuilder(zetasql_base::SourceLocation::current())
            << BuiltinFunctionCatalog::GetDebugNameByKind(kind)
            << " is an optional function implementation which is not present "
               "in this binary or has not been registered";
   }
+
+  auto function_constructor = it->second;
+  BuiltinScalarFunction* function = function_constructor(output_type);
+  function->SetExtendedArgs(arguments);
+  return function;
 }
 
 /* static */ void BuiltinFunctionRegistry::RegisterScalarFunction(
@@ -2090,21 +2096,6 @@ BuiltinScalarFunction::CreateCast(
       std::move(args), error_mode);
 }
 
-absl::StatusOr>
-BuiltinScalarFunction::CreateCall(
-    FunctionKind kind, const LanguageOptions& language_options,
-    const Type* output_type, std::vector> arguments,
-    ResolvedFunctionCallBase::ErrorMode error_mode) {
-  std::vector> converted_arguments;
-  converted_arguments.reserve(arguments.size());
-  for (auto& e : arguments) {
-    converted_arguments.push_back(std::make_unique(std::move(e)));
-  }
-
-  return CreateCall(kind, language_options, output_type,
-                    std::move(converted_arguments), error_mode);
-}
-
 absl::StatusOr>
 BuiltinScalarFunction::CreateCall(
     FunctionKind kind, const LanguageOptions& language_options,
@@ -2372,67 +2363,6 @@ BuiltinScalarFunction::CreateValidatedRaw(
       return new GenerateArrayFunction(output_type);
     case FunctionKind::kRangeBucket:
       return new RangeBucketFunction();
-    case FunctionKind::kJsonSubscript:
-    case FunctionKind::kJsonExtract:
-    case FunctionKind::kJsonExtractScalar:
-    case FunctionKind::kJsonExtractArray:
-    case FunctionKind::kJsonExtractStringArray:
-    case FunctionKind::kJsonQuery:
-    case FunctionKind::kJsonValue:
-    case FunctionKind::kJsonQueryArray:
-    case FunctionKind::kJsonValueArray:
-    case FunctionKind::kToJson:
-    case FunctionKind::kToJsonString:
-    case FunctionKind::kParseJson:
-    case FunctionKind::kJsonType:
-    case FunctionKind::kStringArray:
-    case FunctionKind::kInt32:
-    case FunctionKind::kInt32Array:
-    case FunctionKind::kInt64:
-    case FunctionKind::kInt64Array:
-    case FunctionKind::kUint32:
-    case FunctionKind::kUint32Array:
-    case FunctionKind::kUint64:
-    case FunctionKind::kUint64Array:
-    case FunctionKind::kDouble:
-    case FunctionKind::kDoubleArray:
-    case FunctionKind::kFloat:
-    case FunctionKind::kFloatArray:
-    case FunctionKind::kBool:
-    case FunctionKind::kBoolArray:
-    case FunctionKind::kLaxBool:
-    case FunctionKind::kLaxBoolArray:
-    case FunctionKind::kLaxInt32:
-    case FunctionKind::kLaxInt32Array:
-    case FunctionKind::kLaxInt64:
-    case FunctionKind::kLaxInt64Array:
-    case FunctionKind::kLaxUint32:
-    case FunctionKind::kLaxUint32Array:
-    case FunctionKind::kLaxUint64:
-    case FunctionKind::kLaxUint64Array:
-    case FunctionKind::kLaxDouble:
-    case FunctionKind::kLaxDoubleArray:
-    case FunctionKind::kLaxFloat:
-    case FunctionKind::kLaxFloatArray:
-    case FunctionKind::kLaxString:
-    case FunctionKind::kLaxStringArray:
-    case FunctionKind::kJsonArray:
-    case FunctionKind::kJsonObject:
-    case FunctionKind::kJsonRemove:
-    case FunctionKind::kJsonSet:
-    case FunctionKind::kJsonStripNulls:
-    case FunctionKind::kJsonArrayInsert:
-    case FunctionKind::kJsonArrayAppend:
-      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type);
-    case FunctionKind::kStartsWithWithCollation:
-    case FunctionKind::kEndsWithWithCollation:
-    case FunctionKind::kReplaceWithCollation:
-    case FunctionKind::kStrposWithCollation:
-    case FunctionKind::kInstrWithCollation:
-    case FunctionKind::kSplitWithCollation:
-    case FunctionKind::kSplitSubstrWithCollation:
-    case FunctionKind::kCollationKey:
-      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type);
     case FunctionKind::kArrayConcat:
       return new ArrayConcatFunction(kind, output_type);
     case FunctionKind::kArrayLength:
@@ -2579,29 +2509,8 @@ BuiltinScalarFunction::CreateValidatedRaw(
       break;
     case FunctionKind::kRand:
       return new RandFunction;
-    case FunctionKind::kGenerateUuid:
-    case FunctionKind::kNewUuid:
-      // UUID functions are optional.
-      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type);
-    case FunctionKind::kMd5:
-    case FunctionKind::kSha1:
-    case FunctionKind::kSha256:
-    case FunctionKind::kSha512:
-    case FunctionKind::kFarmFingerprint:
-      // Hash functions are optional.
-      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type);
     case FunctionKind::kError:
       return new ErrorFunction(output_type);
-    case FunctionKind::kRangeCtor:
-    case FunctionKind::kRangeIsStartUnbounded:
-    case FunctionKind::kRangeIsEndUnbounded:
-    case FunctionKind::kRangeStart:
-    case FunctionKind::kRangeEnd:
-    case FunctionKind::kRangeOverlaps:
-    case FunctionKind::kRangeIntersect:
-    case FunctionKind::kGenerateRangeArray:
-    case FunctionKind::kRangeContains:
-      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type);
     case FunctionKind::kCosineDistance: {
       ZETASQL_ASSIGN_OR_RETURN(auto f,
                        CreateCosineDistanceFunction(input_types, output_type));
@@ -2646,13 +2555,9 @@ BuiltinScalarFunction::CreateValidatedRaw(
                        GetLambdaArgumentForArrayZip(arguments));
       return new ArrayZipFunction(kind, output_type, inline_lambda_expr);
     }
-    case FunctionKind::kMapFromArray:
-    case FunctionKind::kMapEntriesSorted:
-    case FunctionKind::kMapEntriesUnsorted:
-    case FunctionKind::kMapGet:
-      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type);
     default:
-      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type);
+      return BuiltinFunctionRegistry::GetScalarFunction(kind, output_type,
+                                                        arguments);
   }
 }  // NOLINT(readability/fn_size)
 
@@ -12566,4 +12471,14 @@ bool ArrayZipFunction::EqualArrayLength(absl::Span arrays) const {
   return true;
 }
 
+std::vector> ConvertValueExprsToAlgebraArgs(
+    std::vector>&& arguments) {
+  std::vector> converted_arguments;
+  converted_arguments.reserve(arguments.size());
+  for (auto& e : arguments) {
+    converted_arguments.push_back(std::make_unique(std::move(e)));
+  }
+  return converted_arguments;
+}
+
 }  // namespace zetasql
diff --git a/zetasql/reference_impl/function.h b/zetasql/reference_impl/function.h
index ed9f2a994..05abeacc2 100644
--- a/zetasql/reference_impl/function.h
+++ b/zetasql/reference_impl/function.h
@@ -22,6 +22,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -46,12 +47,14 @@
 #include "zetasql/reference_impl/tuple.h"
 #include "zetasql/reference_impl/tuple_comparator.h"
 #include "zetasql/resolved_ast/resolved_ast.h"
+#include "absl/base/macros.h"
 #include "zetasql/base/check.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
 #include "absl/synchronization/mutex.h"
 #include "absl/types/span.h"
+#include "zetasql/base/optional_ref.h"
 #include "re2/re2.h"
 #include "zetasql/base/status.h"
 
@@ -530,6 +533,7 @@ enum class FunctionKind {
   kMapEmpty,
   kMapInsert,
   kMapInsertOrReplace,
+  kMapReplaceKeyValuePairs,
 };
 
 // Provides two utility methods to look up a built-in function name or function
@@ -547,6 +551,12 @@ class BuiltinFunctionCatalog {
   BuiltinFunctionCatalog() = default;
 };
 
+// Helper function to convert a series of ValueExpr to AlgebraArg.
+// TODO: b/359716173: Remove this function once all usages of ValueExpr to
+// represent function arguments are migrated to AlgebraArg.
+std::vector> ConvertValueExprsToAlgebraArgs(
+    std::vector>&& arguments);
+
 // Abstract built-in scalar function.
 class BuiltinScalarFunction : public ScalarFunctionBody {
  public:
@@ -579,12 +589,18 @@ class BuiltinScalarFunction : public ScalarFunctionBody {
 
   // Similar to the above, but for functions which do not accept any lambda
   // arguments.
+  // TODO: b/359716173: Remove this function once all usages have been inlined.
+  ABSL_DEPRECATED("Inline me!")
   static absl::StatusOr> CreateCall(
       FunctionKind kind, const LanguageOptions& language_options,
       const Type* output_type,
       std::vector> arguments,
       ResolvedFunctionCallBase::ErrorMode error_mode =
-          ResolvedFunctionCallBase::DEFAULT_ERROR_MODE);
+          ResolvedFunctionCallBase::DEFAULT_ERROR_MODE) {
+    return CreateCall(kind, language_options, output_type,
+                      ConvertValueExprsToAlgebraArgs(std::move(arguments)),
+                      error_mode);
+  }
 
   static absl::StatusOr> CreateCast(
       const LanguageOptions& language_options, const Type* output_type,
@@ -603,6 +619,35 @@ class BuiltinScalarFunction : public ScalarFunctionBody {
       const Type* output_type,
       absl::Span> arguments);
 
+  // Given a list of the AlgebraArgs to a function, store the arguments which
+  // cannot be represented as `zetasql::Value`. Intended to be used only once,
+  // immediately after construction. Only arguments which cannot be represented
+  // as `zetasql::Value` are included, and the rest are set as nullopt.
+  void SetExtendedArgs(
+      absl::Span> arguments) {
+    ABSL_DCHECK_EQ(extended_args_.size(), 0)
+        << "Function extended_args_ should not be set more than once.";
+
+    extended_args_.clear();
+    extended_args_.reserve(arguments.size());
+
+    for (const auto& arg : arguments) {
+      if (arg->value_expr() == nullptr) {
+        extended_args_.push_back(arg.get());
+      } else {
+        extended_args_.push_back(std::nullopt);
+      }
+    }
+  }
+
+ protected:
+  // Function arguments which cannot be represented as `zetasql::Value`,
+  // such as `InlineLambdaArg`. Order and position of the non-value arguments is
+  // preserved by using nullopt in place of Value-coercible arguments.
+  const std::vector>& extended_args() const {
+    return extended_args_;
+  }
+
  private:
   // Like CreateValidated(), but returns a raw pointer with ownership.
   static absl::StatusOr CreateValidatedRaw(
@@ -633,6 +678,7 @@ class BuiltinScalarFunction : public ScalarFunctionBody {
                        absl::Span> arguments);
 
   FunctionKind kind_;
+  std::vector> extended_args_;
 };
 
 // Alternate form of BuiltinScalarFunction that is easier to implement for
@@ -764,7 +810,8 @@ class BuiltinFunctionRegistry {
   // Returns an unowned pointer to the function. The caller is responsible for
   // cleanup.
   static absl::StatusOr GetScalarFunction(
-      FunctionKind kind, const Type* output_type);
+      FunctionKind kind, const Type* output_type,
+      absl::Span> arguments);
 
   // Registers a function implementation for one or more FunctionKinds.
   static void RegisterScalarFunction(
@@ -2247,7 +2294,6 @@ absl::Status MakeMaxArrayValueByteSizeExceededError(
 
 // Returns TimestampScale to use based on language options.
 functions::TimestampScale GetTimestampScale(const LanguageOptions& options);
-
 }  // namespace zetasql
 
 #endif  // ZETASQL_REFERENCE_IMPL_FUNCTION_H_
diff --git a/zetasql/reference_impl/function_test.cc b/zetasql/reference_impl/function_test.cc
index 93869a254..d27a2cd05 100644
--- a/zetasql/reference_impl/function_test.cc
+++ b/zetasql/reference_impl/function_test.cc
@@ -17,8 +17,10 @@
 #include "zetasql/reference_impl/function.h"
 
 #include 
+#include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
@@ -43,7 +45,12 @@
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/cord.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
+#include "absl/types/span.h"
+#include "zetasql/base/optional_ref.h"
 
 namespace zetasql {
 
@@ -540,4 +547,77 @@ TEST(NonDeterministicEvaluationContextTest,
   }
 }
 
+namespace {
+
+class BasicTestFunction : public SimpleBuiltinScalarFunction {
+ public:
+  BasicTestFunction(FunctionKind kind, const Type* output_type)
+      : SimpleBuiltinScalarFunction(kind, output_type) {}
+  absl::StatusOr Eval(absl::Span params,
+                             absl::Span args,
+                             EvaluationContext* context) const override {
+    return absl::UnimplementedError("Not implemented");
+  }
+
+  std::vector> non_value_args_for_testing()
+      const {
+    return extended_args();
+  }
+};
+}  // namespace
+
+std::unique_ptr CreateLambdaExprForTesting() {
+  return InlineLambdaExpr::Create(
+      /*arguments=*/{VariableId("e")},
+      /*body=*/ConstExpr::Create(Value::Bool(true)).value());
+}
+
+TEST(BuiltinFunctionRegistryTest, ScalarFunctionRegistrationAndLookup) {
+  BuiltinFunctionRegistry::RegisterScalarFunction(
+      {FunctionKind::kIsNull}, [](FunctionKind kind, const Type* output_type) {
+        return new BasicTestFunction(kind, output_type);
+      });
+
+  ZETASQL_ASSERT_OK_AND_ASSIGN(BuiltinScalarFunction * unowned_test_fn,
+                       BuiltinFunctionRegistry::GetScalarFunction(
+                           FunctionKind::kIsNull, types::BoolType(), {}));
+  auto test_fn = absl::WrapUnique(unowned_test_fn);
+  EXPECT_EQ(test_fn->kind(), FunctionKind::kIsNull);
+  EXPECT_EQ(test_fn->output_type(), types::BoolType());
+}
+
+TEST(BuiltinFunctionRegistryTest, CreateCallPopulatesNonValueArgsInFunction) {
+  BuiltinFunctionRegistry::RegisterScalarFunction(
+      // Must be a function type that uses BuiltinFunctionRegistry.
+      {FunctionKind::kMapFromArray},
+      [](FunctionKind kind, const Type* output_type) {
+        return new BasicTestFunction(kind, output_type);
+      });
+
+  // Create a call with a const string and a lambda argument.
+  std::vector> args;
+  ZETASQL_ASSERT_OK_AND_ASSIGN(std::unique_ptr expr,
+                       ConstExpr::Create(Value::StringValue("foo")));
+  args.push_back(std::make_unique(std::move(expr)));
+  args.push_back(
+      std::make_unique(CreateLambdaExprForTesting()));
+
+  ZETASQL_ASSERT_OK_AND_ASSIGN(std::unique_ptr call,
+                       BuiltinScalarFunction::CreateCall(
+                           FunctionKind::kMapFromArray, LanguageOptions(),
+                           types::BoolType(), std::move(args)));
+
+  std::function)>
+      algebra_arg_formatter =
+          [](std::string* out, zetasql_base::optional_ref arg) {
+            absl::StrAppend(out, arg.has_value() ? arg->DebugString() : "null");
+          };
+
+  EXPECT_THAT(
+      absl::StrJoin(static_cast(call->function())
+                        ->non_value_args_for_testing(),
+                    ", ", algebra_arg_formatter),
+      absl::StrCat("null, ", CreateLambdaExprForTesting()->DebugString()));
+}
+
 }  // namespace zetasql
diff --git a/zetasql/reference_impl/functions/json.cc b/zetasql/reference_impl/functions/json.cc
index c4b86a97d..846c8153f 100644
--- a/zetasql/reference_impl/functions/json.cc
+++ b/zetasql/reference_impl/functions/json.cc
@@ -625,10 +625,12 @@ absl::StatusOr ToJsonFunction::Eval(
   }
   const bool stringify_wide_numbers = args[1].bool_value();
   ZETASQL_RETURN_IF_ERROR(ValidateMicrosPrecision(args[0], context));
-  zetasql::functions::UnsupportedFields unsupported_fields =
-      args.size() == 3 ? static_cast(
-                             args[2].enum_value())
-                       : zetasql::functions::UnsupportedFields::FAIL;
+  zetasql::functions::UnsupportedFieldsEnum::UnsupportedFields
+      unsupported_fields =
+          args.size() == 3
+              ? static_cast(args[2].enum_value())
+              : zetasql::functions::UnsupportedFieldsEnum::FAIL;
   ZETASQL_ASSIGN_OR_RETURN(
       JSONValue outputJson,
       functions::ToJson(args[0], stringify_wide_numbers,
diff --git a/zetasql/reference_impl/functions/map.cc b/zetasql/reference_impl/functions/map.cc
index db8ff4860..f3b9b0dda 100644
--- a/zetasql/reference_impl/functions/map.cc
+++ b/zetasql/reference_impl/functions/map.cc
@@ -16,6 +16,7 @@
 
 #include "zetasql/reference_impl/functions/map.h"
 
+#include 
 #include 
 #include 
 #include 
@@ -402,10 +403,43 @@ class MapEmptyFunction : public SimpleBuiltinScalarFunction {
   }
 };
 
-static inline absl::StatusOr MapInsertImpl(
-    const bool replace_existing_values, const Type* output_type,
-    absl::Span params, absl::Span args,
-    EvaluationContext* context) {
+static inline absl::Status CheckKeyExistsInMap(const Value& map,
+                                               const Value& key) {
+  if (!map.map_entries().contains(key)) {
+    return MakeEvalError() << "Key does not exist in map: "
+                           << key.Format(/*print_top_level_type=*/false);
+  }
+  return absl::OkStatus();
+}
+
+enum class KeyExistenceConstraint {
+  kNone,          // No constraint on key existence.
+  kMustExist,     // The key must already exist in the map.
+  kMustNotExist,  // The key must not already exist in the map.
+};
+
+// Given a set of keys and a new key, adds the new key to the set if it is not
+// already present. Returns an error if the new key is already present. This is
+// used to ensure that a key passed into a map modification function is not
+// provided more than once.  is the key to add,  is the set of
+// keys already present to be appended with .
+static inline absl::Status AddMapKeyToInsertionSetOrErrorIfNotUnique(
+    const Value& next_key, absl::flat_hash_set& keys) {
+  const auto& [unused_iter, success] = keys.insert(next_key);
+  if (!success) {
+    return MakeEvalError() << "Key provided more than once as argument: "
+                           << next_key.Format(/*print_top_level_type=*/false);
+  }
+  return absl::OkStatus();
+}
+
+// Implementation for functions which modify a map by adding or replacing
+// key/value pairs.  controls the precondition for
+// existence within the map value for the keys provided in the arguments.
+static inline absl::StatusOr PairwiseMapModificationFunctionImpl(
+    const KeyExistenceConstraint key_existence_constraint,
+    const Type* output_type, absl::Span params,
+    absl::Span args, EvaluationContext* context) {
   ZETASQL_RET_CHECK(args.size() >= 3) << args.size();
   ZETASQL_RET_CHECK(args.size() % 2 == 1)
       << args.size()
@@ -419,34 +453,41 @@ static inline absl::StatusOr MapInsertImpl(
   }
 
   // (size - 1) because the first argument is the map, which we don't include.
-  const int num_entries = (args.size() - 1) / 2;
+  const size_t keys_to_modify_count = (args.size() - 1) / 2;
 
   absl::flat_hash_set keys_to_insert;
-  keys_to_insert.reserve(num_entries);
-  std::vector> entries;
-  entries.reserve(num_entries);
+  keys_to_insert.reserve(keys_to_modify_count);
+  std::vector> map_entries;
+  map_entries.reserve(
+      (key_existence_constraint == KeyExistenceConstraint::kMustNotExist
+           ? map.num_elements() + keys_to_modify_count
+           : map.num_elements()));
 
   // The ZETASQL_RET_CHECK above should catch any issue with unexpected size, but just
   // to be safe, check (size - 1) here since the loop increments by 2.
   for (int i = 1; i < args.size() - 1; i += 2) {
-    const auto& [unused, success] = keys_to_insert.emplace(args[i]);
-    if (!success) {
-      return MakeEvalError() << "Key provided more than once as argument: "
-                             << args[i].Format(/*print_top_level_type=*/false);
+    ZETASQL_RETURN_IF_ERROR(
+        AddMapKeyToInsertionSetOrErrorIfNotUnique(args[i], keys_to_insert));
+    map_entries.push_back({args[i], args[i + 1]});
+  }
+
+  if (key_existence_constraint == KeyExistenceConstraint::kMustExist) {
+    for (const auto& key : keys_to_insert) {
+      ZETASQL_RETURN_IF_ERROR(CheckKeyExistsInMap(map, key));
     }
-    entries.push_back({args[i], args[i + 1]});
   }
 
   for (const auto& [key, value] : map.map_entries()) {
     if (!keys_to_insert.contains(key)) {
-      entries.push_back({key, value});
-    } else if (!replace_existing_values) {
+      map_entries.push_back({key, value});
+    } else if (key_existence_constraint ==
+               KeyExistenceConstraint::kMustNotExist) {
       return MakeEvalError() << "Key already exists in map: "
                              << key.Format(/*print_top_level_type=*/false);
     }
   }
 
-  return Value::MakeMap(args[0].type(), std::move(entries));
+  return Value::MakeMap(output_type, std::move(map_entries));
 }
 
 class MapInsertFunction : public SimpleBuiltinScalarFunction {
@@ -456,8 +497,9 @@ class MapInsertFunction : public SimpleBuiltinScalarFunction {
   absl::StatusOr Eval(absl::Span params,
                              absl::Span args,
                              EvaluationContext* context) const override {
-    return MapInsertImpl(/*replace_existing_values=*/false, output_type(),
-                         params, args, context);
+    return PairwiseMapModificationFunctionImpl(
+        KeyExistenceConstraint::kMustNotExist, output_type(), params, args,
+        context);
   }
 };
 
@@ -468,8 +510,21 @@ class MapInsertOrReplaceFunction : public SimpleBuiltinScalarFunction {
   absl::StatusOr Eval(absl::Span params,
                              absl::Span args,
                              EvaluationContext* context) const override {
-    return MapInsertImpl(/*replace_existing_values=*/true, output_type(),
-                         params, args, context);
+    return PairwiseMapModificationFunctionImpl(
+        KeyExistenceConstraint::kNone, output_type(), params, args, context);
+  }
+};
+
+class MapReplaceKeysAndValuesFunction : public SimpleBuiltinScalarFunction {
+ public:
+  MapReplaceKeysAndValuesFunction(FunctionKind kind, const Type* output_type)
+      : SimpleBuiltinScalarFunction(kind, output_type) {}
+  absl::StatusOr Eval(absl::Span params,
+                             absl::Span args,
+                             EvaluationContext* context) const override {
+    return PairwiseMapModificationFunctionImpl(
+        KeyExistenceConstraint::kMustExist, output_type(), params, args,
+        context);
   }
 };
 
@@ -545,5 +600,11 @@ void RegisterBuiltinMapFunctions() {
       [](FunctionKind kind, const Type* output_type) {
         return new MapInsertOrReplaceFunction(kind, output_type);
       });
+  BuiltinFunctionRegistry::RegisterScalarFunction(
+      {FunctionKind::kMapReplaceKeyValuePairs},
+      [](FunctionKind kind, const Type* output_type) {
+        return new MapReplaceKeysAndValuesFunction(kind, output_type);
+      });
 }
+
 }  // namespace zetasql
diff --git a/zetasql/reference_impl/operator.cc b/zetasql/reference_impl/operator.cc
index bb877fbfa..7d3df0dd2 100644
--- a/zetasql/reference_impl/operator.cc
+++ b/zetasql/reference_impl/operator.cc
@@ -25,6 +25,7 @@
 #include "zetasql/common/thread_stack.h"
 #include "zetasql/public/type.h"
 #include "absl/base/attributes.h"
+#include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_join.h"
 #include "zetasql/base/stl_util.h"
@@ -53,6 +54,13 @@ ValueExpr* AlgebraArg::mutable_value_expr() {
   return node() ? mutable_node()->AsMutableValueExpr() : nullptr;
 }
 
+std::unique_ptr AlgebraArg::release_value_expr() {
+  if (node() == nullptr || node()->AsValueExpr() == nullptr) {
+    return nullptr;
+  }
+  return absl::WrapUnique(node_.release()->AsMutableValueExpr());
+}
+
 const RelationalOp* AlgebraArg::relational_op() const {
   return node() ? node()->AsRelationalOp() : nullptr;
 }
diff --git a/zetasql/reference_impl/operator.h b/zetasql/reference_impl/operator.h
index fc5e87b19..f5b01da4c 100644
--- a/zetasql/reference_impl/operator.h
+++ b/zetasql/reference_impl/operator.h
@@ -64,6 +64,7 @@
 #include "zetasql/resolved_ast/resolved_collation.h"
 #include "zetasql/resolved_ast/resolved_column.h"
 #include "zetasql/resolved_ast/resolved_node.h"
+#include "gtest/gtest_prod.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/container/node_hash_map.h"
 #include "absl/hash/hash.h"
@@ -123,6 +124,7 @@ class AlgebraArg {
   // Convenience method, returns node()->AsValueExpr() or nullptr.
   const ValueExpr* value_expr() const;
   ValueExpr* mutable_value_expr();
+  std::unique_ptr release_value_expr();
   // Convenience method, returns node()->AsRelationalOp() or nullptr.
   const RelationalOp* relational_op() const;
   RelationalOp* mutable_relational_op();
@@ -3159,6 +3161,8 @@ class ScalarFunctionCallExpr final : public ValueExpr {
   ScalarFunctionCallExpr& operator=(const ScalarFunctionCallExpr&) = delete;
 
   const ScalarFunctionBody* function() const { return function_.get(); }
+  FRIEND_TEST(BuiltinFunctionRegistryTest,
+              CreateCallPopulatesNonValueArgsInFunction);
 
   std::unique_ptr function_;
   const ResolvedFunctionCallBase::ErrorMode error_mode_;
diff --git a/zetasql/reference_impl/value_expr.cc b/zetasql/reference_impl/value_expr.cc
index 1e1c28a14..d4d072f44 100644
--- a/zetasql/reference_impl/value_expr.cc
+++ b/zetasql/reference_impl/value_expr.cc
@@ -499,9 +499,13 @@ absl::Status DerefExpr::SetSchemasForEvaluation(
 
 bool DerefExpr::Eval(absl::Span params,
                      EvaluationContext* context, VirtualTupleSlot* result,
-                     absl::Status* /* status */) const {
-  ABSL_DCHECK(idx_in_params_ >= 0 && slot_ >= 0)
-      << "You forgot to call SetSchemasForEvaluation() " << name_;
+                     absl::Status* status) const {
+  if (idx_in_params_ < 0 || slot_ < 0) {
+    *status = zetasql_base::InternalErrorBuilder()
+              << "No schemas are available for " << name_
+              << ". SetSchemasForEvaluation() may not have run.";
+    return false;
+  }
   result->CopyFromSlot(params[idx_in_params_]->slot(slot_));
   return true;
 }
diff --git a/zetasql/resolved_ast/sql_builder.h b/zetasql/resolved_ast/sql_builder.h
index 3d19a6b5b..7a81aee7e 100644
--- a/zetasql/resolved_ast/sql_builder.h
+++ b/zetasql/resolved_ast/sql_builder.h
@@ -224,7 +224,7 @@ class SQLBuilder : public ResolvedASTVisitor {
     // Records syntax hint against specific Resolved AST node to decide what
     // SQL syntax to restore. The syntax hint is particularly useful when
     // multiple SQL syntaxes are possible.
-    // WARNING: his map should only be populated by ZetaSQL resolver and RQG.
+    // WARNING: This map should only be populated by ZetaSQL resolver and RQG.
     // It's an advanced feature with specific intended use cases and misusing it
     // can cause the SQLBuilder to misbehave.
     TargetSyntaxMap target_syntax;
diff --git a/zetasql/tools/execute_query/BUILD b/zetasql/tools/execute_query/BUILD
index e05247fae..c60ffec1a 100644
--- a/zetasql/tools/execute_query/BUILD
+++ b/zetasql/tools/execute_query/BUILD
@@ -13,10 +13,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("//zetasql/tools/execute_query:build_rules.bzl", "execute_query_test")
+
 package(
     default_visibility = ["//zetasql/base:zetasql_implementation"],
 )
 
+exports_files(["run_execute_query_test.sh"])
+
 proto_library(
     name = "execute_query_proto",
     srcs = ["execute_query.proto"],
@@ -123,8 +128,10 @@ cc_binary(
         "//zetasql/public:catalog",
         "//zetasql/public:language_options",
         "//zetasql/public:simple_catalog",
+        "@com_google_absl//absl/flags:config",
         "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/flags:usage",
         "@com_google_absl//absl/functional:bind_front",
         "@com_google_absl//absl/log:initialize",
         "@com_google_absl//absl/memory",
@@ -538,3 +545,20 @@ cc_library(
         "@com_google_absl//absl/synchronization",
     ],
 )
+
+execute_query_test(
+    name = "run_execute_query_test",
+    args = ["--catalog=tpch"],
+    sql_file = "testdata/run_execute_query_test.sql",
+)
+
+execute_query_test(
+    name = "run_execute_query_bad_parse_test",
+    args = ["--mode=parse"],
+    sql_file = "testdata/run_execute_query_test_bad.sql",
+)
+
+bzl_library(
+    name = "build_rules_bzl",
+    srcs = ["build_rules.bzl"],
+)
diff --git a/zetasql/tools/execute_query/build_rules.bzl b/zetasql/tools/execute_query/build_rules.bzl
new file mode 100644
index 000000000..6c7f46748
--- /dev/null
+++ b/zetasql/tools/execute_query/build_rules.bzl
@@ -0,0 +1,42 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+This build rule invokes `execute_query`, running the SQL from `sql_file`.
+`args` are passed as additional command-line arguments to `execute_query`.
+This just runs the script and validates that it succeeds, without checking
+query output.
+"""
+
+def execute_query_test(
+        name,
+        sql_file,
+        args = [],
+        **kwargs):
+    native.sh_test(
+        name = name,
+        srcs = ["//zetasql/tools/execute_query:run_execute_query_test.sh"],
+        args = [
+            "$(location //zetasql/tools/execute_query)",
+            "$(location " + sql_file + ")",
+        ] + args,
+        data = [
+            sql_file,
+            # bazel doesn't support listing this as `tools`.
+            "//zetasql/tools/execute_query",
+        ],
+        **kwargs
+    )
diff --git a/zetasql/tools/execute_query/execute_query.cc b/zetasql/tools/execute_query/execute_query.cc
index f2c375237..dbb8b254d 100644
--- a/zetasql/tools/execute_query/execute_query.cc
+++ b/zetasql/tools/execute_query/execute_query.cc
@@ -37,9 +37,12 @@
 #include "zetasql/tools/execute_query/execute_query_web.h"
 #include "zetasql/tools/execute_query/execute_query_writer.h"
 #include "absl/flags/flag.h"
-#include "absl/flags/parse.h"
 #include "absl/functional/bind_front.h"
+#include "absl/flags/parse.h"
+#include "absl/flags/usage.h"
+#include "absl/flags/usage_config.h"
 #include "absl/log/initialize.h"
+#include "absl/strings/match.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_format.h"
@@ -52,12 +55,6 @@ constexpr absl::string_view kHistoryFileName{
     ".zetasql_execute_query_history"};
 }
 
-ABSL_FLAG(
-    bool, interactive, false,
-    absl::StrFormat("Use interactive shell for entering multiple queries with "
-                    "the query history stored in ~/%s.",
-                    kHistoryFileName));
-
 ABSL_FLAG(bool, web, false, "Run a local webserver to execute queries.");
 ABSL_FLAG(int32_t, port, 8080, "Port to run the local webserver on.");
 
@@ -72,42 +69,51 @@ absl::Status RunTool(const std::vector& args) {
   ZETASQL_ASSIGN_OR_RETURN(std::unique_ptr writer,
                    MakeWriterFromFlags(config, std::cout));
 
-  if (absl::GetFlag(FLAGS_interactive)) {
-    ABSL_LOG(QFATAL) << "Interactive mode is not implemented in this version";
-  }
-
   if (absl::GetFlag(FLAGS_web)) {
     return RunExecuteQueryWebServer(absl::GetFlag(FLAGS_port));
   }
 
   const std::string sql = absl::StrJoin(args, " ");
-
-  ExecuteQuerySingleInput prompt(sql, config);
-
-  return ExecuteQueryLoop(prompt, config, *writer,
-                          &ExecuteQueryLoopPrintErrorHandler);
+  return ExecuteQuery(sql, config, *writer);
 }
 }  // namespace
 }  // namespace zetasql
 
+// Make --help show these flags.
+static bool HelpFilter(absl::string_view module) {
+  return absl::StrContains(module, "/execute_query.cc") ||
+         absl::StrContains(module, "/execute_query_tool.cc");
+}
+
+// Make --helpshort show these flags.
+static bool HelpShortFilter(absl::string_view module) {
+  return absl::StrContains(module, "/execute_query.cc");
+}
+
 int main(int argc, char* argv[]) {
   const char kUsage[] =
-      "Usage: execute_query [--table_spec=] "
-      "{ --interactive |  } { --web --port= }\n";
+      "Usage: execute_query "
+      "{ \"\" | {--web [--port=] } }\n";
 
   std::vector args;
 
+  absl::FlagsUsageConfig flag_config;
+  flag_config.contains_help_flags = &HelpFilter;
+  flag_config.contains_helpshort_flags = &HelpShortFilter;
+  absl::SetFlagsUsageConfig(flag_config);
+
+  absl::SetProgramUsageMessage(kUsage);
   {
     std::vector remaining_args = absl::ParseCommandLine(argc, argv);
     args.assign(remaining_args.cbegin() + 1, remaining_args.cend());
   }
   absl::InitializeLog();
 
-  bool args_needed = !absl::GetFlag(FLAGS_interactive);
-  args_needed = args_needed && !absl::GetFlag(FLAGS_web);
+  bool args_needed = !absl::GetFlag(FLAGS_web);
 
   if (args_needed && args.empty()) {
-    ABSL_LOG(QFATAL) << "\n" << kUsage << "Pass --help for a full list of flags.\n";
+    std::cerr << kUsage << "Pass --help for a full list of flags.\n";
+    return 1;
   }
 
   if (const absl::Status status = zetasql::RunTool(args); status.ok()) {
diff --git a/zetasql/tools/execute_query/execute_query_internal_csv.cc b/zetasql/tools/execute_query/execute_query_internal_csv.cc
index e9608c25b..cd5d09d2f 100644
--- a/zetasql/tools/execute_query/execute_query_internal_csv.cc
+++ b/zetasql/tools/execute_query/execute_query_internal_csv.cc
@@ -14,13 +14,6 @@
 // limitations under the License.
 //
 
-// Tool for running a query against a Catalog constructed from various input
-// sources. Also serves as a demo of the PreparedQuery API.
-//
-// The tool uses the global proto db to lookup proto descriptors based on their
-// names. For an example of how to use this tool with a custom proto db, see
-// :execute_query_test. Note that not all protos are in the global proto db.
-
 #include 
 
 #include 
diff --git a/zetasql/tools/execute_query/execute_query_loop_test.cc b/zetasql/tools/execute_query/execute_query_loop_test.cc
index f6c6d4ab0..ab6d4b643 100644
--- a/zetasql/tools/execute_query/execute_query_loop_test.cc
+++ b/zetasql/tools/execute_query/execute_query_loop_test.cc
@@ -60,22 +60,6 @@ class StaticResultPrompt : public ExecuteQueryPrompt {
 };
 }  // namespace
 
-TEST(ExecuteQueryLoopTest, SelectOne) {
-  ExecuteQueryConfig config;
-  ExecuteQuerySingleInput prompt{"SELECT 1", config};
-  std::ostringstream output;
-  ExecuteQueryStreamWriter writer{output};
-
-  ZETASQL_EXPECT_OK(ExecuteQueryLoop(prompt, config, writer));
-  EXPECT_EQ(output.str(), R"(+---+
-|   |
-+---+
-| 1 |
-+---+
-
-)");
-}
-
 TEST(ExecuteQueryLoopTest, ReadError) {
   StaticResultPrompt prompt;
 
diff --git a/zetasql/tools/execute_query/execute_query_prompt.cc b/zetasql/tools/execute_query/execute_query_prompt.cc
index b7f490d00..363129447 100644
--- a/zetasql/tools/execute_query/execute_query_prompt.cc
+++ b/zetasql/tools/execute_query/execute_query_prompt.cc
@@ -262,22 +262,4 @@ void ExecuteQueryStatementPrompt::ProcessBuffer() {
   }
 }
 
-ExecuteQuerySingleInput::ExecuteQuerySingleInput(
-    absl::string_view query, const ExecuteQueryConfig& config)
-    : ExecuteQueryStatementPrompt{config,
-                                  absl::bind_front(
-                                      &ExecuteQuerySingleInput::ReadNext,
-                                      this)},
-      query_{query} {}
-
-std::optional ExecuteQuerySingleInput::ReadNext(
-    bool continuation) {
-  if (!done_) {
-    done_ = true;
-    return query_;
-  }
-
-  return std::nullopt;
-}
-
 }  // namespace zetasql
diff --git a/zetasql/tools/execute_query/execute_query_prompt.h b/zetasql/tools/execute_query/execute_query_prompt.h
index 0b742e850..90551167f 100644
--- a/zetasql/tools/execute_query/execute_query_prompt.h
+++ b/zetasql/tools/execute_query/execute_query_prompt.h
@@ -131,21 +131,6 @@ class ExecuteQueryStatementPrompt : public ExecuteQueryPrompt {
   std::deque>> queue_;
 };
 
-class ExecuteQuerySingleInput : public ExecuteQueryStatementPrompt {
- public:
-  ExecuteQuerySingleInput(absl::string_view query,
-                          const ExecuteQueryConfig& config);
-  ExecuteQuerySingleInput(const ExecuteQuerySingleInput&) = delete;
-  ExecuteQuerySingleInput& operator=(const ExecuteQuerySingleInput&) = delete;
-
- private:
-  std::optional ReadNext(bool continuation);
-
- private:
-  const std::string query_;
-  bool done_ = false;
-};
-
 }  // namespace zetasql
 
 #endif  // ZETASQL_TOOLS_EXECUTE_QUERY_EXECUTE_QUERY_PROMPT_H_
diff --git a/zetasql/tools/execute_query/execute_query_prompt_test.cc b/zetasql/tools/execute_query/execute_query_prompt_test.cc
index 0ae64d1aa..eef438007 100644
--- a/zetasql/tools/execute_query/execute_query_prompt_test.cc
+++ b/zetasql/tools/execute_query/execute_query_prompt_test.cc
@@ -469,29 +469,4 @@ TEST(ExecuteQueryStatementPrompt, LargeInput) {
   }
 }
 
-TEST(ExecuteQuerySingleInputTest, ReadEmptyString) {
-  ExecuteQueryConfig config;
-  ExecuteQuerySingleInput prompt{"", config};
-
-  EXPECT_THAT(prompt.Read(), IsOkAndHolds(std::nullopt));
-}
-
-TEST(ExecuteQuerySingleInputTest, ReadMultiLine) {
-  ExecuteQueryConfig config;
-  ExecuteQuerySingleInput prompt{"test\nline; SELECT 100;", config};
-
-  EXPECT_THAT(prompt.Read(), IsOkAndHolds("test\nline;"));
-  EXPECT_THAT(prompt.Read(), IsOkAndHolds("SELECT 100;"));
-  EXPECT_THAT(prompt.Read(), IsOkAndHolds(std::nullopt));
-}
-
-TEST(ExecuteQuerySingleInputTest, UnexpectedEnd) {
-  ExecuteQueryConfig config;
-  ExecuteQuerySingleInput prompt{"SELECT 99;\nSELECT", config};
-
-  EXPECT_THAT(prompt.Read(), IsOkAndHolds("SELECT 99;"));
-  EXPECT_THAT(prompt.Read(), IsOkAndHolds("SELECT"));
-  EXPECT_THAT(prompt.Read(), IsOkAndHolds(std::nullopt));
-}
-
 }  // namespace zetasql
diff --git a/zetasql/tools/execute_query/execute_query_tool.cc b/zetasql/tools/execute_query/execute_query_tool.cc
index 6b42c889e..3cdd69f63 100644
--- a/zetasql/tools/execute_query/execute_query_tool.cc
+++ b/zetasql/tools/execute_query/execute_query_tool.cc
@@ -44,6 +44,7 @@
 #include "zetasql/public/multi_catalog.h"
 #include "zetasql/public/parse_helpers.h"
 #include "zetasql/public/parse_resume_location.h"
+#include "zetasql/public/parse_tokens.h"
 #include "zetasql/public/simple_catalog.h"
 #include "zetasql/public/simple_catalog_util.h"
 #include "zetasql/public/sql_formatter.h"
@@ -993,6 +994,35 @@ static absl::Status ExecuteDescribe(const ResolvedNode* resolved_node,
   return absl::OkStatus();
 }
 
+static absl::StatusOr ExtractNextStatement(
+    absl::string_view script, const ExecuteQueryConfig& config,
+    const ParseResumeLocation& start_location) {
+  // Use GetParseTokens to find the end of the statement.
+  std::vector tokens;
+  ParseResumeLocation end_location = start_location;
+  ZETASQL_RETURN_IF_ERROR(
+      GetParseTokens({.stop_at_end_of_statement = true,
+                      .language_options = config.analyzer_options().language()},
+                     &end_location, &tokens));
+
+  absl::string_view statement_text = script.substr(
+      start_location.byte_position(),
+      end_location.byte_position() - start_location.byte_position());
+
+  // Strip leading and trailing newlines.  From the web input, they show up
+  // with both \n and \r.
+  while (!statement_text.empty() &&
+         (statement_text.front() == '\n' || statement_text.front() == '\r')) {
+    statement_text.remove_prefix(1);
+  }
+  while (!statement_text.empty() &&
+         (statement_text.back() == '\n' || statement_text.back() == '\r')) {
+    statement_text.remove_suffix(1);
+  }
+
+  return statement_text;
+}
+
 // Process the next statement from script, updating `parse_result_location`
 // and `at_end_of_input` on success.
 static absl::Status ExecuteOneQuery(absl::string_view script,
@@ -1000,6 +1030,11 @@ static absl::Status ExecuteOneQuery(absl::string_view script,
                                     ExecuteQueryWriter& writer,
                                     ParseResumeLocation* parse_resume_location,
                                     bool* at_end_of_input) {
+  ZETASQL_ASSIGN_OR_RETURN(
+      absl::string_view statement_text,
+      ExtractNextStatement(script, config, *parse_resume_location));
+  ZETASQL_RETURN_IF_ERROR(writer.statement_text(statement_text));
+
   std::unique_ptr parser_output;
   absl::StatusOr ast = ParseSql(
       script, config, parse_resume_location, at_end_of_input, &parser_output);
diff --git a/zetasql/tools/execute_query/execute_query_web_handler_test.cc b/zetasql/tools/execute_query/execute_query_web_handler_test.cc
index 17fc2ceb0..921bdadc4 100644
--- a/zetasql/tools/execute_query/execute_query_web_handler_test.cc
+++ b/zetasql/tools/execute_query/execute_query_web_handler_test.cc
@@ -206,4 +206,29 @@ TEST(ExecuteQueryWebHandlerTest, TestCatalogUsed) {
               Eq("Table: Value\nColumns:\n  Value   INT64\n  Value_1 INT64\n"));
 }
 
+TEST(ExecuteQueryWebHandlerTest, TestEchoStatement) {
+  std::string result;
+  const auto web_template = FakeQueryWebTemplates("{{> body}}", "",
+                                                  "{{#statements}}"
+                                                  "{{#show_statement_text}}"
+                                                  "{{statement_text}}"
+                                                  "{{/show_statement_text}}"
+                                                  "\n---\n"
+                                                  "{{/statements}}");
+
+  // With one input statement, show_statement_text isn't set.
+  EXPECT_TRUE(
+      HandleRequest(ExecuteQueryWebRequest({"execute"}, "SELECT 1;", "none"),
+                    web_template, result));
+  EXPECT_THAT(result, Eq("\n---\n"));
+
+  // With more than one input statement, show_statement_text is set.
+  // Newlines are stripped off the beginning and end of statement_text.
+  EXPECT_TRUE(HandleRequest(
+      ExecuteQueryWebRequest(
+          {"execute"}, "\n\n\r\nSELECT 1;\n\r\nSELECT\n  2;\n\r\n", "none"),
+      web_template, result));
+  EXPECT_THAT(result, Eq("SELECT 1;\n---\nSELECT\n  2;\n---\n"));
+}
+
 }  // namespace zetasql
diff --git a/zetasql/tools/execute_query/execute_query_web_writer.h b/zetasql/tools/execute_query/execute_query_web_writer.h
index b09a982c3..720b2398b 100644
--- a/zetasql/tools/execute_query/execute_query_web_writer.h
+++ b/zetasql/tools/execute_query/execute_query_web_writer.h
@@ -42,6 +42,11 @@ class ExecuteQueryWebWriter : public ExecuteQueryWriter {
   ExecuteQueryWebWriter(const ExecuteQueryWebWriter &) = delete;
   ExecuteQueryWebWriter &operator=(const ExecuteQueryWebWriter &) = delete;
 
+  absl::Status statement_text(absl::string_view statement) override {
+    current_statement_params_["statement_text"] = std::string(statement);
+    return absl::OkStatus();
+  }
+
   absl::Status log(absl::string_view message) override {
     absl::StrAppend(&log_messages_, message, "\n");
     current_statement_params_["result_log"] = std::string(log_messages_);
@@ -122,6 +127,12 @@ class ExecuteQueryWebWriter : public ExecuteQueryWriter {
     statement_params_array_.push_back(current_statement_params_);
     template_params_["statements"] = mstch::array(statement_params_array_);
 
+    // Show the input statements when there is more than one statement,
+    // so it's easy to match output to input statements.
+    if (statement_params_array_.size() > 1) {
+      template_params_["show_statement_text"] = true;
+    }
+
     got_results_ = false;
     current_statement_params_.clear();
     log_messages_.clear();
diff --git a/zetasql/tools/execute_query/execute_query_writer.h b/zetasql/tools/execute_query/execute_query_writer.h
index db8f0413f..4a048726e 100644
--- a/zetasql/tools/execute_query/execute_query_writer.h
+++ b/zetasql/tools/execute_query/execute_query_writer.h
@@ -32,6 +32,12 @@ class ExecuteQueryWriter {
  public:
   virtual ~ExecuteQueryWriter() = default;
 
+  // Write the text of the statement, if desired by the subclass.
+  // By default, this is a no-op.
+  virtual absl::Status statement_text(absl::string_view statement) {
+    return absl::OkStatus();
+  }
+
   // Write textual logging messages. A newline will be added on the end.
   // This can be called multiple times for the same statement.
   virtual absl::Status log(absl::string_view message) {
diff --git a/zetasql/tools/execute_query/run_execute_query_test.sh b/zetasql/tools/execute_query/run_execute_query_test.sh
new file mode 100755
index 000000000..ea76c5846
--- /dev/null
+++ b/zetasql/tools/execute_query/run_execute_query_test.sh
@@ -0,0 +1,44 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#!/bin/bash
+# This script is used under the execute_query_test build rule as part of a
+# sh_test to invoke execute_query on a file.
+
+if [[ $# -lt 2 ]]; then
+  echo "Usage: run_execute_query_test.sh   [ ...]"
+  exit 1
+fi
+
+TOOL="$1"
+shift
+
+FILE="$1"
+shift
+
+echo "Tool binary is $TOOL"
+echo "Input file is $FILE"
+echo "pwd is $(pwd)"
+echo
+
+if [[ ! -f "$FILE" ]]; then
+  echo "Failed: Can't read file $FILE"
+  exit 1
+fi
+
+echo "Running..."
+"$TOOL" " $(cat "$FILE")" "$@"
+exit $?
diff --git a/zetasql/tools/execute_query/testdata/execute_query_tool.test b/zetasql/tools/execute_query/testdata/execute_query_tool.test
index 2db119a14..fa77137e3 100644
--- a/zetasql/tools/execute_query/testdata/execute_query_tool.test
+++ b/zetasql/tools/execute_query/testdata/execute_query_tool.test
@@ -1180,3 +1180,116 @@ TVF registered.
 +----+
 +----+
 \
+==
+
+[mode=analyze,execute]
+insert into KeyValue values (null, null)
+--
+InsertStmt
++-table_scan=
+| +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
++-insert_column_list=KeyValue.[Key#1, Value#2]
++-query=
+| +-ProjectScan
+|   +-column_list=KeyValue.[$col#3, $col#4]
+|   +-expr_list=
+|   | +-$col#3 := Literal(type=INT64, value=NULL)
+|   | +-$col#4 := Literal(type=STRING, value=NULL)
+|   +-input_scan=
+|     +-SingleRowScan
++-query_output_column_list=KeyValue.[$col#3, $col#4]
++-column_access_list=WRITE,WRITE
+
+ERROR: INVALID_ARGUMENT: The statement InsertStmt is not supported for execution.
+==
+
+[mode=analyze,execute]
+insert into KeyValue
+select * from KeyValue where value is not null
+--
+InsertStmt
++-table_scan=
+| +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
++-insert_column_list=KeyValue.[Key#1, Value#2]
++-query=
+| +-ProjectScan
+|   +-column_list=KeyValue.[Key#3, Value#4]
+|   +-input_scan=
+|     +-FilterScan
+|       +-column_list=KeyValue.[Key#3, Value#4]
+|       +-input_scan=
+|       | +-TableScan(column_list=KeyValue.[Key#3, Value#4], table=KeyValue, column_index_list=[0, 1])
+|       +-filter_expr=
+|         +-FunctionCall(ZetaSQL:$not(BOOL) -> BOOL)
+|           +-FunctionCall(ZetaSQL:$is_null(STRING) -> BOOL)
+|             +-ColumnRef(type=STRING, column=KeyValue.Value#4)
++-query_output_column_list=KeyValue.[Key#3, Value#4]
++-column_access_list=WRITE,WRITE
+
+ERROR: INVALID_ARGUMENT: The statement InsertStmt is not supported for execution.
+==
+
+[mode=analyze,execute]
+delete from KeyValue
+where value is null
+--
+DeleteStmt
++-table_scan=
+| +-TableScan(column_list=KeyValue.[Key#1, Value#2], table=KeyValue, column_index_list=[0, 1])
++-column_access_list=NONE,READ
++-where_expr=
+  +-FunctionCall(ZetaSQL:$is_null(STRING) -> BOOL)
+    +-ColumnRef(type=STRING, column=KeyValue.Value#2)
+
+ERROR: INVALID_ARGUMENT: The statement DeleteStmt is not supported for execution.
+==
+
+# An empty statement is not allowed.
+--
+ERROR: INVALID_ARGUMENT: Syntax error: Unexpected end of statement [at 1:1]
+
+^
+==
+
+# A statement that is just a comment is not allowed.
+/* comment */
+--
+ERROR: INVALID_ARGUMENT: Syntax error: Unexpected end of statement [at 1:14]
+/* comment */
+             ^
+==
+
+# A statement that is just a semicolon is not allowed.
+;
+--
+ERROR: INVALID_ARGUMENT: Syntax error: Unexpected ";" [at 1:1]
+;
+^
+==
+
+# Empty statements from double-semicolons are not allowed.
+select 1;;
+select 2;
+--
++---+
+|   |
++---+
+| 1 |
++---+
+
+ERROR: INVALID_ARGUMENT: Syntax error: Unexpected ";" [at 1:10]
+select 1;;
+         ^
+==
+
+# Trailing comments are allowed.
+/* comment */
+select 1;
+/* comment */
+--
++---+
+|   |
++---+
+| 1 |
++---+
+\
diff --git a/zetasql/tools/execute_query/testdata/run_execute_query_test.sql b/zetasql/tools/execute_query/testdata/run_execute_query_test.sql
new file mode 100644
index 000000000..2441137b0
--- /dev/null
+++ b/zetasql/tools/execute_query/testdata/run_execute_query_test.sql
@@ -0,0 +1,23 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+-- This is a valid script for execute_query_test.
+-- Leading comments with -- work and are not confused as flags.
+SELECT 123;
+
+SELECT sqrt(n_nationkey) FROM nation;
+# Trailing comments are okay.
+/* More trailing comments. */
diff --git a/zetasql/tools/execute_query/testdata/run_execute_query_test_bad.sql b/zetasql/tools/execute_query/testdata/run_execute_query_test_bad.sql
new file mode 100644
index 000000000..2b5834f39
--- /dev/null
+++ b/zetasql/tools/execute_query/testdata/run_execute_query_test_bad.sql
@@ -0,0 +1,18 @@
+#
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This script for execute_query_test is valid to parse but invalid to analyze.
+SELECT x;
diff --git a/zetasql/tools/execute_query/web/page_body.html b/zetasql/tools/execute_query/web/page_body.html
index 5909fd8c7..a01c4302b 100644
--- a/zetasql/tools/execute_query/web/page_body.html
+++ b/zetasql/tools/execute_query/web/page_body.html
@@ -2,7 +2,6 @@
 
     
-

Query

@@ -65,13 +64,18 @@

Query

{{#statements}} {{#result_or_error}}
+ {{#show_statement_text}} + {{#statement_text}} +

Statement

+
{{statement_text}}
+ {{/statement_text}} + {{/show_statement_text}} {{#error}} -

Error

+

Error

{{error}}
+

{{/error}} {{#result}} -

Result

- {{#result_executed}}

Execute

{{#result_executed_table}} diff --git a/zetasql/tools/execute_query/web/style.css b/zetasql/tools/execute_query/web/style.css index 5afc5e871..7642282ee 100644 --- a/zetasql/tools/execute_query/web/style.css +++ b/zetasql/tools/execute_query/web/style.css @@ -69,8 +69,8 @@ main { textarea { width: 100%; - min-height: 30em; - height: 60vh; + min-height: 20em; + height: 40vh; margin: 0; padding: 0.7em; outline: none; @@ -134,7 +134,7 @@ form { } .result > h3 { - margin-top: 2.5em; + margin-top: 1.5em; margin-bottom: 0; background-color: #1597ec; color: #fff; @@ -147,6 +147,11 @@ form { margin-top: auto; } +h3.statement { + background-color: #3cb151; + color: #fff; +} + pre.output { background-color: #fafafa; border: 1px dashed #aaa; @@ -197,3 +202,10 @@ pre.output tbody tr:nth-child(even) { pre.output tbody tr:hover { background: #eee; } + +hr { + height: 1px; + background-color: #111; + margin-top: 1.5em; + margin-bottom: 1.5em; +} diff --git a/zetasql/tools/formatter/internal/chunk.cc b/zetasql/tools/formatter/internal/chunk.cc index 1b8f42523..36400d532 100644 --- a/zetasql/tools/formatter/internal/chunk.cc +++ b/zetasql/tools/formatter/internal/chunk.cc @@ -343,11 +343,11 @@ bool Chunk::SpaceBetweenTokens(const Token& token_before, // Always have space after ':' and after '{' // e.g.: "field1: { a: 1 ..." if (token_before.Is(Token::Type::BRACED_CONSTR_COLON) || - (token_before.Is(Token::Type::BRACED_CONSTR_BRACKET) && + (token_before.Is(Token::Type::BRACED_CONSTR_OPEN_BRACKET) && token_before.GetKeyword() == "{")) { return true; // Always have space before '}'. - } else if (token_after.Is(Token::Type::BRACED_CONSTR_BRACKET) && + } else if (token_after.Is(Token::Type::BRACED_CONSTR_CLOSE_BRACKET) && token_after.GetKeyword() == "}") { return true; // Keep space before '{' only if when it goes after another map constructor @@ -355,18 +355,16 @@ bool Chunk::SpaceBetweenTokens(const Token& token_before, // repeated_field: [ { ... // but: // ARRAY[{ ... - } else if (token_after.Is(Token::Type::BRACED_CONSTR_BRACKET) && - token_after.GetKeyword() == "{" && - token_before.Is(Token::Type::BRACED_CONSTR_BRACKET)) { + } else if (token_after.Is(Token::Type::BRACED_CONSTR_OPEN_BRACKET) && + token_before.Is(Token::Type::BRACED_CONSTR_OPEN_BRACKET)) { return true; // After '}' leave space only when next token is another map constructor // bracket, otherwise use default rules. e.g.: // repeated_field: [ { ... } ] // but: // ARRAY[{ ... }] - } else if (token_before.Is(Token::Type::BRACED_CONSTR_BRACKET) && - token_before.GetKeyword() == "}" && - token_after.Is(Token::Type::BRACED_CONSTR_BRACKET)) { + } else if (token_before.Is(Token::Type::BRACED_CONSTR_CLOSE_BRACKET) && + token_after.Is(Token::Type::BRACED_CONSTR_CLOSE_BRACKET)) { return true; } @@ -662,6 +660,7 @@ bool Chunk::OpensParenBlock() const { return !Empty() && LastKeyword() == "("; } bool Chunk::OpensParenOrBracketBlock() const { return LastToken().Is(Token::Type::OPEN_BRACKET) || + LastToken().Is(Token::Type::BRACED_CONSTR_OPEN_BRACKET) || IsOpenParenOrBracket(LastKeyword()); } @@ -671,6 +670,7 @@ bool Chunk::ClosesParenBlock() const { bool Chunk::ClosesParenOrBracketBlock() const { return FirstToken().Is(Token::Type::CLOSE_BRACKET) || + FirstToken().Is(Token::Type::BRACED_CONSTR_CLOSE_BRACKET) || IsCloseParenOrBracket(FirstKeyword()); } @@ -1074,7 +1074,7 @@ bool IsPartOfSameChunk(const Chunk& chunk, const std::vector& tokens, // In proto constructors colon acts similar to the assignment operator: // field_name: value if (previous_token->Is(Token::Type::BRACED_CONSTR_COLON)) { - return current_token->Is(Token::Type::BRACED_CONSTR_BRACKET); + return current_token->Is(Token::Type::BRACED_CONSTR_OPEN_BRACKET); } else if (current_token->Is(Token::Type::BRACED_CONSTR_COLON)) { return true; } @@ -2312,20 +2312,21 @@ void MarkAllComplexScriptNames(const TokensView& tokens_view) { void MarkTokensInBracedConstructors(const TokensView& tokens_view) { const std::vector& tokens = tokens_view.WithoutComments(); for (int t = 0; t < tokens.size(); ++t) { - if (!MaybeAnOpenBraceForMapConstructor(tokens, t)) { + if (!tokens[t]->Is(Token::Type::BRACED_CONSTR_OPEN_BRACKET) && + !MaybeAnOpenBraceForMapConstructor(tokens, t)) { continue; } // Found braced constructor start. - tokens[t]->SetType(Token::Type::BRACED_CONSTR_BRACKET); + tokens[t]->SetType(Token::Type::BRACED_CONSTR_OPEN_BRACKET); const int end = FindMatchingClosingBracket(tokens, t); if (end < tokens.size()) { - tokens[end]->SetType(Token::Type::BRACED_CONSTR_BRACKET); + tokens[end]->SetType(Token::Type::BRACED_CONSTR_CLOSE_BRACKET); } while (++t < end) { bool found_field_name = false; if (tokens[t]->GetKeyword() == "{") { if (MaybeAnOpenBraceForMapConstructor(tokens, t)) { - tokens[t]->SetType(Token::Type::BRACED_CONSTR_BRACKET); + tokens[t]->SetType(Token::Type::BRACED_CONSTR_OPEN_BRACKET); if (tokens[t - 1]->GetKeyword() != ":" && !IsOpenParenOrBracket(tokens[t - 1]->GetKeyword())) { found_field_name = true; @@ -2340,7 +2341,7 @@ void MarkTokensInBracedConstructors(const TokensView& tokens_view) { found_field_name = true; } else if (tokens[t]->GetKeyword() == "}" && tokens[t]->Is(Token::Type::UNKNOWN)) { - tokens[t]->SetType(Token::Type::BRACED_CONSTR_BRACKET); + tokens[t]->SetType(Token::Type::BRACED_CONSTR_CLOSE_BRACKET); // [] and () in map constructors behave similarly to {} if they // surround the entire value and are not part of bigger expression. } else if ((tokens[t]->GetKeyword() == "[" || @@ -2357,9 +2358,9 @@ void MarkTokensInBracedConstructors(const TokensView& tokens_view) { tokens[next_token]->GetKeyword() == "," || tokens[next_token]->GetKeyword() == "}" || tokens[next_token]->MayBeStartOfIdentifier())) { - tokens[t]->SetType(Token::Type::BRACED_CONSTR_BRACKET); + tokens[t]->SetType(Token::Type::BRACED_CONSTR_OPEN_BRACKET); tokens[closing_bracket]->SetType( - Token::Type::BRACED_CONSTR_BRACKET); + Token::Type::BRACED_CONSTR_CLOSE_BRACKET); } } } diff --git a/zetasql/tools/formatter/internal/layout.cc b/zetasql/tools/formatter/internal/layout.cc index 27fe4392d..5eaba4aaf 100644 --- a/zetasql/tools/formatter/internal/layout.cc +++ b/zetasql/tools/formatter/internal/layout.cc @@ -1022,7 +1022,7 @@ void StmtLayout::PruneLineBreaks() { if (prev_line->LengthInChunks() == 1 && !ChunkAt(prev_line->start) .LastToken() - .Is(Token::Type::BRACED_CONSTR_BRACKET)) { + .Is(Token::Type::BRACED_CONSTR_OPEN_BRACKET)) { const Chunk& prev_chunk = ChunkAt(prev_line->start); const int curr_line_level = first_chunk.ChunkBlock()->Level(); const int prev_line_level = prev_chunk.ChunkBlock()->Level(); @@ -1580,7 +1580,7 @@ absl::btree_set StmtLayout::FindSiblingBreakpoints(const Line& line, // Curly braced constructor `NEW Type { foo: 1 }`. ChunkAt(closing_bracket) .FirstToken() - .Is(Token::Type::BRACED_CONSTR_BRACKET)) { + .Is(Token::Type::BRACED_CONSTR_CLOSE_BRACKET)) { // Add a line break before closing parenthesis. result.insert(closing_bracket); } else { diff --git a/zetasql/tools/formatter/internal/token.cc b/zetasql/tools/formatter/internal/token.cc index c31f52106..4d89b04d4 100644 --- a/zetasql/tools/formatter/internal/token.cc +++ b/zetasql/tools/formatter/internal/token.cc @@ -767,10 +767,14 @@ bool IsEmbeddedSqlStart(const ParseToken& parse_token, const Token& prev_token) { return parse_token.IsValue() && parse_token.GetValue().type_kind() == TYPE_STRING && - prev_token.IsSlashStarComment() && - StrContainsIgnoreCase(prev_token.GetImage(), "/*sql*/") && + (prev_token.IsEmbeddedSqlAnnotation() || + prev_token.IsEmbeddedProtoAnnotation()) && !absl::StripAsciiWhitespace(parse_token.GetValue().string_value()) - .empty(); + .empty() && + // Allow only multiline string literals (otherwise, formatter might + // break a string by adding a line break to it). + (absl::EndsWith(parse_token.GetImage(), "'''") || + absl::EndsWith(parse_token.GetImage(), "\"\"\"")); } // Updates grouping state with the end of no format region if the given @@ -1034,23 +1038,18 @@ void UpdateGroupingStateForDoubleSlashComment( void UpdateGroupingStateForEmbeddedSql(const ParseToken& embedded_sql, TokenGroupingState* grouping_state) { grouping_state->type = GroupingType::kEmbeddedSql; - const int quotes_length = - static_cast(embedded_sql.GetImage().size() - - embedded_sql.GetValue().string_value().size()); - // Find the start and end of the embedded SQL string within quotes. This - // handles single quotes, triple quotes and string literal prefixes, e.g.: - // r'raw string'. - int content_start = TokenStartOffset(embedded_sql); - int content_end = TokenEndOffset(embedded_sql); - if (quotes_length >= 6) { // Triple quotes ''', """ or r'''. - content_start += quotes_length - 3; - content_end -= 3; - } else { // Single quotes '' or "", or r''. - content_start += quotes_length - 1; - content_end -= 1; - } - grouping_state->start_position = content_start; - grouping_state->end_position = content_end; + // Find offsets of the string content. + absl::string_view sql = embedded_sql.GetImage(); + int quotes_length = 1; + if (absl::EndsWith(sql, "'''") || absl::EndsWith(sql, "\"\"\"")) { + quotes_length = 3; + } + grouping_state->start_position = + TokenStartOffset(embedded_sql) + quotes_length; + grouping_state->end_position = TokenEndOffset(embedded_sql) - quotes_length; + if (sql[0] == 'r' || sql[0] == 'R') { // Consume raw string prefix if any. + grouping_state->start_position++; + } } // Creates a zetasql::ParseToken. @@ -1496,9 +1495,15 @@ absl::Status ParseEmbeddedSql(const ParseToken& string_literal, const FormatterOptions& options, std::vector& tokens) { // Create a token that represents opening quotes of the string literal. + Token::Type open_bracket_type = Token::Type::OPEN_BRACKET; + if (tokens.back().IsEmbeddedProtoAnnotation()) { + // We parse textproto as SQL as well: ZetaSQL has braced constructors + // syntax that looks very similar to textproto. + open_bracket_type = Token::Type::BRACED_CONSTR_OPEN_BRACKET; + } tokens.push_back(CreateTokenForQuotes(sql, TokenStartOffset(string_literal), grouping_state.start_position, - Token::Type::OPEN_BRACKET)); + open_bracket_type)); // Parse substring inside the quotes. We preserve all bytes before the string // start to get the correct byte offsets. @@ -1521,9 +1526,13 @@ absl::Status ParseEmbeddedSql(const ParseToken& string_literal, } } // Create a token that represents closing quotes of the string literal. + const Token::Type close_bracket_type = + open_bracket_type == Token::Type::OPEN_BRACKET + ? Token::Type::CLOSE_BRACKET + : Token::Type::BRACED_CONSTR_CLOSE_BRACKET; tokens.push_back(CreateTokenForQuotes(sql, grouping_state.end_position, TokenEndOffset(string_literal), - Token::Type::CLOSE_BRACKET)); + close_bracket_type)); return absl::OkStatus(); } @@ -1762,6 +1771,16 @@ bool Token::IsCloseAngleBracket() const { return Is(Type::CLOSE_BRACKET) && GetKeyword() == ">"; } +bool Token::IsEmbeddedSqlAnnotation() const { + return IsSlashStarComment() && StrContainsIgnoreCase(GetImage(), "/*sql*/"); +} + +bool Token::IsEmbeddedProtoAnnotation() const { + return IsSlashStarComment() && + (StrContainsIgnoreCase(GetImage(), "/*proto*/") || + StrContainsIgnoreCase(GetImage(), "/*txtpb*/")); +} + bool Token::MayBeIdentifier() const { return !UsedAsKeyword() && (IsIdentifier() || Is(Token::Type::KEYWORD_AS_IDENTIFIER_FRAGMENT) || @@ -1955,7 +1974,7 @@ absl::string_view CorrespondingOpenBracket(absl::string_view keyword) { absl::string_view CorrespondingCloseBracket(absl::string_view keyword) { if (keyword.empty()) { - return ""; + return ""; } switch (keyword[0]) { case '(': @@ -1964,8 +1983,16 @@ absl::string_view CorrespondingCloseBracket(absl::string_view keyword) { return "]"; case '{': return "}"; + case '<': + return ">"; + // Quotes behave as parentheses when the contents of a string literal is + // parsed as SQL. + case '\'': + return keyword.length() == 1 ? "'" : "'''"; + case '"': + return keyword.length() == 1 ? "\"" : "\"\"\""; default: - return ""; + return ""; } } diff --git a/zetasql/tools/formatter/internal/token.h b/zetasql/tools/formatter/internal/token.h index 9f708ff6c..4ac3d982a 100644 --- a/zetasql/tools/formatter/internal/token.h +++ b/zetasql/tools/formatter/internal/token.h @@ -120,8 +120,10 @@ class Token : public ParseToken { // Marks builtin-functions, some of which may be multipart, e.g. // D3A_COUNT.INIT() BUILTIN_FUNCTION, - // Marks open and closing brackets inside braced (map) constructors. - BRACED_CONSTR_BRACKET, + // Marks open brackets inside braced (map) constructors. + BRACED_CONSTR_OPEN_BRACKET, + // Marks close brackets inside braced (map) constructors. + BRACED_CONSTR_CLOSE_BRACKET, // Marks colons in braced constructors. BRACED_CONSTR_COLON, // Marks the beginning of a field name in braced constructor. It can be '(' @@ -184,6 +186,14 @@ class Token : public ParseToken { // (but not a comparison operator). bool IsCloseAngleBracket() const; + // Returns true if the current token is a comment that annotates the following + // string literal as embedded SQL. Example: /*sql*/'''select 1''' + bool IsEmbeddedSqlAnnotation() const; + + // Returns true if the current token is a comment that annotates the following + // string literal as embedded proto. Example: /*proto*/'''foo: 1''' + bool IsEmbeddedProtoAnnotation() const; + // Checks if the current token may be an identifier (but may be also a non // reserved keyword). bool MayBeIdentifier() const;