You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by dl...@apache.org on 2021/03/03 23:28:31 UTC

[asterixdb] branch master updated: [NO-ISSUE][IDX] Adding support for array-indexes.

This is an automated email from the ASF dual-hosted git repository.

dlych pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 67fd1f3  [NO-ISSUE][IDX] Adding support for array-indexes.
67fd1f3 is described below

commit 67fd1f3825ec2ab5581041651299f17582e858fc
Author: Glenn <gg...@uci.edu>
AuthorDate: Thu Feb 25 16:04:49 2021 -0800

    [NO-ISSUE][IDX] Adding support for array-indexes.
    
    - user model changes: yes
    - storage format changes: no
    - interface changes: no
    
    details:
    - Users can now create indexes on array-valued fields, of the general
    form "CREATE INDEX ... ON Dataset (UNNEST arrayField SELECT atomicField)".
    - Metadata tuples for indexes has been updated to support a list
    of "complex search keys", as well as multiple types for one key.
    In the presence of a complex search key, all keys (even simple SKs)
    will be placed inside this new list.
    - Queries involving membership, UNNESTs, quantification
    (existential and partially supported universal) will utilize an
    applicable array index. Users must set the compiler option "arrayindex"
    to true to enable this.
    - SIDX maintenance operations now allow the use of nested plans, which
    the aforementioned maintenance op will operate on from the end of the
    nested plan(s).
    - Indexes are now classified by different types in metadata, with a new
    "array" index type.
    
    Change-Id: Id0e9eee940cc94819e169a74ed180387b7a3093b
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/7684
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Dmitry Lychagin <dm...@couchbase.com>
---
 .../asterix/algebra/operators/CommitOperator.java  |   4 +
 .../operators/physical/BTreeSearchPOperator.java   |   5 +-
 .../operators/physical/InvertedIndexPOperator.java |   2 +-
 .../IntroduceSecondaryIndexInsertDeleteRule.java   | 533 ++++++++++++---
 .../am/AbstractIntroduceAccessMethodRule.java      | 384 ++++-------
 .../optimizer/rules/am/AccessMethodUtils.java      | 556 ++++++++++++++-
 .../optimizer/rules/am/ArrayBTreeAccessMethod.java | 133 ++++
 .../optimizer/rules/am/BTreeAccessMethod.java      |  58 +-
 .../asterix/optimizer/rules/am/IAccessMethod.java  |   9 +
 .../rules/am/IntroduceJoinAccessMethodRule.java    |   1 +
 .../rules/am/IntroduceLSMComponentFilterRule.java  |  65 +-
 .../IntroducePrimaryIndexForAggregationRule.java   |   2 +-
 .../rules/am/IntroduceSelectAccessMethodRule.java  |  22 +
 .../rules/am/InvertedIndexAccessMethod.java        |  49 +-
 .../rules/am/OptimizableOperatorSubTree.java       |  11 +
 .../optimizer/rules/am/RTreeAccessMethod.java      |   7 +-
 .../rules/util/SelectInSubplanBranchCreator.java   | 424 ++++++++++++
 .../asterix/translator/util/ValidateUtil.java      | 221 +++---
 .../asterix-app/data/yelp-checkin/use-case-1.json  |  30 +
 .../asterix-app/data/yelp-checkin/use-case-2.json  |  31 +
 .../asterix-app/data/yelp-checkin/use-case-3.json  |  30 +
 .../asterix-app/data/yelp-checkin/use-case-4.json  |  30 +
 .../yelp-checkin/with-3-level-record-path.json     |  31 +
 .../data/yelp-checkin/with-composite-pk.json       |  30 +
 .../apache/asterix/api/common/APIFramework.java    |   2 +-
 .../asterix/app/translator/QueryTranslator.java    | 373 +++++++---
 .../asterix/app/bootstrap/TestNodeController.java  |  51 +-
 .../apache/asterix/test/dataflow/TestDataset.java  |  10 +-
 .../TestLsmBTreeResourceFactoryProvider.java       |  22 +-
 .../src/test/resources/metadata/testsuite.xml      |  12 +-
 .../join-unnest-queries/use-case-1/query1.sqlpp    |  44 ++
 .../join-unnest-queries/use-case-1/query2.sqlpp    |  44 ++
 .../join-unnest-queries/use-case-1/query3.sqlpp    |  46 ++
 .../join-unnest-queries/use-case-2/query1.sqlpp    |  47 ++
 .../join-unnest-queries/use-case-2/query2.sqlpp    |  47 ++
 .../join-unnest-queries/use-case-2/query3.sqlpp    |  49 ++
 .../join-unnest-queries/use-case-3/query1.sqlpp    |  47 ++
 .../join-unnest-queries/use-case-3/query2.sqlpp    |  47 ++
 .../join-unnest-queries/use-case-3/query3.sqlpp    |  47 ++
 .../join-unnest-queries/use-case-3/query4.sqlpp    |  49 ++
 .../join-unnest-queries/use-case-4/query1.sqlpp    |  48 ++
 .../join-unnest-queries/use-case-4/query2.sqlpp    |  49 ++
 .../join-unnest-queries/use-case-4/query3.sqlpp    |  48 ++
 .../join-unnest-queries/use-case-4/query4.sqlpp    |  50 ++
 .../use-case-1/query1.sqlpp                        |  36 +
 .../use-case-1/query2.sqlpp                        |  37 +
 .../use-case-1/query3.sqlpp                        |  38 ++
 .../use-case-2/query1.sqlpp                        |  39 ++
 .../use-case-2/query2.sqlpp                        |  40 ++
 .../use-case-2/query3.sqlpp                        |  41 ++
 .../use-case-3/query1.sqlpp                        |  40 ++
 .../use-case-3/query2.sqlpp                        |  40 ++
 .../use-case-3/query3.sqlpp                        |  41 ++
 .../use-case-4/query1.sqlpp                        |  40 ++
 .../use-case-4/query2.sqlpp                        |  43 ++
 .../use-case-4/query3.sqlpp                        |  45 ++
 .../closed/use-case-1/query1.sqlpp                 |  36 +
 .../closed/use-case-1/query2.sqlpp                 |  36 +
 .../closed/use-case-2/query1.sqlpp                 |  39 ++
 .../closed/use-case-2/query2.sqlpp                 |  39 ++
 .../closed/use-case-3/query1.sqlpp                 |  39 ++
 .../closed/use-case-3/query2.sqlpp                 |  39 ++
 .../closed/use-case-3/query3.sqlpp                 |  40 ++
 .../closed/use-case-4/query1.sqlpp                 |  39 ++
 .../closed/use-case-4/query2.sqlpp                 |  39 ++
 .../closed/with-3-level-record-path/query1.sqlpp   |  43 ++
 .../closed/with-3-level-record-path/query2.sqlpp   |  43 ++
 .../closed/with-composite-pk/query1.sqlpp          |  36 +
 .../closed/with-composite-pk/query2.sqlpp          |  36 +
 .../closed/with-composite-sk/query1.sqlpp          |  41 ++
 .../closed/with-composite-sk/query2.sqlpp          |  39 ++
 .../closed/with-filter-fields/query1.sqlpp         |  37 +
 .../closed/with-filter-fields/query2.sqlpp         |  36 +
 .../open/use-case-1/query1.sqlpp                   |  34 +
 .../open/use-case-1/query2.sqlpp                   |  34 +
 .../open/use-case-2/query1.sqlpp                   |  34 +
 .../open/use-case-2/query2.sqlpp                   |  34 +
 .../open/use-case-3/query1.sqlpp                   |  34 +
 .../open/use-case-3/query2.sqlpp                   |  34 +
 .../open/use-case-3/query3.sqlpp                   |  35 +
 .../open/use-case-4/query1.sqlpp                   |  34 +
 .../open/use-case-4/query2.sqlpp                   |  34 +
 .../open/with-3-level-record-path/query1.sqlpp     |  34 +
 .../open/with-3-level-record-path/query2.sqlpp     |  34 +
 .../open/with-composite-sk/query1.sqlpp            |  36 +
 .../join-unnest-queries/use-case-1/query1.plan     |  29 +
 .../join-unnest-queries/use-case-1/query2.plan     |  29 +
 .../join-unnest-queries/use-case-1/query3.plan     |  32 +
 .../join-unnest-queries/use-case-2/query1.plan     |  29 +
 .../join-unnest-queries/use-case-2/query2.plan     |  29 +
 .../join-unnest-queries/use-case-2/query3.plan     |  32 +
 .../join-unnest-queries/use-case-3/query1.plan     |  29 +
 .../join-unnest-queries/use-case-3/query2.plan     |  29 +
 .../join-unnest-queries/use-case-3/query3.plan     |  29 +
 .../join-unnest-queries/use-case-3/query4.plan     |  32 +
 .../join-unnest-queries/use-case-4/query1.plan     |  33 +
 .../join-unnest-queries/use-case-4/query2.plan     |  34 +
 .../join-unnest-queries/use-case-4/query3.plan     |  33 +
 .../join-unnest-queries/use-case-4/query4.plan     |  36 +
 .../use-case-1/query1.plan                         |  30 +
 .../use-case-1/query2.plan                         |  32 +
 .../use-case-1/query3.plan                         |  33 +
 .../use-case-2/query1.plan                         |  30 +
 .../use-case-2/query2.plan                         |  32 +
 .../use-case-2/query3.plan                         |  33 +
 .../use-case-3/query1.plan                         |  31 +
 .../use-case-3/query2.plan                         |  31 +
 .../use-case-3/query3.plan                         |  32 +
 .../use-case-4/query1.plan                         |  38 ++
 .../use-case-4/query2.plan                         |  38 ++
 .../use-case-4/query3.plan                         |  39 ++
 .../closed/use-case-1/query1.plan                  |  23 +
 .../closed/use-case-1/query2.plan                  |  26 +
 .../closed/use-case-2/query1.plan                  |  23 +
 .../closed/use-case-2/query2.plan                  |  26 +
 .../closed/use-case-3/query1.plan                  |  23 +
 .../closed/use-case-3/query2.plan                  |  28 +
 .../closed/use-case-3/query3.plan                  |  23 +
 .../closed/use-case-4/query1.plan                  |  27 +
 .../closed/use-case-4/query2.plan                  |  30 +
 .../closed/with-3-level-record-path/query1.plan    |  23 +
 .../closed/with-3-level-record-path/query2.plan    |  26 +
 .../closed/with-composite-pk/query1.plan           |  24 +
 .../closed/with-composite-pk/query2.plan           |  26 +
 .../closed/with-composite-sk/query1.plan           |  25 +
 .../closed/with-composite-sk/query2.plan           |  28 +
 .../closed/with-filter-fields/query1.plan          |  25 +
 .../closed/with-filter-fields/query2.plan          |  26 +
 .../open/use-case-1/query1.plan                    |  23 +
 .../open/use-case-1/query2.plan                    |  26 +
 .../open/use-case-2/query1.plan                    |  23 +
 .../open/use-case-2/query2.plan                    |  26 +
 .../open/use-case-3/query1.plan                    |  23 +
 .../open/use-case-3/query2.plan                    |  28 +
 .../open/use-case-3/query3.plan                    |  23 +
 .../open/use-case-4/query1.plan                    |  27 +
 .../open/use-case-4/query2.plan                    |  30 +
 .../open/with-3-level-record-path/query1.plan      |  23 +
 .../open/with-3-level-record-path/query2.plan      |  26 +
 .../open/with-composite-sk/query1.plan             |  25 +
 .../use-case-1/use-case-1.1.ddl.sqlpp              |  38 ++
 .../use-case-1/use-case-1.2.update.sqlpp           |  24 +
 .../use-case-1/use-case-1.3.query.sqlpp            |  25 +
 .../use-case-2/use-case-2.1.ddl.sqlpp              |  40 ++
 .../use-case-2/use-case-2.2.update.sqlpp           |  24 +
 .../use-case-2/use-case-2.3.query.sqlpp            |  25 +
 .../use-case-3/use-case-3.1.ddl.sqlpp              |  42 ++
 .../use-case-3/use-case-3.2.update.sqlpp           |  24 +
 .../use-case-3/use-case-3.3.query.sqlpp            |  25 +
 .../use-case-4/use-case-4.1.ddl.sqlpp              |  40 ++
 .../use-case-4/use-case-4.2.update.sqlpp           |  24 +
 .../use-case-4/use-case-4.3.query.sqlpp            |  25 +
 .../with-3-level-record-path.1.ddl.sqlpp           |  43 ++
 .../with-3-level-record-path.2.update.sqlpp        |  24 +
 .../with-3-level-record-path.3.query.sqlpp         |  25 +
 .../with-composite-pk.1.ddl.sqlpp                  |  35 +
 .../with-composite-pk.2.update.sqlpp               |  24 +
 .../with-composite-pk.3.query.sqlpp                |  25 +
 .../with-filter-fields.1.ddl.sqlpp                 |  35 +
 .../with-filter-fields.2.update.sqlpp              |  24 +
 .../with-filter-fields.3.query.sqlpp               |  25 +
 .../with-open-index/with-open-index.1.ddl.sqlpp    |  33 +
 .../with-open-index/with-open-index.2.update.sqlpp |  24 +
 .../with-open-index/with-open-index.3.query.sqlpp  |  25 +
 .../closed/use-case-1/use-case-1.1.ddl.sqlpp       |  34 +
 .../closed/use-case-1/use-case-1.2.update.sqlpp    | 276 ++++++++
 .../closed/use-case-1/use-case-1.3.ddl.sqlpp       |  24 +
 .../closed/use-case-1/use-case-1.4.query.sqlpp     |  25 +
 .../closed/use-case-2/use-case-2.1.ddl.sqlpp       |  37 +
 .../closed/use-case-2/use-case-2.2.update.sqlpp    | 497 ++++++++++++++
 .../closed/use-case-2/use-case-2.3.ddl.sqlpp       |  27 +
 .../closed/use-case-2/use-case-2.4.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.1.ddl.sqlpp       |  37 +
 .../closed/use-case-3/use-case-3.2.update.sqlpp    | 276 ++++++++
 .../closed/use-case-3/use-case-3.3.ddl.sqlpp       |  22 +
 .../closed/use-case-3/use-case-3.4.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.5.ddl.sqlpp       |  25 +
 .../closed/use-case-3/use-case-3.6.query.sqlpp     |  25 +
 .../closed/use-case-4/use-case-4.1.ddl.sqlpp       |  37 +
 .../closed/use-case-4/use-case-4.2.update.sqlpp    | 425 ++++++++++++
 .../closed/use-case-4/use-case-4.3.ddl.sqlpp       |  24 +
 .../closed/use-case-4/use-case-4.4.query.sqlpp     |  25 +
 .../with-3-level-record-path.1.ddl.sqlpp           |  42 ++
 .../with-3-level-record-path.2.update.sqlpp        | 617 +++++++++++++++++
 .../with-3-level-record-path.3.ddl.sqlpp           |  22 +
 .../with-3-level-record-path.4.query.sqlpp         |  25 +
 .../with-composite-pk.1.ddl.sqlpp                  |  34 +
 .../with-composite-pk.2.update.sqlpp               | 306 +++++++++
 .../with-composite-pk.3.ddl.sqlpp                  |  22 +
 .../with-composite-pk.4.query.sqlpp                |  25 +
 .../with-filter-fields.1.ddl.sqlpp                 |  34 +
 .../with-filter-fields.2.update.sqlpp              | 276 ++++++++
 .../with-filter-fields.3.ddl.sqlpp                 |  22 +
 .../with-filter-fields.4.query.sqlpp               |  25 +
 .../open/use-case-1/use-case-1.1.ddl.sqlpp         |  32 +
 .../open/use-case-1/use-case-1.2.update.sqlpp      | 276 ++++++++
 .../open/use-case-1/use-case-1.3.ddl.sqlpp         |  22 +
 .../open/use-case-1/use-case-1.4.query.sqlpp       |  25 +
 .../open/use-case-2/use-case-2.1.ddl.sqlpp         |  32 +
 .../open/use-case-2/use-case-2.2.update.sqlpp      | 497 ++++++++++++++
 .../open/use-case-2/use-case-2.3.ddl.sqlpp         |  22 +
 .../open/use-case-2/use-case-2.4.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.1.ddl.sqlpp         |  32 +
 .../open/use-case-3/use-case-3.2.update.sqlpp      | 276 ++++++++
 .../open/use-case-3/use-case-3.3.ddl.sqlpp         |  22 +
 .../open/use-case-3/use-case-3.4.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.5.ddl.sqlpp         |  23 +
 .../open/use-case-3/use-case-3.6.query.sqlpp       |  25 +
 .../open/use-case-4/use-case-4.1.ddl.sqlpp         |  32 +
 .../open/use-case-4/use-case-4.2.update.sqlpp      | 425 ++++++++++++
 .../open/use-case-4/use-case-4.3.ddl.sqlpp         |  22 +
 .../open/use-case-4/use-case-4.4.query.sqlpp       |  25 +
 .../index-mixed-composite.1.ddl.sqlpp              |  39 ++
 .../index-on-closed-array.1.ddl.sqlpp              |  39 ++
 .../index-two-array-fields.1.ddl.sqlpp             |  37 +
 .../index-two-array-fields.2.ddl.sqlpp             |  34 +
 .../index-with-enforced-type.1.ddl.sqlpp           |  39 ++
 .../invalid-array-path.1.ddl.sqlpp                 |  39 ++
 .../invalid-array-path.2.ddl.sqlpp                 |  39 ++
 .../closed/use-case-1/use-case-1.1.ddl.sqlpp       |  37 +
 .../closed/use-case-1/use-case-1.2.update.sqlpp    | 272 ++++++++
 .../closed/use-case-1/use-case-1.3.query.sqlpp     |  25 +
 .../closed/use-case-1/use-case-1.4.update.sqlpp    |  23 +
 .../closed/use-case-1/use-case-1.5.query.sqlpp     |  25 +
 .../closed/use-case-1/use-case-1.6.update.sqlpp    | 276 ++++++++
 .../closed/use-case-1/use-case-1.7.query.sqlpp     |  25 +
 .../closed/use-case-2/use-case-2.1.ddl.sqlpp       |  41 ++
 .../closed/use-case-2/use-case-2.2.update.sqlpp    | 497 ++++++++++++++
 .../closed/use-case-2/use-case-2.3.query.sqlpp     |  25 +
 .../closed/use-case-2/use-case-2.4.update.sqlpp    |  23 +
 .../closed/use-case-2/use-case-2.5.query.sqlpp     |  25 +
 .../closed/use-case-2/use-case-2.6.update.sqlpp    | 497 ++++++++++++++
 .../closed/use-case-2/use-case-2.7.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.1.ddl.sqlpp       |  41 ++
 .../closed/use-case-3/use-case-3.2.update.sqlpp    | 276 ++++++++
 .../closed/use-case-3/use-case-3.3.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.4.update.sqlpp    |  23 +
 .../closed/use-case-3/use-case-3.5.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.6.update.sqlpp    | 276 ++++++++
 .../closed/use-case-3/use-case-3.7.query.sqlpp     |  25 +
 .../closed/use-case-4/use-case-4.1.ddl.sqlpp       |  41 ++
 .../closed/use-case-4/use-case-4.2.update.sqlpp    | 425 ++++++++++++
 .../closed/use-case-4/use-case-4.3.query.sqlpp     |  25 +
 .../closed/use-case-4/use-case-4.4.update.sqlpp    |  23 +
 .../closed/use-case-4/use-case-4.5.query.sqlpp     |  25 +
 .../closed/use-case-4/use-case-4.6.update.sqlpp    | 425 ++++++++++++
 .../closed/use-case-4/use-case-4.7.query.sqlpp     |  25 +
 .../with-additional-atomic-index.1.ddl.sqlpp       |  39 ++
 .../with-additional-atomic-index.2.update.sqlpp    | 302 ++++++++
 .../with-additional-atomic-index.3.query.sqlpp     |  25 +
 .../with-additional-atomic-index.4.update.sqlpp    |  23 +
 .../with-additional-atomic-index.5.query.sqlpp     |  25 +
 .../with-additional-atomic-index.6.update.sqlpp    | 306 +++++++++
 .../with-additional-atomic-index.7.query.sqlpp     |  25 +
 .../with-composite-sk.1.ddl.sqlpp                  |  41 ++
 .../with-composite-sk.2.update.sqlpp               | 306 +++++++++
 .../with-composite-sk.3.query.sqlpp                |  27 +
 .../with-composite-sk.4.update.sqlpp               |  23 +
 .../with-composite-sk.5.query.sqlpp                |  27 +
 .../with-composite-sk.6.update.sqlpp               | 306 +++++++++
 .../with-composite-sk.7.query.sqlpp                |  27 +
 .../with-filter-fields.1.ddl.sqlpp                 |  37 +
 .../with-filter-fields.2.update.sqlpp              | 276 ++++++++
 .../with-filter-fields.3.query.sqlpp               |  25 +
 .../with-filter-fields.4.update.sqlpp              |  23 +
 .../with-filter-fields.5.query.sqlpp               |  25 +
 .../with-filter-fields.6.update.sqlpp              | 276 ++++++++
 .../with-filter-fields.7.query.sqlpp               |  25 +
 .../open/use-case-1/use-case-1.1.ddl.sqlpp         |  36 +
 .../open/use-case-1/use-case-1.2.update.sqlpp      | 272 ++++++++
 .../open/use-case-1/use-case-1.3.query.sqlpp       |  25 +
 .../open/use-case-1/use-case-1.4.update.sqlpp      |  23 +
 .../open/use-case-1/use-case-1.5.query.sqlpp       |  25 +
 .../open/use-case-1/use-case-1.6.update.sqlpp      | 276 ++++++++
 .../open/use-case-1/use-case-1.7.query.sqlpp       |  25 +
 .../open/use-case-2/use-case-2.1.ddl.sqlpp         |  37 +
 .../open/use-case-2/use-case-2.2.update.sqlpp      | 497 ++++++++++++++
 .../open/use-case-2/use-case-2.3.query.sqlpp       |  25 +
 .../open/use-case-2/use-case-2.4.update.sqlpp      |  23 +
 .../open/use-case-2/use-case-2.5.query.sqlpp       |  25 +
 .../open/use-case-2/use-case-2.6.update.sqlpp      | 497 ++++++++++++++
 .../open/use-case-2/use-case-2.7.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.1.ddl.sqlpp         |  37 +
 .../open/use-case-3/use-case-3.2.update.sqlpp      | 276 ++++++++
 .../open/use-case-3/use-case-3.3.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.4.update.sqlpp      |  23 +
 .../open/use-case-3/use-case-3.5.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.6.update.sqlpp      | 276 ++++++++
 .../open/use-case-3/use-case-3.7.query.sqlpp       |  25 +
 .../open/use-case-4/use-case-4.1.ddl.sqlpp         |  37 +
 .../open/use-case-4/use-case-4.2.update.sqlpp      | 425 ++++++++++++
 .../open/use-case-4/use-case-4.3.query.sqlpp       |  25 +
 .../open/use-case-4/use-case-4.4.update.sqlpp      |  23 +
 .../open/use-case-4/use-case-4.5.query.sqlpp       |  25 +
 .../open/use-case-4/use-case-4.6.update.sqlpp      | 425 ++++++++++++
 .../open/use-case-4/use-case-4.7.query.sqlpp       |  25 +
 .../with-additional-atomic-index.1.ddl.sqlpp       |  37 +
 .../with-additional-atomic-index.2.update.sqlpp    | 302 ++++++++
 .../with-additional-atomic-index.3.query.sqlpp     |  25 +
 .../with-additional-atomic-index.4.update.sqlpp    |  23 +
 .../with-additional-atomic-index.5.query.sqlpp     |  25 +
 .../with-additional-atomic-index.6.update.sqlpp    | 306 +++++++++
 .../with-additional-atomic-index.7.query.sqlpp     |  25 +
 .../with-composite-sk.1.ddl.sqlpp                  |  36 +
 .../with-composite-sk.2.update.sqlpp               | 306 +++++++++
 .../with-composite-sk.3.query.sqlpp                |  27 +
 .../with-composite-sk.4.update.sqlpp               |  23 +
 .../with-composite-sk.5.query.sqlpp                |  27 +
 .../with-composite-sk.6.update.sqlpp               | 306 +++++++++
 .../with-composite-sk.7.query.sqlpp                |  27 +
 .../use-case-1/use-case-1.1.ddl.sqlpp              |  45 ++
 .../use-case-1/use-case-1.2.update.sqlpp           | 276 ++++++++
 .../use-case-1/use-case-1.3.update.sqlpp           | 132 ++++
 .../use-case-1/use-case-1.4.ddl.sqlpp              |  22 +
 .../use-case-1/use-case-1.5.query.sqlpp            |  26 +
 .../use-case-1/use-case-1.6.query.sqlpp            |  26 +
 .../use-case-1/use-case-1.7.query.sqlpp            |  28 +
 .../use-case-2/use-case-2.1.ddl.sqlpp              |  48 ++
 .../use-case-2/use-case-2.2.update.sqlpp           | 497 ++++++++++++++
 .../use-case-2/use-case-2.3.update.sqlpp           | 132 ++++
 .../use-case-2/use-case-2.4.ddl.sqlpp              |  22 +
 .../use-case-2/use-case-2.5.query.sqlpp            |  26 +
 .../use-case-2/use-case-2.6.query.sqlpp            |  26 +
 .../use-case-2/use-case-2.7.query.sqlpp            |  28 +
 .../use-case-3/use-case-3.1.ddl.sqlpp              |  49 ++
 .../use-case-3/use-case-3.2.update.sqlpp           | 276 ++++++++
 .../use-case-3/use-case-3.3.update.sqlpp           | 132 ++++
 .../use-case-3/use-case-3.4.ddl.sqlpp              |  22 +
 .../use-case-3/use-case-3.5.query.sqlpp            |  26 +
 .../use-case-3/use-case-3.6.query.sqlpp            |  26 +
 .../use-case-3/use-case-3.7.query.sqlpp            |  26 +
 .../use-case-3/use-case-3.8.query.sqlpp            |  28 +
 .../use-case-4/use-case-4.1.ddl.sqlpp              |  50 ++
 .../use-case-4/use-case-4.2.update.sqlpp           | 487 +++++++++++++
 .../use-case-4/use-case-4.3.update.sqlpp           | 132 ++++
 .../use-case-4/use-case-4.4.ddl.sqlpp              |  22 +
 .../use-case-4/use-case-4.5.query.sqlpp            |  26 +
 .../use-case-4/use-case-4.6.query.sqlpp            |  27 +
 .../use-case-4/use-case-4.7.query.sqlpp            |  26 +
 .../use-case-4/use-case-4.8.query.sqlpp            |  28 +
 .../with-open-index/with-open-index.1.ddl.sqlpp    |  43 ++
 .../with-open-index/with-open-index.2.update.sqlpp | 276 ++++++++
 .../with-open-index/with-open-index.3.update.sqlpp | 132 ++++
 .../with-open-index/with-open-index.4.ddl.sqlpp    |  22 +
 .../with-open-index/with-open-index.5.query.sqlpp  |  26 +
 .../with-open-index/with-open-index.6.query.sqlpp  |  26 +
 .../with-open-index/with-open-index.7.query.sqlpp  |  28 +
 .../closed/use-case-1/use-case-1.1.ddl.sqlpp       |  37 +
 .../closed/use-case-1/use-case-1.2.query.sqlpp     |  22 +
 .../closed/use-case-2/use-case-2.1.ddl.sqlpp       |  39 ++
 .../closed/use-case-2/use-case-2.2.query.sqlpp     |  22 +
 .../closed/use-case-3/use-case-3.1.ddl.sqlpp       |  40 ++
 .../closed/use-case-3/use-case-3.2.query.sqlpp     |  23 +
 .../closed/use-case-3/use-case-3.3.query.sqlpp     |  24 +
 .../closed/use-case-4/use-case-4.1.ddl.sqlpp       |  40 ++
 .../closed/use-case-4/use-case-4.2.query.sqlpp     |  23 +
 .../with-3-level-record-path.1.ddl.sqlpp           |  44 ++
 .../with-3-level-record-path.2.query.sqlpp         |  23 +
 ...omposite-array-different-indicators.1.ddl.sqlpp |  40 ++
 ...posite-array-different-indicators.2.query.sqlpp |  23 +
 .../with-composite-sk.1.ddl.sqlpp                  |  37 +
 .../with-composite-sk.2.query.sqlpp                |  23 +
 .../with-composite-sk.3.query.sqlpp                |  23 +
 .../open/use-case-1/use-case-1.1.ddl.sqlpp         |  35 +
 .../open/use-case-1/use-case-1.2.query.sqlpp       |  23 +
 .../open/use-case-2/use-case-2.1.ddl.sqlpp         |  35 +
 .../open/use-case-2/use-case-2.2.query.sqlpp       |  23 +
 .../open/use-case-3/use-case-3.1.ddl.sqlpp         |  35 +
 .../open/use-case-3/use-case-3.2.query.sqlpp       |  24 +
 .../open/use-case-3/use-case-3.3.query.sqlpp       |  24 +
 .../open/use-case-4/use-case-4.1.ddl.sqlpp         |  35 +
 .../open/use-case-4/use-case-4.2.query.sqlpp       |  23 +
 .../with-3-level-record-path.1.ddl.sqlpp           |  34 +
 .../with-3-level-record-path.2.query.sqlpp         |  23 +
 ...omposite-array-different-indicators.1.ddl.sqlpp |  34 +
 ...posite-array-different-indicators.2.query.sqlpp |  23 +
 .../with-composite-sk.1.ddl.sqlpp                  |  33 +
 .../with-composite-sk.2.query.sqlpp                |  22 +
 .../use-case-1/use-case-1.1.ddl.sqlpp              |  37 +
 .../use-case-1/use-case-1.2.update.sqlpp           | 276 ++++++++
 .../use-case-1/use-case-1.3.ddl.sqlpp              |  22 +
 .../use-case-1/use-case-1.4.query.sqlpp            |  25 +
 .../use-case-1/use-case-1.5.query.sqlpp            |  26 +
 .../use-case-1/use-case-1.6.query.sqlpp            |  26 +
 .../use-case-2/use-case-2.1.ddl.sqlpp              |  40 ++
 .../use-case-2/use-case-2.2.update.sqlpp           | 497 ++++++++++++++
 .../use-case-2/use-case-2.3.ddl.sqlpp              |  22 +
 .../use-case-2/use-case-2.4.query.sqlpp            |  25 +
 .../use-case-2/use-case-2.5.query.sqlpp            |  26 +
 .../use-case-2/use-case-2.6.query.sqlpp            |  26 +
 .../use-case-3/use-case-3.1.ddl.sqlpp              |  40 ++
 .../use-case-3/use-case-3.2.update.sqlpp           | 276 ++++++++
 .../use-case-3/use-case-3.3.ddl.sqlpp              |  22 +
 .../use-case-3/use-case-3.4.query.sqlpp            |  26 +
 .../use-case-3/use-case-3.5.query.sqlpp            |  26 +
 .../use-case-3/use-case-3.6.query.sqlpp            |  26 +
 .../use-case-4/use-case-4.1.ddl.sqlpp              |  41 ++
 .../use-case-4/use-case-4.2.update.sqlpp           | 425 ++++++++++++
 .../use-case-4/use-case-4.3.ddl.sqlpp              |  22 +
 .../use-case-4/use-case-4.4.query.sqlpp            |  26 +
 .../use-case-4/use-case-4.5.query.sqlpp            |  29 +
 .../use-case-4/use-case-4.6.query.sqlpp            |  29 +
 .../use-case-4/use-case-4.7.query.sqlpp            |  29 +
 .../with-composite-pk.1.ddl.sqlpp                  |  38 ++
 .../with-composite-pk.2.update.sqlpp               | 306 +++++++++
 .../with-composite-pk.3.ddl.sqlpp                  |  22 +
 .../with-composite-pk.4.query.sqlpp                |  25 +
 .../with-composite-pk.5.query.sqlpp                |  26 +
 .../with-composite-pk.6.query.sqlpp                |  26 +
 .../with-open-index/with-open-index.1.ddl.sqlpp    |  35 +
 .../with-open-index/with-open-index.2.update.sqlpp | 276 ++++++++
 .../with-open-index/with-open-index.3.ddl.sqlpp    |  22 +
 .../with-open-index/with-open-index.4.query.sqlpp  |  25 +
 .../with-open-index/with-open-index.5.query.sqlpp  |  26 +
 .../with-open-index/with-open-index.6.query.sqlpp  |  26 +
 .../closed/use-case-1/use-case-1.1.ddl.sqlpp       |  36 +
 .../closed/use-case-1/use-case-1.2.update.sqlpp    | 276 ++++++++
 .../closed/use-case-1/use-case-1.3.ddl.sqlpp       |  22 +
 .../closed/use-case-1/use-case-1.4.query.sqlpp     |  25 +
 .../closed/use-case-1/use-case-1.5.query.sqlpp     |  25 +
 .../closed/use-case-2/use-case-2.1.ddl.sqlpp       |  39 ++
 .../closed/use-case-2/use-case-2.2.update.sqlpp    | 497 ++++++++++++++
 .../closed/use-case-2/use-case-2.3.ddl.sqlpp       |  22 +
 .../closed/use-case-2/use-case-2.4.query.sqlpp     |  25 +
 .../closed/use-case-2/use-case-2.5.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.1.ddl.sqlpp       |  40 ++
 .../closed/use-case-3/use-case-3.2.update.sqlpp    | 276 ++++++++
 .../closed/use-case-3/use-case-3.3.ddl.sqlpp       |  22 +
 .../closed/use-case-3/use-case-3.4.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.5.query.sqlpp     |  25 +
 .../closed/use-case-3/use-case-3.6.query.sqlpp     |  26 +
 .../closed/use-case-4/use-case-4.1.ddl.sqlpp       |  39 ++
 .../closed/use-case-4/use-case-4.2.update.sqlpp    | 425 ++++++++++++
 .../closed/use-case-4/use-case-4.3.ddl.sqlpp       |  22 +
 .../closed/use-case-4/use-case-4.4.query.sqlpp     |  26 +
 .../closed/use-case-4/use-case-4.5.query.sqlpp     |  25 +
 .../with-3-level-record-path.1.ddl.sqlpp           |  44 ++
 .../with-3-level-record-path.2.update.sqlpp        | 617 +++++++++++++++++
 .../with-3-level-record-path.3.ddl.sqlpp           |  22 +
 .../with-3-level-record-path.4.query.sqlpp         |  25 +
 .../with-3-level-record-path.5.query.sqlpp         |  25 +
 .../with-composite-pk.1.ddl.sqlpp                  |  36 +
 .../with-composite-pk.2.update.sqlpp               | 306 +++++++++
 .../with-composite-pk.3.ddl.sqlpp                  |  22 +
 .../with-composite-pk.4.query.sqlpp                |  25 +
 .../with-composite-pk.5.query.sqlpp                |  25 +
 .../with-composite-sk.1.ddl.sqlpp                  |  39 ++
 .../with-composite-sk.2.update.sqlpp               | 276 ++++++++
 .../with-composite-sk.3.ddl.sqlpp                  |  22 +
 .../with-composite-sk.4.query.sqlpp                |  27 +
 .../with-composite-sk.5.query.sqlpp                |  25 +
 .../with-filter-fields.1.ddl.sqlpp                 |  36 +
 .../with-filter-fields.2.update.sqlpp              | 276 ++++++++
 .../with-filter-fields.3.ddl.sqlpp                 |  22 +
 .../with-filter-fields.4.query.sqlpp               |  26 +
 .../with-filter-fields.5.query.sqlpp               |  25 +
 .../open/use-case-1/use-case-1.1.ddl.sqlpp         |  34 +
 .../open/use-case-1/use-case-1.2.update.sqlpp      | 276 ++++++++
 .../open/use-case-1/use-case-1.3.ddl.sqlpp         |  22 +
 .../open/use-case-1/use-case-1.4.query.sqlpp       |  25 +
 .../open/use-case-1/use-case-1.5.query.sqlpp       |  25 +
 .../open/use-case-2/use-case-2.1.ddl.sqlpp         |  34 +
 .../open/use-case-2/use-case-2.2.update.sqlpp      | 497 ++++++++++++++
 .../open/use-case-2/use-case-2.3.ddl.sqlpp         |  22 +
 .../open/use-case-2/use-case-2.4.query.sqlpp       |  25 +
 .../open/use-case-2/use-case-2.5.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.1.ddl.sqlpp         |  35 +
 .../open/use-case-3/use-case-3.2.update.sqlpp      | 276 ++++++++
 .../open/use-case-3/use-case-3.3.ddl.sqlpp         |  22 +
 .../open/use-case-3/use-case-3.4.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.5.query.sqlpp       |  25 +
 .../open/use-case-3/use-case-3.6.query.sqlpp       |  26 +
 .../open/use-case-4/use-case-4.1.ddl.sqlpp         |  34 +
 .../open/use-case-4/use-case-4.2.update.sqlpp      | 425 ++++++++++++
 .../open/use-case-4/use-case-4.3.ddl.sqlpp         |  22 +
 .../open/use-case-4/use-case-4.4.query.sqlpp       |  26 +
 .../open/use-case-4/use-case-4.5.query.sqlpp       |  25 +
 .../with-3-level-record-path.1.ddl.sqlpp           |  35 +
 .../with-3-level-record-path.2.update.sqlpp        | 617 +++++++++++++++++
 .../with-3-level-record-path.3.ddl.sqlpp           |  22 +
 .../with-3-level-record-path.4.query.sqlpp         |  25 +
 .../with-3-level-record-path.5.query.sqlpp         |  25 +
 .../with-composite-sk.1.ddl.sqlpp                  |  34 +
 .../with-composite-sk.2.update.sqlpp               | 276 ++++++++
 .../with-composite-sk.3.ddl.sqlpp                  |  22 +
 .../with-composite-sk.4.query.sqlpp                |  27 +
 .../with-composite-sk.5.query.sqlpp                |  25 +
 .../api/cluster_state_1/cluster_state_1.1.regexadm |   1 +
 .../cluster_state_1_full.1.regexadm                |   1 +
 .../cluster_state_1_less.1.regexadm                |   1 +
 .../use-case-1/use-case-1.1.adm                    |   1 +
 .../use-case-2/use-case-2.1.adm                    |   1 +
 .../use-case-3/use-case-3.1.adm                    |   1 +
 .../use-case-4/use-case-4.1.adm                    |   1 +
 .../with-3-level-record-path.1.adm                 |   1 +
 .../with-composite-pk/with-composite-pk.1.adm      |   1 +
 .../with-filter-fields/with-filter-fields.1.adm    |   1 +
 .../with-open-index/with-open-index.1.adm          |   1 +
 .../closed/use-case-1/use-case-1.1.adm             |   1 +
 .../closed/use-case-2/use-case-2.1.adm             |   1 +
 .../closed/use-case-3/use-case-3.1.adm             |   1 +
 .../closed/use-case-3/use-case-3.2.adm             |   1 +
 .../closed/use-case-4/use-case-4.1.adm             |   1 +
 .../with-3-level-record-path.1.adm                 |   1 +
 .../with-composite-pk/with-composite-pk.1.adm      |   1 +
 .../with-filter-fields/with-filter-fields.1.adm    |   1 +
 .../open/use-case-1/use-case-1.1.adm               |   1 +
 .../open/use-case-2/use-case-2.1.adm               |   1 +
 .../open/use-case-3/use-case-3.1.adm               |   1 +
 .../open/use-case-3/use-case-3.2.adm               |   1 +
 .../open/use-case-4/use-case-4.1.adm               |   1 +
 .../closed/use-case-1/use-case-1.1.adm             |   1 +
 .../closed/use-case-1/use-case-1.2.adm             |   1 +
 .../closed/use-case-1/use-case-1.3.adm             |   1 +
 .../closed/use-case-2/use-case-2.1.adm             |   1 +
 .../closed/use-case-2/use-case-2.2.adm             |   1 +
 .../closed/use-case-2/use-case-2.3.adm             |   1 +
 .../closed/use-case-3/use-case-3.1.adm             |   1 +
 .../closed/use-case-3/use-case-3.2.adm             |   1 +
 .../closed/use-case-3/use-case-3.3.adm             |   1 +
 .../closed/use-case-4/use-case-4.1.adm             |   1 +
 .../closed/use-case-4/use-case-4.2.adm             |   1 +
 .../closed/use-case-4/use-case-4.3.adm             |   1 +
 .../with-additional-atomic-index.1.adm             |   1 +
 .../with-additional-atomic-index.2.adm             |   1 +
 .../with-additional-atomic-index.3.adm             |   1 +
 .../with-composite-sk/with-composite-sk.1.adm      |   1 +
 .../with-composite-sk/with-composite-sk.2.adm      |   1 +
 .../with-composite-sk/with-composite-sk.3.adm      |   1 +
 .../with-filter-fields/with-filter-fields.1.adm    |   1 +
 .../with-filter-fields/with-filter-fields.2.adm    |   1 +
 .../with-filter-fields/with-filter-fields.3.adm    |   1 +
 .../open/use-case-1/use-case-1.1.adm               |   1 +
 .../open/use-case-1/use-case-1.2.adm               |   1 +
 .../open/use-case-1/use-case-1.3.adm               |   1 +
 .../open/use-case-2/use-case-2.1.adm               |   1 +
 .../open/use-case-2/use-case-2.2.adm               |   1 +
 .../open/use-case-2/use-case-2.3.adm               |   1 +
 .../open/use-case-3/use-case-3.1.adm               |   1 +
 .../open/use-case-3/use-case-3.2.adm               |   1 +
 .../open/use-case-3/use-case-3.3.adm               |   1 +
 .../open/use-case-4/use-case-4.1.adm               |   1 +
 .../open/use-case-4/use-case-4.2.adm               |   1 +
 .../open/use-case-4/use-case-4.3.adm               |   1 +
 .../with-additional-atomic-index.1.adm             |   1 +
 .../with-additional-atomic-index.2.adm             |   1 +
 .../with-additional-atomic-index.3.adm             |   1 +
 .../open/with-composite-sk/with-composite-sk.1.adm |   1 +
 .../open/with-composite-sk/with-composite-sk.2.adm |   1 +
 .../open/with-composite-sk/with-composite-sk.3.adm |   1 +
 .../use-case-1/with-open-index.1.adm               |   1 +
 .../use-case-1/with-open-index.2.adm               |   1 +
 .../use-case-1/with-open-index.3.adm               |   1 +
 .../use-case-2/use-case-2.1.adm                    |   1 +
 .../use-case-2/use-case-2.2.adm                    |   1 +
 .../use-case-2/use-case-2.3.adm                    |   1 +
 .../use-case-3/use-case-3.1.adm                    |   1 +
 .../use-case-3/use-case-3.2.adm                    |   1 +
 .../use-case-3/use-case-3.3.adm                    |   1 +
 .../use-case-3/use-case-3.4.adm                    |   1 +
 .../use-case-4/use-case-4.1.adm                    |   1 +
 .../use-case-4/use-case-4.2.adm                    |   1 +
 .../use-case-4/use-case-4.3.adm                    |   1 +
 .../use-case-4/use-case-4.4.adm                    |   1 +
 .../with-open-index/use-case-1.1.adm               |   1 +
 .../with-open-index/use-case-1.2.adm               |   1 +
 .../with-open-index/use-case-1.3.adm               |   1 +
 .../metadata/closed/use-case-1/use-case-1.1.adm    |   1 +
 .../metadata/closed/use-case-2/use-case-2.1.adm    |   1 +
 .../metadata/closed/use-case-3/use-case-3.1.adm    |   1 +
 .../metadata/closed/use-case-3/use-case-3.2.adm    |   1 +
 .../metadata/closed/use-case-4/use-case-4.1.adm    |   1 +
 .../with-3-level-record-path.1.adm                 |   1 +
 ...with-composite-array-different-indicators.1.adm |   1 +
 .../with-composite-sk/with-composite-sk.1.adm      |   1 +
 .../with-composite-sk/with-composite-sk.2.adm      |   1 +
 .../metadata/open/use-case-1/use-case-1.1.adm      |   1 +
 .../metadata/open/use-case-2/use-case-2.1.adm      |   1 +
 .../metadata/open/use-case-3/use-case-3.1.adm      |   1 +
 .../metadata/open/use-case-3/use-case-3.2.adm      |   1 +
 .../metadata/open/use-case-4/use-case-4.1.adm      |   1 +
 .../with-3-level-record-path.1.adm                 |   1 +
 ...with-composite-array-different-indicators.1.adm |   1 +
 .../open/with-composite-sk/with-composite-sk.1.adm |   1 +
 .../use-case-1/use-case-1.1.adm                    |   1 +
 .../use-case-1/use-case-1.2.adm                    |   1 +
 .../use-case-1/use-case-1.3.adm                    |   1 +
 .../use-case-2/use-case-2.1.adm                    |   1 +
 .../use-case-2/use-case-2.2.adm                    |   1 +
 .../use-case-2/use-case-2.3.adm                    |   1 +
 .../use-case-3/use-case-3.1.adm                    |   1 +
 .../use-case-3/use-case-3.2.adm                    |   1 +
 .../use-case-3/use-case-3.3.adm                    |   1 +
 .../use-case-4/use-case-4.1.adm                    |   1 +
 .../use-case-4/use-case-4.2.adm                    |   1 +
 .../use-case-4/use-case-4.3.adm                    |   1 +
 .../use-case-4/use-case-4.4.adm                    |   1 +
 .../with-composite-pk/with-composite-pk.1.adm      |   1 +
 .../with-composite-pk/with-composite-pk.2.adm      |   1 +
 .../with-composite-pk/with-composite-pk.3.adm      |   1 +
 .../with-open-index/with-open-index.1.adm          |   1 +
 .../with-open-index/with-open-index.2.adm          |   1 +
 .../with-open-index/with-open-index.3.adm          |   1 +
 .../closed/use-case-1/use-case-1.1.adm             |   1 +
 .../closed/use-case-1/use-case-1.2.adm             |   1 +
 .../closed/use-case-2/use-case-2.1.adm             |   1 +
 .../closed/use-case-2/use-case-2.2.adm             |   1 +
 .../closed/use-case-3/use-case-3.1.adm             |   1 +
 .../closed/use-case-3/use-case-3.2.adm             |   1 +
 .../closed/use-case-3/use-case-3.3.adm             |   1 +
 .../closed/use-case-4/use-case-4.1.adm             |   1 +
 .../closed/use-case-4/use-case-4.2.adm             |   1 +
 .../with-3-level-record-path.1.adm                 |   1 +
 .../with-3-level-record-path.2.adm                 |   1 +
 .../with-composite-pk/with-composite-pk.1.adm      |   1 +
 .../with-composite-pk/with-composite-pk.2.adm      |   1 +
 .../with-composite-sk/with-composite-sk.1.adm      |   1 +
 .../with-composite-sk/with-composite-sk.2.adm      |   1 +
 .../with-filter-fields/with-filter-fields.1.adm    |   1 +
 .../with-filter-fields/with-filter-fields.2.adm    |   1 +
 .../open/use-case-1/use-case-1.1.adm               |   1 +
 .../open/use-case-1/use-case-1.2.adm               |   1 +
 .../open/use-case-2/use-case-2.1.adm               |   1 +
 .../open/use-case-2/use-case-2.2.adm               |   1 +
 .../open/use-case-3/use-case-3.1.adm               |   1 +
 .../open/use-case-3/use-case-3.2.adm               |   1 +
 .../open/use-case-3/use-case-3.3.adm               |   1 +
 .../open/use-case-4/use-case-4.1.adm               |   1 +
 .../open/use-case-4/use-case-4.2.adm               |   1 +
 .../with-3-level-record-path.1.adm                 |   1 +
 .../with-3-level-record-path.2.adm                 |   1 +
 .../open/with-composite-sk/with-composite-sk.1.adm |   1 +
 .../open/with-composite-sk/with-composite-sk.2.adm |   1 +
 .../test/resources/runtimets/testsuite_sqlpp.xml   | 422 +++++++++++-
 .../asterix/common/config/CompilerProperties.java  |  12 +-
 .../asterix/common/config/DatasetConfig.java       |   3 +-
 .../common/config/OptimizationConfUtil.java        |   3 +
 .../asterix/common/exceptions/ErrorCode.java       |   1 +
 .../src/main/resources/asx_errormsg/en.properties  |   1 +
 asterixdb/asterix-doc/pom.xml                      |   2 +-
 asterixdb/asterix-doc/src/main/grammar/sqlpp.ebnf  |   8 +-
 .../src/main/markdown/sqlpp/7_ddl_dml.md           |  17 +-
 .../main/markdown/sqlpp/appendix_2_arrayindex.md   |  34 +
 .../src/site/markdown/sqlpp/arrayindex.md          | 188 +++++
 asterixdb/asterix-doc/src/site/site.xml            |   1 +
 .../common/statement/CreateIndexStatement.java     | 156 +++--
 .../lang/common/visitor/FormatPrintVisitor.java    |  54 +-
 .../asterix-lang-sqlpp/src/main/javacc/SQLPP.jj    | 170 ++++-
 .../org/apache/asterix/metadata/MetadataNode.java  |  12 +-
 .../metadata/MetadataTransactionContext.java       |   4 +-
 ...java => ArrayBTreeResourceFactoryProvider.java} |  86 +--
 .../declared/BTreeResourceFactoryProvider.java     |  56 +-
 .../metadata/declared/MetadataProvider.java        | 140 +++-
 .../apache/asterix/metadata/entities/Dataset.java  |   8 +-
 .../apache/asterix/metadata/entities/Index.java    | 313 +++++++--
 .../IndexTupleTranslator.java                      | 513 +++++++++++---
 .../asterix/metadata/utils/ArrayIndexUtil.java     | 380 +++++++++++
 .../metadata/utils/ExternalIndexingOperations.java |   5 +
 .../apache/asterix/metadata/utils/IndexUtil.java   |  17 +-
 .../InvertedIndexResourceFactoryProvider.java      |  34 +-
 .../asterix/metadata/utils/KeyFieldTypeUtil.java   | 163 ++++-
 .../utils/RTreeResourceFactoryProvider.java        |  29 +-
 .../SecondaryArrayIndexBTreeOperationsHelper.java  | 757 +++++++++++++++++++++
 .../utils/SecondaryBTreeOperationsHelper.java      |  30 +-
 .../SecondaryCorrelatedBTreeOperationsHelper.java  |  20 +-
 ...aryCorrelatedInvertedIndexOperationsHelper.java |  22 +-
 .../SecondaryCorrelatedRTreeOperationsHelper.java  |  11 +-
 .../utils/SecondaryIndexOperationsHelper.java      |   8 +-
 .../SecondaryInvertedIndexOperationsHelper.java    |  21 +-
 .../utils/SecondaryRTreeOperationsHelper.java      |  11 +-
 .../apache/asterix/metadata/utils/TypeUtil.java    | 372 +++++++---
 .../IndexTupleTranslatorTest.java                  |  10 +-
 ...sertDeleteWithNestedPlanOperatorDescriptor.java |  54 ++
 ...rtDeleteWithNestedPlanOperatorNodePushable.java | 181 +++++
 .../LSMSecondaryUpsertOperatorDescriptor.java      |   8 +-
 .../LSMSecondaryUpsertOperatorNodePushable.java    |  21 +-
 ...aryUpsertWithNestedPlanOperatorDescriptor.java} |  42 +-
 ...ryUpsertWithNestedPlanOperatorNodePushable.java | 222 ++++++
 .../core/algebra/metadata/IMetadataProvider.java   |  18 +-
 .../logical/IndexInsertDeleteUpsertOperator.java   |  94 ++-
 .../visitors/FDsAndEquivClassesVisitor.java        |  11 +
 .../visitors/IsomorphismOperatorVisitor.java       |   8 +-
 .../IsomorphismVariableMappingVisitor.java         |   3 +-
 .../logical/visitors/OperatorDeepCopyVisitor.java  |   3 +
 .../logical/visitors/SchemaVariableVisitor.java    |   1 +
 .../visitors/SubstituteVariableVisitor.java        |   3 +
 .../logical/visitors/UsedVariableVisitor.java      |   4 +-
 .../physical/AbstractPhysicalOperator.java         |  22 +-
 .../operators/physical/IndexBulkloadPOperator.java |   2 +-
 .../physical/IndexInsertDeleteUpsertPOperator.java |  38 +-
 .../operators/physical/SubplanPOperator.java       |   3 +-
 .../LogicalOperatorPrettyPrintVisitor.java         |   5 +-
 .../LogicalOperatorPrettyPrintVisitorJson.java     |  10 +-
 .../algebricks/core/config/AlgebricksConfig.java   |   1 +
 .../rewriter/base/PhysicalOptimizationConfig.java  |   9 +
 .../core/utils/LogicalOperatorDotVisitor.java      |   4 +
 .../runtime/operators/meta/PipelineAssembler.java  |  18 +
 .../operators/meta/SubplanRuntimeFactory.java      |  19 +-
 698 files changed, 41395 insertions(+), 1342 deletions(-)

diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/CommitOperator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/CommitOperator.java
index 6578c9c..135c3e3 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/CommitOperator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/CommitOperator.java
@@ -44,6 +44,10 @@ public class CommitOperator extends AbstractDelegatedLogicalOperator {
         this.isSink = isSink;
     }
 
+    public List<LogicalVariable> getPrimaryKeyLogicalVars() {
+        return this.primaryKeyLogicalVars;
+    }
+
     @Override
     public boolean isMap() {
         return false;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java
index 8c2c1df..ed8e9bf 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/BTreeSearchPOperator.java
@@ -156,7 +156,7 @@ public class BTreeSearchPOperator extends IndexSearchPOperator {
             return false;
         }
         Index searchIndex = ((DataSourceIndex) idx).getIndex();
-        int numberOfKeyFields = searchIndex.getKeyFieldNames().size();
+        int numberOfKeyFields = ((Index.ValueIndexDetails) searchIndex.getIndexDetails()).getKeyFieldNames().size();
 
         if (lowKeyVarList.size() != numberOfKeyFields || highKeyVarList.size() != numberOfKeyFields) {
             return false;
@@ -186,7 +186,8 @@ public class BTreeSearchPOperator extends IndexSearchPOperator {
 
                 // If this is a composite primary index, then all of the keys should be provided.
                 Index searchIndex = ((DataSourceIndex) idx).getIndex();
-                int numberOfKeyFields = searchIndex.getKeyFieldNames().size();
+                int numberOfKeyFields =
+                        ((Index.ValueIndexDetails) searchIndex.getIndexDetails()).getKeyFieldNames().size();
 
                 if (numberOfKeyFields < 2
                         || (lowKeyVarList.size() == numberOfKeyFields && highKeyVarList.size() == numberOfKeyFields)) {
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/InvertedIndexPOperator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/InvertedIndexPOperator.java
index fd664df..2d82f6f 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/InvertedIndexPOperator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/InvertedIndexPOperator.java
@@ -172,7 +172,7 @@ public class InvertedIndexPOperator extends IndexSearchPOperator {
                 InvertedIndexAccessMethod.getBinaryTokenizerFactory(searchModifierType, searchKeyType, secondaryIndex);
         IFullTextConfigEvaluatorFactory fullTextConfigEvaluatorFactory =
                 FullTextUtil.fetchFilterAndCreateConfigEvaluator(metadataProvider, secondaryIndex.getDataverseName(),
-                        secondaryIndex.getFullTextConfigName());
+                        ((Index.TextIndexDetails) secondaryIndex.getIndexDetails()).getFullTextConfigName());
         IIndexDataflowHelperFactory dataflowHelperFactory = new IndexDataflowHelperFactory(
                 metadataProvider.getStorageComponentProvider().getStorageManager(), secondarySplitsAndConstraint.first);
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
index ed35026..b7713c9 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/IntroduceSecondaryIndexInsertDeleteRule.java
@@ -18,11 +18,15 @@
  */
 package org.apache.asterix.optimizer.rules;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.asterix.algebra.operators.CommitOperator;
@@ -38,6 +42,7 @@ import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.Index;
 import org.apache.asterix.metadata.entities.InternalDatasetDetails;
+import org.apache.asterix.metadata.utils.ArrayIndexUtil;
 import org.apache.asterix.om.base.AInt32;
 import org.apache.asterix.om.base.AOrderedList;
 import org.apache.asterix.om.base.AString;
@@ -57,6 +62,7 @@ import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
 import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
@@ -65,17 +71,24 @@ import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCa
 import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator.ExecutionMode;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DelegateOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistinctOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IndexInsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator.Kind;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.ProjectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.ReplicateOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.TokenizeOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
+import org.apache.hyracks.algebricks.core.algebra.plan.ALogicalPlanImpl;
 import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
 import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
 import org.apache.hyracks.api.exceptions.SourceLocation;
@@ -87,6 +100,8 @@ import org.apache.hyracks.api.exceptions.SourceLocation;
  * assign --> insert-delete-upsert --> *(secondary indexes index-insert-delete-upsert) --> sink
  */
 public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewriteRule {
+    private IOptimizationContext context;
+    private SourceLocation sourceLoc;
 
     @Override
     public boolean rewritePre(Mutable<ILogicalOperator> opRef, IOptimizationContext context)
@@ -121,7 +136,8 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                 primaryIndexModificationOp.getAdditionalNonFilteringExpressions();
         LogicalVariable newRecordVar;
         LogicalVariable newMetaVar = null;
-        SourceLocation sourceLoc = primaryIndexModificationOp.getSourceLocation();
+        sourceLoc = primaryIndexModificationOp.getSourceLocation();
+        this.context = context;
 
         /**
          * inputOp is the assign operator which extracts primary keys from the input
@@ -129,14 +145,14 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
          */
         AbstractLogicalOperator inputOp =
                 (AbstractLogicalOperator) primaryIndexModificationOp.getInputs().get(0).getValue();
-        newRecordVar = getRecordVar(context, inputOp, newRecordExpr, 0);
+        newRecordVar = getRecordVar(inputOp, newRecordExpr, 0);
         if (newMetaExprs != null && !newMetaExprs.isEmpty()) {
             if (newMetaExprs.size() > 1) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
                         "Number of meta records can't be more than 1. Number of meta records found = "
                                 + newMetaExprs.size());
             }
-            newMetaVar = getRecordVar(context, inputOp, newMetaExprs.get(0).getValue(), 1);
+            newMetaVar = getRecordVar(inputOp, newMetaExprs.get(0).getValue(), 1);
         }
 
         /*
@@ -177,9 +193,9 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
             // for insert, primary key index is handled together when primary index
             indexes = indexes.stream().filter(index -> !index.isPrimaryKeyIndex()).collect(Collectors.toList());
         }
+
         // Set the top operator pointer to the primary IndexInsertDeleteOperator
         ILogicalOperator currentTop = primaryIndexModificationOp;
-        boolean hasSecondaryIndex = false;
 
         // Put an n-gram or a keyword index in the later stage of index-update,
         // since TokenizeOperator needs to be involved.
@@ -216,7 +232,7 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
 
         // Replicate Operator is applied only when doing the bulk-load.
         ReplicateOperator replicateOp = null;
-        if (secondaryIndexTotalCnt > 1 && primaryIndexModificationOp.isBulkload()) {
+        if (secondaryIndexTotalCnt > 1 && isBulkload) {
             // Split the logical plan into "each secondary index update branch"
             // to replicate each <PK,OBJECT> pair.
             replicateOp = new ReplicateOperator(secondaryIndexTotalCnt);
@@ -247,8 +263,8 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                  * is solved
                  */
                 || primaryIndexModificationOp.getOperation() == Kind.DELETE) {
-            injectFieldAccessesForIndexes(context, dataset, indexes, fieldVarsForNewRecord, recType, metaType,
-                    newRecordVar, newMetaVar, primaryIndexModificationOp, false);
+            injectFieldAccessesForIndexes(dataset, indexes, fieldVarsForNewRecord, recType, metaType, newRecordVar,
+                    newMetaVar, primaryIndexModificationOp, false);
             if (replicateOp != null) {
                 context.computeAndSetTypeEnvironmentForOperator(replicateOp);
             }
@@ -260,38 +276,61 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
          */) {
             List<LogicalVariable> beforeOpMetaVars = primaryIndexModificationOp.getBeforeOpAdditionalNonFilteringVars();
             LogicalVariable beforeOpMetaVar = beforeOpMetaVars == null ? null : beforeOpMetaVars.get(0);
-            currentTop = injectFieldAccessesForIndexes(context, dataset, indexes, fieldVarsForBeforeOperation, recType,
-                    metaType, primaryIndexModificationOp.getBeforeOpRecordVar(), beforeOpMetaVar, currentTop, true);
+            currentTop = injectFieldAccessesForIndexes(dataset, indexes, fieldVarsForBeforeOperation, recType, metaType,
+                    primaryIndexModificationOp.getBeforeOpRecordVar(), beforeOpMetaVar, currentTop, true);
         }
 
-        // Iterate each secondary index and applying Index Update operations.
-        // At first, op1 is the index insert op insertOp
+        // Add the appropriate SIDX maintenance operations.
         for (Index index : indexes) {
             if (!index.isSecondaryIndex()) {
                 continue;
             }
-            hasSecondaryIndex = true;
+
             // Get the secondary fields names and types
-            List<List<String>> secondaryKeyFields = index.getKeyFieldNames();
-            List<IAType> secondaryKeyTypes = index.getKeyFieldTypes();
+            List<List<String>> secondaryKeyFields = null;
+            List<IAType> secondaryKeyTypes = null;
+            List<Integer> secondaryKeySources = null;
+            switch (Index.IndexCategory.of(index.getIndexType())) {
+                case VALUE:
+                    Index.ValueIndexDetails valueIndexDetails = (Index.ValueIndexDetails) index.getIndexDetails();
+                    secondaryKeyFields = valueIndexDetails.getKeyFieldNames();
+                    secondaryKeyTypes = valueIndexDetails.getKeyFieldTypes();
+                    secondaryKeySources = valueIndexDetails.getKeyFieldSourceIndicators();
+                    break;
+                case TEXT:
+                    Index.TextIndexDetails textIndexDetails = (Index.TextIndexDetails) index.getIndexDetails();
+                    secondaryKeyFields = textIndexDetails.getKeyFieldNames();
+                    secondaryKeyTypes = textIndexDetails.getKeyFieldTypes();
+                    secondaryKeySources = textIndexDetails.getKeyFieldSourceIndicators();
+                    break;
+                case ARRAY:
+                    // These details are handled separately for array indexes.
+                    break;
+                default:
+                    continue;
+            }
+
+            // Set our key variables and expressions for non-array indexes. Our secondary keys for array indexes will
+            // always be an empty list.
             List<LogicalVariable> secondaryKeyVars = new ArrayList<>();
             List<Mutable<ILogicalExpression>> secondaryExpressions = new ArrayList<>();
             List<Mutable<ILogicalExpression>> beforeOpSecondaryExpressions = new ArrayList<>();
             ILogicalOperator replicateOutput;
-
-            for (int i = 0; i < secondaryKeyFields.size(); i++) {
-                IndexFieldId indexFieldId = new IndexFieldId(index.getKeyFieldSourceIndicators().get(i),
-                        secondaryKeyFields.get(i), secondaryKeyTypes.get(i).getTypeTag());
-                LogicalVariable skVar = fieldVarsForNewRecord.get(indexFieldId);
-                secondaryKeyVars.add(skVar);
-                VariableReferenceExpression skVarRef = new VariableReferenceExpression(skVar);
-                skVarRef.setSourceLocation(sourceLoc);
-                secondaryExpressions.add(new MutableObject<ILogicalExpression>(skVarRef));
-                if (primaryIndexModificationOp.getOperation() == Kind.UPSERT) {
-                    VariableReferenceExpression varRef =
-                            new VariableReferenceExpression(fieldVarsForBeforeOperation.get(indexFieldId));
-                    varRef.setSourceLocation(sourceLoc);
-                    beforeOpSecondaryExpressions.add(new MutableObject<ILogicalExpression>(varRef));
+            if (!index.getIndexType().equals(IndexType.ARRAY)) {
+                for (int i = 0; i < secondaryKeyFields.size(); i++) {
+                    IndexFieldId indexFieldId = new IndexFieldId(secondaryKeySources.get(i), secondaryKeyFields.get(i),
+                            secondaryKeyTypes.get(i).getTypeTag());
+                    LogicalVariable skVar = fieldVarsForNewRecord.get(indexFieldId);
+                    secondaryKeyVars.add(skVar);
+                    VariableReferenceExpression skVarRef = new VariableReferenceExpression(skVar);
+                    skVarRef.setSourceLocation(sourceLoc);
+                    secondaryExpressions.add(new MutableObject<ILogicalExpression>(skVarRef));
+                    if (primaryIndexModificationOp.getOperation() == Kind.UPSERT) {
+                        VariableReferenceExpression varRef =
+                                new VariableReferenceExpression(fieldVarsForBeforeOperation.get(indexFieldId));
+                        varRef.setSourceLocation(sourceLoc);
+                        beforeOpSecondaryExpressions.add(new MutableObject<ILogicalExpression>(varRef));
+                    }
                 }
             }
 
@@ -301,12 +340,13 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                 Mutable<ILogicalExpression> filterExpression =
                         (primaryIndexModificationOp.getOperation() == Kind.UPSERT) ? null
                                 : createFilterExpression(secondaryKeyVars, context.getOutputTypeEnvironment(currentTop),
-                                        index.isOverridingKeyFieldTypes(), sourceLoc);
+                                        index.getIndexDetails().isOverridingKeyFieldTypes());
                 DataSourceIndex dataSourceIndex = new DataSourceIndex(index, dataverseName, datasetName, mp);
 
                 // Introduce the TokenizeOperator only when doing bulk-load,
                 // and index type is keyword or n-gram.
-                if (index.getIndexType() != IndexType.BTREE && primaryIndexModificationOp.isBulkload()) {
+                if (index.getIndexType() != IndexType.BTREE && index.getIndexType() != IndexType.ARRAY
+                        && primaryIndexModificationOp.isBulkload()) {
                     // Note: Bulk load case, we don't need to take care of it for upsert operation
                     // Check whether the index is length-partitioned or not.
                     // If partitioned, [input variables to TokenizeOperator,
@@ -330,8 +370,8 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
 
                     // Check the field type of the secondary key.
                     IAType secondaryKeyType;
-                    Pair<IAType, Boolean> keyPairType = Index.getNonNullableOpenFieldType(
-                            index.getKeyFieldTypes().get(0), secondaryKeyFields.get(0), recType);
+                    Pair<IAType, Boolean> keyPairType = Index.getNonNullableOpenFieldType(secondaryKeyTypes.get(0),
+                            secondaryKeyFields.get(0), recType);
                     secondaryKeyType = keyPairType.first;
 
                     List<Object> varTypes = new ArrayList<>();
@@ -399,11 +439,82 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                         }
                     }
                     indexUpdate.getInputs().add(new MutableObject<ILogicalOperator>(currentTop));
+
+                    // For array indexes we have no secondary keys to reference. We must add separate branches to
+                    // first extract our keys.
+                    if (index.getIndexType() == IndexType.ARRAY && !isBulkload) {
+                        NestedTupleSourceOperator unnestSourceOp =
+                                new NestedTupleSourceOperator(new MutableObject<>(indexUpdate));
+                        unnestSourceOp.setSourceLocation(sourceLoc);
+                        context.computeAndSetTypeEnvironmentForOperator(unnestSourceOp);
+                        UnnestBranchCreator unnestSIDXBranch = buildUnnestBranch(unnestSourceOp, index, newRecordVar,
+                                newMetaVar, recType, metaType, dataset.hasMetaPart());
+                        unnestSIDXBranch.applyProjectDistinct();
+
+                        // If there exists a filter expression, add it to the top of our nested plan.
+                        filterExpression = (primaryIndexModificationOp.getOperation() == Kind.UPSERT) ? null
+                                : createFilterExpression(unnestSIDXBranch.lastFieldVars,
+                                        context.getOutputTypeEnvironment(unnestSIDXBranch.currentTop),
+                                        index.getIndexDetails().isOverridingKeyFieldTypes());
+                        if (filterExpression != null) {
+                            unnestSIDXBranch.applyFilteringExpression(filterExpression);
+                        }
+
+                        // Finalize our nested plan.
+                        ILogicalPlan unnestPlan = unnestSIDXBranch.buildBranch();
+                        indexUpdate.getNestedPlans().add(unnestPlan);
+
+                        // If we have an UPSERT, then create and add a branch to extract our old keys as well.
+                        if (primaryIndexModificationOp.getOperation() == Kind.UPSERT) {
+                            NestedTupleSourceOperator unnestBeforeSourceOp =
+                                    new NestedTupleSourceOperator(new MutableObject<>(indexUpdate));
+                            unnestBeforeSourceOp.setSourceLocation(sourceLoc);
+                            context.computeAndSetTypeEnvironmentForOperator(unnestBeforeSourceOp);
+
+                            List<LogicalVariable> beforeOpMetaVars =
+                                    primaryIndexModificationOp.getBeforeOpAdditionalNonFilteringVars();
+                            LogicalVariable beforeOpMetaVar = beforeOpMetaVars == null ? null : beforeOpMetaVars.get(0);
+                            UnnestBranchCreator unnestBeforeSIDXBranch = buildUnnestBranch(unnestBeforeSourceOp, index,
+                                    primaryIndexModificationOp.getBeforeOpRecordVar(), beforeOpMetaVar, recType,
+                                    metaType, dataset.hasMetaPart());
+                            unnestBeforeSIDXBranch.applyProjectDistinct();
+                            indexUpdate.getNestedPlans().add(unnestBeforeSIDXBranch.buildBranch());
+                        }
+                    } else if (index.getIndexType() == IndexType.ARRAY && isBulkload) {
+                        // If we have a bulk load, we must sort the entire input by <SK, PK>. Do not use any
+                        // nested plans here.
+                        UnnestBranchCreator unnestSIDXBranch = buildUnnestBranch(currentTop, index, newRecordVar,
+                                newMetaVar, recType, metaType, dataset.hasMetaPart());
+                        unnestSIDXBranch.applyProjectDistinct(primaryIndexModificationOp.getPrimaryKeyExpressions(),
+                                primaryIndexModificationOp.getAdditionalFilteringExpressions());
+                        indexUpdate.getInputs().clear();
+                        introduceNewOp(unnestSIDXBranch.currentTop, indexUpdate, true);
+
+                        // Update the secondary expressions of our index.
+                        secondaryExpressions = new ArrayList<>();
+                        for (LogicalVariable var : unnestSIDXBranch.lastFieldVars) {
+                            secondaryExpressions.add(new MutableObject<>(new VariableReferenceExpression(var)));
+                        }
+                        indexUpdate.setSecondaryKeyExprs(secondaryExpressions);
+
+                        // Update the filter expression to include these new keys.
+                        filterExpression = createFilterExpression(unnestSIDXBranch.lastFieldVars,
+                                context.getOutputTypeEnvironment(unnestSIDXBranch.currentTop),
+                                index.getIndexDetails().isOverridingKeyFieldTypes());
+                        indexUpdate.setFilterExpression(filterExpression);
+
+                        if (replicateOp != null) {
+                            // If we have a replicate, then update the replicate operator to include this branch.
+                            replicateOp.getOutputs().add(new MutableObject<>(unnestSIDXBranch.currentBottom));
+                            op0.getInputs().add(new MutableObject<ILogicalOperator>(indexUpdate));
+                            continue;
+                        }
+                    }
                 }
             } else {
                 // Get type, dimensions and number of keys
-                Pair<IAType, Boolean> keyPairType = Index.getNonNullableOpenFieldType(index.getKeyFieldTypes().get(0),
-                        secondaryKeyFields.get(0), recType);
+                Pair<IAType, Boolean> keyPairType =
+                        Index.getNonNullableOpenFieldType(secondaryKeyTypes.get(0), secondaryKeyFields.get(0), recType);
                 IAType spatialType = keyPairType.first;
                 boolean isPointMBR =
                         spatialType.getTypeTag() == ATypeTag.POINT || spatialType.getTypeTag() == ATypeTag.POINT3D;
@@ -485,7 +596,7 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                     // nullable.
                     boolean forceFilter = keyPairType.second;
                     filterExpression = createFilterExpression(keyVarList,
-                            context.getOutputTypeEnvironment(assignCoordinates), forceFilter, sourceLoc);
+                            context.getOutputTypeEnvironment(assignCoordinates), forceFilter);
                 }
                 DataSourceIndex dataSourceIndex = new DataSourceIndex(index, dataverseName, datasetName, mp);
                 indexUpdate = new IndexInsertDeleteUpsertOperator(dataSourceIndex,
@@ -532,7 +643,7 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                  * "blocking" sort operator since tuples are already sorted. We mark the materialization flag for that
                  * branch to make it blocking. Without "blocking", the activity cluster graph would be messed up
                  */
-                if (index.getKeyFieldNames().isEmpty() && index.getIndexType() == IndexType.BTREE) {
+                if (index.isPrimaryKeyIndex()) {
                     int positionOfSecondaryPrimaryIndex = replicateOp.getOutputs().size() - 1;
                     replicateOp.getOutputMaterializationFlags()[positionOfSecondaryPrimaryIndex] = true;
                 }
@@ -541,10 +652,6 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                 // For bulk load, we connect all fanned out insert operator to a single SINK operator
                 op0.getInputs().add(new MutableObject<ILogicalOperator>(indexUpdate));
             }
-
-        }
-        if (!hasSecondaryIndex) {
-            return false;
         }
 
         if (!primaryIndexModificationOp.isBulkload()) {
@@ -557,8 +664,103 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
         return true;
     }
 
-    private LogicalVariable getRecordVar(IOptimizationContext context, AbstractLogicalOperator inputOp,
-            ILogicalExpression recordExpr, int expectedRecordIndex) throws AlgebricksException {
+    private UnnestBranchCreator buildUnnestBranch(ILogicalOperator unnestSourceOp, Index index,
+            LogicalVariable recordVar, LogicalVariable metaVar, ARecordType recType, ARecordType metaType,
+            boolean hasMetaPart) throws AlgebricksException {
+        Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) index.getIndexDetails();
+
+        // First, locate a field having the required UNNEST path. Queue this first, and all other keys will follow.
+        Deque<Integer> keyPositionQueue = new ArrayDeque<>();
+        for (int i = 0; i < arrayIndexDetails.getElementList().size(); i++) {
+            Index.ArrayIndexElement e = arrayIndexDetails.getElementList().get(i);
+            if (e.getUnnestList().isEmpty()) {
+                keyPositionQueue.addLast(i);
+            } else {
+                keyPositionQueue.addFirst(i);
+            }
+        }
+
+        // Get the record variable associated with our record path.
+        Index.ArrayIndexElement workingElement = arrayIndexDetails.getElementList().get(keyPositionQueue.getFirst());
+        int sourceIndicatorForBaseRecord = workingElement.getSourceIndicator();
+        LogicalVariable sourceVarForBaseRecord = hasMetaPart
+                ? ((sourceIndicatorForBaseRecord == Index.RECORD_INDICATOR) ? recordVar : metaVar) : recordVar;
+        VariableReferenceExpression baseRecordVarRef = new VariableReferenceExpression(sourceVarForBaseRecord);
+        baseRecordVarRef.setSourceLocation(sourceLoc);
+        UnnestBranchCreator branchCreator = new UnnestBranchCreator(baseRecordVarRef, unnestSourceOp);
+
+        int initialKeyPositionQueueSize = keyPositionQueue.size();
+        Set<LogicalVariable> secondaryKeyVars = new HashSet<>();
+        for (int i = 0; i < initialKeyPositionQueueSize; i++) {
+
+            // Poll from our queue, and get a key position.
+            int workingKeyPos = keyPositionQueue.pollFirst();
+            workingElement = arrayIndexDetails.getElementList().get(workingKeyPos);
+            int sourceIndicator = workingElement.getSourceIndicator();
+            ARecordType recordType =
+                    hasMetaPart ? ((sourceIndicator == Index.RECORD_INDICATOR) ? recType : metaType) : recType;
+
+            boolean isOpenOrNestedField;
+            if (workingElement.getUnnestList().isEmpty()) {
+                // We have an atomic element (i.e. we have a composite array index).
+                List<String> atomicFieldName = workingElement.getProjectList().get(0);
+                isOpenOrNestedField =
+                        (atomicFieldName.size() != 1) || !recordType.isClosedField(atomicFieldName.get(0));
+
+                // The UNNEST path has already been created (we queued this first), so we look at the current top.
+                LogicalVariable newVar = context.newVar();
+                VariableReferenceExpression varRef = new VariableReferenceExpression(sourceVarForBaseRecord);
+                varRef.setSourceLocation(sourceLoc);
+                AbstractFunctionCallExpression newVarRef = (isOpenOrNestedField)
+                        ? getFieldAccessFunction(new MutableObject<>(varRef),
+                                recordType.getFieldIndex(atomicFieldName.get(0)), atomicFieldName)
+                        : getFieldAccessFunction(new MutableObject<>(varRef), -1, atomicFieldName);
+
+                AssignOperator newAssignOp = new AssignOperator(newVar, new MutableObject<>(newVarRef));
+                newAssignOp.setSourceLocation(sourceLoc);
+                branchCreator.currentTop = introduceNewOp(branchCreator.currentTop, newAssignOp, true);
+                secondaryKeyVars.add(newVar);
+
+            } else {
+                // We have an array element. The "open / nestedness" is determined by the first UNNEST field.
+                isOpenOrNestedField = workingElement.getUnnestList().get(0).size() > 1
+                        || !recordType.isClosedField(workingElement.getUnnestList().get(0).get(0));
+
+                // Walk the array path.
+                List<String> flatFirstFieldName = ArrayIndexUtil.getFlattenedKeyFieldNames(
+                        workingElement.getUnnestList(), workingElement.getProjectList().get(0));
+                List<Integer> firstArrayIndicator = ArrayIndexUtil
+                        .getArrayDepthIndicator(workingElement.getUnnestList(), workingElement.getProjectList().get(0));
+                ArrayIndexUtil.walkArrayPath((isOpenOrNestedField) ? null : recordType, flatFirstFieldName,
+                        firstArrayIndicator, branchCreator);
+
+                // For all other elements in the PROJECT list, add an assign.
+                for (int j = 1; j < workingElement.getProjectList().size(); j++) {
+                    LogicalVariable newVar = context.newVar();
+                    AbstractFunctionCallExpression newVarRef =
+                            getFieldAccessFunction(new MutableObject<>(branchCreator.lastRecordVarRef), -1,
+                                    workingElement.getProjectList().get(j));
+
+                    AssignOperator newAssignOp = new AssignOperator(newVar, new MutableObject<>(newVarRef));
+                    newAssignOp.setSourceLocation(sourceLoc);
+                    branchCreator.currentTop = introduceNewOp(branchCreator.currentTop, newAssignOp, true);
+                    secondaryKeyVars.add(newVar);
+                }
+            }
+
+            branchCreator.lowerIsFirstWalkFlag();
+            secondaryKeyVars.addAll(branchCreator.lastFieldVars);
+        }
+
+        // Update the variables we are to use for the head operators.
+        branchCreator.lastFieldVars.clear();
+        branchCreator.lastFieldVars.addAll(secondaryKeyVars);
+
+        return branchCreator;
+    }
+
+    private LogicalVariable getRecordVar(AbstractLogicalOperator inputOp, ILogicalExpression recordExpr,
+            int expectedRecordIndex) throws AlgebricksException {
         if (exprIsRecord(context.getOutputTypeEnvironment(inputOp), recordExpr)) {
             return ((VariableReferenceExpression) recordExpr).getVariableReference();
         } else {
@@ -598,21 +800,39 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
         return false;
     }
 
-    private ILogicalOperator injectFieldAccessesForIndexes(IOptimizationContext context, Dataset dataset,
-            List<Index> indexes, Map<IndexFieldId, LogicalVariable> fieldAccessVars, ARecordType recType,
-            ARecordType metaType, LogicalVariable recordVar, LogicalVariable metaVar, ILogicalOperator currentTop,
-            boolean afterOp) throws AlgebricksException {
+    private ILogicalOperator injectFieldAccessesForIndexes(Dataset dataset, List<Index> indexes,
+            Map<IndexFieldId, LogicalVariable> fieldAccessVars, ARecordType recType, ARecordType metaType,
+            LogicalVariable recordVar, LogicalVariable metaVar, ILogicalOperator currentTop, boolean afterOp)
+            throws AlgebricksException {
         List<LogicalVariable> vars = new ArrayList<>();
         List<Mutable<ILogicalExpression>> exprs = new ArrayList<>();
         SourceLocation sourceLoc = currentTop.getSourceLocation();
         for (Index index : indexes) {
-            if (index.isPrimaryIndex()) {
+            if (index.isPrimaryIndex() || index.getIndexType() == IndexType.ARRAY) {
+                // Array indexes require UNNESTs, which must be handled after the PIDX op.
                 continue;
             }
-            List<IAType> skTypes = index.getKeyFieldTypes();
-            List<List<String>> skNames = index.getKeyFieldNames();
-            List<Integer> indicators = index.getKeyFieldSourceIndicators();
-            for (int i = 0; i < index.getKeyFieldNames().size(); i++) {
+            List<List<String>> skNames;
+            List<IAType> skTypes;
+            List<Integer> indicators;
+            switch (Index.IndexCategory.of(index.getIndexType())) {
+                case VALUE:
+                    Index.ValueIndexDetails valueIndexDetails = (Index.ValueIndexDetails) index.getIndexDetails();
+                    skNames = valueIndexDetails.getKeyFieldNames();
+                    skTypes = valueIndexDetails.getKeyFieldTypes();
+                    indicators = valueIndexDetails.getKeyFieldSourceIndicators();
+                    break;
+                case TEXT:
+                    Index.TextIndexDetails textIndexDetails = (Index.TextIndexDetails) index.getIndexDetails();
+                    skNames = textIndexDetails.getKeyFieldNames();
+                    skTypes = textIndexDetails.getKeyFieldTypes();
+                    indicators = textIndexDetails.getKeyFieldSourceIndicators();
+                    break;
+                default:
+                    throw new CompilationException(ErrorCode.COMPILATION_UNKNOWN_INDEX_TYPE,
+                            String.valueOf(index.getIndexType()));
+            }
+            for (int i = 0; i < skNames.size(); i++) {
                 IndexFieldId indexFieldId =
                         new IndexFieldId(indicators.get(i), skNames.get(i), skTypes.get(i).getTypeTag());
                 if (fieldAccessVars.containsKey(indexFieldId)) {
@@ -634,8 +854,8 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                     // make handling of records with incorrect value type for this field easier and cleaner
                     context.addNotToBeInlinedVar(fieldVar);
                     // create field access
-                    AbstractFunctionCallExpression fieldAccessFunc = getOpenOrNestedFieldAccessFunction(
-                            new MutableObject<>(varRef), indexFieldId.fieldName, sourceLoc);
+                    AbstractFunctionCallExpression fieldAccessFunc =
+                            getFieldAccessFunction(new MutableObject<>(varRef), -1, indexFieldId.fieldName);
                     // create cast
                     theFieldAccessFunc = new ScalarFunctionCallExpression(FunctionUtil.getFunctionInfo(
                             index.isEnforced() ? BuiltinFunctions.CAST_TYPE : BuiltinFunctions.CAST_TYPE_LAX));
@@ -648,24 +868,25 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
                     int pos = indexFieldId.fieldName.size() > 1 ? -1
                             : sourceType.getFieldIndex(indexFieldId.fieldName.get(0));
                     // Field not found --> This is either an open field or a nested field. it can't be accessed by index
-                    theFieldAccessFunc = (pos == -1)
-                            ? getOpenOrNestedFieldAccessFunction(new MutableObject<>(varRef), indexFieldId.fieldName,
-                                    sourceLoc)
-                            : getClosedFieldAccessFunction(new MutableObject<>(varRef), pos, sourceLoc);
+                    theFieldAccessFunc =
+                            getFieldAccessFunction(new MutableObject<>(varRef), pos, indexFieldId.fieldName);
                 }
                 vars.add(fieldVar);
                 exprs.add(new MutableObject<ILogicalExpression>(theFieldAccessFunc));
                 fieldAccessVars.put(indexFieldId, fieldVar);
             }
         }
-        // AssignOperator assigns secondary keys to their vars
-        AssignOperator castedFieldAssignOperator = new AssignOperator(vars, exprs);
-        castedFieldAssignOperator.setSourceLocation(sourceLoc);
-        return introduceNewOp(context, currentTop, castedFieldAssignOperator, afterOp);
+        if (!vars.isEmpty()) {
+            // AssignOperator assigns secondary keys to their vars
+            AssignOperator castedFieldAssignOperator = new AssignOperator(vars, exprs);
+            castedFieldAssignOperator.setSourceLocation(sourceLoc);
+            return introduceNewOp(currentTop, castedFieldAssignOperator, afterOp);
+        }
+        return currentTop;
     }
 
-    private static ILogicalOperator introduceNewOp(IOptimizationContext context, ILogicalOperator currentTopOp,
-            ILogicalOperator newOp, boolean afterOp) throws AlgebricksException {
+    private ILogicalOperator introduceNewOp(ILogicalOperator currentTopOp, ILogicalOperator newOp, boolean afterOp)
+            throws AlgebricksException {
         if (afterOp) {
             newOp.getInputs().add(new MutableObject<>(currentTopOp));
             context.computeAndSetTypeEnvironmentForOperator(newOp);
@@ -680,34 +901,34 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
         }
     }
 
-    private static AbstractFunctionCallExpression getClosedFieldAccessFunction(Mutable<ILogicalExpression> varRef,
-            int position, SourceLocation sourceLoc) {
-        Mutable<ILogicalExpression> indexRef =
-                new MutableObject<>(new ConstantExpression(new AsterixConstantValue(new AInt32(position))));
-        ScalarFunctionCallExpression fnExpr = new ScalarFunctionCallExpression(
-                FunctionUtil.getFunctionInfo(BuiltinFunctions.FIELD_ACCESS_BY_INDEX), varRef, indexRef);
-        fnExpr.setSourceLocation(sourceLoc);
-        return fnExpr;
-    }
+    private AbstractFunctionCallExpression getFieldAccessFunction(Mutable<ILogicalExpression> varRef, int fieldPos,
+            List<String> fieldName) {
+        if (fieldName.size() == 1 && fieldPos != -1) {
+            Mutable<ILogicalExpression> indexRef =
+                    new MutableObject<>(new ConstantExpression(new AsterixConstantValue(new AInt32(fieldPos))));
+            ScalarFunctionCallExpression fnExpr = new ScalarFunctionCallExpression(
+                    FunctionUtil.getFunctionInfo(BuiltinFunctions.FIELD_ACCESS_BY_INDEX), varRef, indexRef);
+            fnExpr.setSourceLocation(sourceLoc);
+            return fnExpr;
 
-    private static AbstractFunctionCallExpression getOpenOrNestedFieldAccessFunction(Mutable<ILogicalExpression> varRef,
-            List<String> fields, SourceLocation sourceLoc) {
-        ScalarFunctionCallExpression func;
-        if (fields.size() > 1) {
-            IAObject fieldList = stringListToAOrderedList(fields);
-            Mutable<ILogicalExpression> fieldRef = constantToMutableLogicalExpression(fieldList);
-            // Create an expression for the nested case
-            func = new ScalarFunctionCallExpression(FunctionUtil.getFunctionInfo(BuiltinFunctions.FIELD_ACCESS_NESTED),
-                    varRef, fieldRef);
         } else {
-            IAObject fieldList = new AString(fields.get(0));
-            Mutable<ILogicalExpression> fieldRef = constantToMutableLogicalExpression(fieldList);
-            // Create an expression for the open field case (By name)
-            func = new ScalarFunctionCallExpression(FunctionUtil.getFunctionInfo(BuiltinFunctions.FIELD_ACCESS_BY_NAME),
-                    varRef, fieldRef);
-        }
-        func.setSourceLocation(sourceLoc);
-        return func;
+            ScalarFunctionCallExpression func;
+            if (fieldName.size() > 1) {
+                IAObject fieldList = stringListToAOrderedList(fieldName);
+                Mutable<ILogicalExpression> fieldRef = constantToMutableLogicalExpression(fieldList);
+                // Create an expression for the nested case
+                func = new ScalarFunctionCallExpression(
+                        FunctionUtil.getFunctionInfo(BuiltinFunctions.FIELD_ACCESS_NESTED), varRef, fieldRef);
+            } else {
+                IAObject fieldList = new AString(fieldName.get(0));
+                Mutable<ILogicalExpression> fieldRef = constantToMutableLogicalExpression(fieldList);
+                // Create an expression for the open field case (By name)
+                func = new ScalarFunctionCallExpression(
+                        FunctionUtil.getFunctionInfo(BuiltinFunctions.FIELD_ACCESS_BY_NAME), varRef, fieldRef);
+            }
+            func.setSourceLocation(sourceLoc);
+            return func;
+        }
     }
 
     private static AOrderedList stringListToAOrderedList(List<String> fields) {
@@ -723,8 +944,7 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
     }
 
     private Mutable<ILogicalExpression> createFilterExpression(List<LogicalVariable> secondaryKeyVars,
-            IVariableTypeEnvironment typeEnv, boolean forceFilter, SourceLocation sourceLoc)
-            throws AlgebricksException {
+            IVariableTypeEnvironment typeEnv, boolean forceFilter) throws AlgebricksException {
         List<Mutable<ILogicalExpression>> filterExpressions = new ArrayList<>();
         // Add 'is not null' to all nullable secondary index keys as a filtering
         // condition.
@@ -762,6 +982,137 @@ public class IntroduceSecondaryIndexInsertDeleteRule implements IAlgebraicRewrit
         return filterExpression;
     }
 
+    /**
+     * Builds the nested plan required for array index maintenance.
+     */
+    private class UnnestBranchCreator implements ArrayIndexUtil.TypeTrackerCommandExecutor {
+        private final List<LogicalVariable> lastFieldVars;
+        private VariableReferenceExpression lastRecordVarRef;
+        private ILogicalOperator currentTop, currentBottom;
+        private boolean isFirstWalk = true;
+
+        public UnnestBranchCreator(VariableReferenceExpression recordVarRef, ILogicalOperator sourceOperator) {
+            this.lastRecordVarRef = recordVarRef;
+            this.currentTop = sourceOperator;
+            this.lastFieldVars = new ArrayList<>();
+        }
+
+        public ILogicalPlan buildBranch() {
+            return new ALogicalPlanImpl(new MutableObject<>(currentTop));
+        }
+
+        public void lowerIsFirstWalkFlag() {
+            isFirstWalk = false;
+        }
+
+        @SafeVarargs
+        public final void applyProjectDistinct(List<Mutable<ILogicalExpression>>... auxiliaryExpressions)
+                throws AlgebricksException {
+            // Apply the following: PROJECT(SK, AK) --> (ORDER (SK, AK)) implicitly --> DISTINCT (SK, AK) .
+            List<LogicalVariable> projectVars = new ArrayList<>(this.lastFieldVars);
+            List<Mutable<ILogicalExpression>> distinctVarRefs =
+                    OperatorManipulationUtil.createVariableReferences(projectVars, sourceLoc);
+
+            // If we have any additional expressions to be added to our index, append them here.
+            if (auxiliaryExpressions.length > 0) {
+                for (List<Mutable<ILogicalExpression>> exprList : auxiliaryExpressions) {
+                    if (exprList != null) {
+                        // Sanity check: ensure that we only have variable references.
+                        if (exprList.stream().anyMatch(
+                                e -> !e.getValue().getExpressionTag().equals(LogicalExpressionTag.VARIABLE))) {
+                            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc,
+                                    "Given auxiliary expression list contains non-variable reference expressions. We"
+                                            + " cannot apply DISTINCT to this expression at this stage.");
+                        }
+
+                        distinctVarRefs.addAll(OperatorManipulationUtil.cloneExpressions(exprList));
+                        for (Mutable<ILogicalExpression> e : OperatorManipulationUtil.cloneExpressions(exprList)) {
+                            projectVars.add(((VariableReferenceExpression) e.getValue()).getVariableReference());
+                        }
+                    }
+                }
+            }
+
+            ProjectOperator projectOperator = new ProjectOperator(projectVars);
+            projectOperator.setSourceLocation(sourceLoc);
+            this.currentTop = introduceNewOp(currentTop, projectOperator, true);
+            DistinctOperator distinctOperator = new DistinctOperator(distinctVarRefs);
+            distinctOperator.setSourceLocation(sourceLoc);
+            this.currentTop = introduceNewOp(currentTop, distinctOperator, true);
+        }
+
+        public void applyFilteringExpression(Mutable<ILogicalExpression> filterExpression) throws AlgebricksException {
+            SelectOperator selectOperator = new SelectOperator(filterExpression, false, null);
+            selectOperator.setSourceLocation(sourceLoc);
+            this.currentTop = introduceNewOp(currentTop, selectOperator, true);
+        }
+
+        @Override
+        public void executeActionOnEachArrayStep(ARecordType startingStepRecordType, IAType workingType,
+                List<String> fieldName, boolean isFirstArrayStep, boolean isFirstUnnestInStep,
+                boolean isLastUnnestInIntermediateStep) throws AlgebricksException {
+            if (!isFirstWalk) {
+                // We have already built the UNNEST path, do not build again.
+                return;
+            }
+
+            ILogicalExpression accessToUnnestVar;
+            if (isFirstUnnestInStep) {
+                // This is the first UNNEST step. Get the field we want to UNNEST from our record.
+                accessToUnnestVar = (startingStepRecordType != null)
+                        ? getFieldAccessFunction(new MutableObject<>(lastRecordVarRef),
+                                startingStepRecordType.getFieldIndex(fieldName.get(0)), fieldName)
+                        : getFieldAccessFunction(new MutableObject<>(lastRecordVarRef), -1, fieldName);
+            } else {
+                // This is the second+ UNNEST step. Refer back to the previously unnested variable.
+                accessToUnnestVar = new VariableReferenceExpression(this.lastFieldVars.get(0));
+                this.lastFieldVars.clear();
+            }
+            UnnestingFunctionCallExpression scanCollection = new UnnestingFunctionCallExpression(
+                    BuiltinFunctions.getBuiltinFunctionInfo(BuiltinFunctions.SCAN_COLLECTION),
+                    Collections.singletonList(new MutableObject<>(accessToUnnestVar)));
+            scanCollection.setReturnsUniqueValues(false);
+            scanCollection.setSourceLocation(sourceLoc);
+            LogicalVariable unnestVar = context.newVar();
+            this.lastFieldVars.add(unnestVar);
+
+            UnnestOperator unnestOp = new UnnestOperator(unnestVar, new MutableObject<>(scanCollection));
+            unnestOp.setSourceLocation(sourceLoc);
+            this.currentTop = introduceNewOp(currentTop, unnestOp, true);
+            if (isFirstArrayStep) {
+                this.currentBottom = unnestOp;
+            }
+
+            if (isLastUnnestInIntermediateStep) {
+                // This is the last UNNEST before the next array step. Update our record variable.
+                this.lastRecordVarRef = new VariableReferenceExpression(unnestVar);
+                this.lastRecordVarRef.setSourceLocation(sourceLoc);
+                this.lastFieldVars.clear();
+            }
+        }
+
+        @Override
+        public void executeActionOnFinalArrayStep(ARecordType startingStepRecordType, List<String> fieldName,
+                boolean isNonArrayStep, boolean requiresOnlyOneUnnest) throws AlgebricksException {
+            // If the final value is nested inside a record, add an additional ASSIGN.
+            if (!isNonArrayStep) {
+                return;
+            }
+
+            // Create the function to access our final field.
+            AbstractFunctionCallExpression accessToFinalVar = (startingStepRecordType != null)
+                    ? getFieldAccessFunction(new MutableObject<>(lastRecordVarRef),
+                            startingStepRecordType.getFieldIndex(fieldName.get(0)), fieldName)
+                    : getFieldAccessFunction(new MutableObject<>(lastRecordVarRef), -1, fieldName);
+
+            LogicalVariable finalVar = context.newVar();
+            this.lastFieldVars.add(finalVar);
+            AssignOperator assignOperator = new AssignOperator(finalVar, new MutableObject<>(accessToFinalVar));
+            assignOperator.setSourceLocation(sourceLoc);
+            this.currentTop = introduceNewOp(currentTop, assignOperator, true);
+        }
+    }
+
     private final class IndexFieldId {
         private final int indicator;
         private final List<String> fieldName;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
index 1946f82..76b57ef 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java
@@ -19,9 +19,7 @@
 package org.apache.asterix.optimizer.rules.am;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -38,20 +36,15 @@ import org.apache.asterix.dataflow.data.common.ExpressionTypeComputer;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.metadata.utils.ArrayIndexUtil;
 import org.apache.asterix.metadata.utils.DatasetUtil;
 import org.apache.asterix.metadata.utils.MetadataUtil;
-import org.apache.asterix.om.base.AOrderedList;
-import org.apache.asterix.om.base.AString;
-import org.apache.asterix.om.constants.AsterixConstantValue;
 import org.apache.asterix.om.functions.BuiltinFunctions;
-import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
-import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.om.types.ATypeTag;
 import org.apache.asterix.om.types.AbstractCollectionType;
 import org.apache.asterix.om.types.BuiltinType;
 import org.apache.asterix.om.types.IAType;
 import org.apache.asterix.om.types.hierachy.ATypeHierarchy;
-import org.apache.asterix.om.utils.ConstantExpressionUtil;
 import org.apache.asterix.optimizer.base.AnalysisUtil;
 import org.apache.asterix.optimizer.rules.am.OptimizableOperatorSubTree.DataSourceType;
 import org.apache.asterix.optimizer.rules.util.FullTextUtil;
@@ -59,6 +52,7 @@ import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.commons.lang3.mutable.MutableInt;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
@@ -66,8 +60,6 @@ import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractLogicalExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
 import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
 import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
@@ -83,7 +75,6 @@ import org.apache.hyracks.algebricks.core.algebra.typing.ITypingContext;
 import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableSet;
 
 /**
  * Class that embodies the commonalities between rewrite rules for access
@@ -93,15 +84,6 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
 
     protected MetadataProvider metadataProvider;
 
-    // Function Identifier sets that retain the original field variable through each function's arguments
-    private final ImmutableSet<FunctionIdentifier> funcIDSetThatRetainFieldName =
-            ImmutableSet.of(BuiltinFunctions.WORD_TOKENS, BuiltinFunctions.GRAM_TOKENS, BuiltinFunctions.SUBSTRING,
-                    BuiltinFunctions.SUBSTRING_BEFORE, BuiltinFunctions.SUBSTRING_AFTER,
-                    BuiltinFunctions.CREATE_POLYGON, BuiltinFunctions.CREATE_MBR, BuiltinFunctions.CREATE_RECTANGLE,
-                    BuiltinFunctions.CREATE_CIRCLE, BuiltinFunctions.CREATE_LINE, BuiltinFunctions.CREATE_POINT,
-                    BuiltinFunctions.NUMERIC_ADD, BuiltinFunctions.NUMERIC_SUBTRACT, BuiltinFunctions.NUMERIC_MULTIPLY,
-                    BuiltinFunctions.NUMERIC_DIVIDE, BuiltinFunctions.NUMERIC_DIV, BuiltinFunctions.NUMERIC_MOD);
-
     public abstract Map<FunctionIdentifier, List<IAccessMethod>> getAccessMethods();
 
     protected static void registerAccessMethod(IAccessMethod accessMethod,
@@ -243,6 +225,7 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
                 boolean isNgramIndexChosen = indexType == IndexType.LENGTH_PARTITIONED_NGRAM_INVIX
                         || indexType == IndexType.SINGLE_PARTITION_NGRAM_INVIX;
                 if ((chosenAccessMethod == BTreeAccessMethod.INSTANCE && indexType == IndexType.BTREE)
+                        || (chosenAccessMethod == ArrayBTreeAccessMethod.INSTANCE && indexType == IndexType.ARRAY)
                         || (chosenAccessMethod == RTreeAccessMethod.INSTANCE && indexType == IndexType.RTREE)
                         // the inverted index will be utilized
                         // For Ngram, the full-text config used in the index and in the query are always the default one,
@@ -263,8 +246,7 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
                         // 1) the full-text ftcontains() function
                         // 2) functions that take keyword as an argument, e.g. edit_distance_check() when the threshold is 1
                         || (chosenAccessMethod == InvertedIndexAccessMethod.INSTANCE && isKeywordIndexChosen
-                                && isSameFullTextConfigInIndexAndQuery(analysisCtx,
-                                        chosenIndex.getFullTextConfigName()))) {
+                                && isSameFullTextConfigInIndexAndQuery(analysisCtx, chosenIndex.getIndexDetails()))) {
 
                     if (resultVarsToIndexTypesMap.containsKey(indexEntry.getValue())) {
                         List<IndexType> appliedIndexTypes = resultVarsToIndexTypesMap.get(indexEntry.getValue());
@@ -285,7 +267,9 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
     }
 
     private boolean isSameFullTextConfigInIndexAndQuery(AccessMethodAnalysisContext analysisCtx,
-            String indexFullTextConfig) {
+            Index.IIndexDetails indexDetails) {
+        String indexFullTextConfig = ((Index.TextIndexDetails) indexDetails).getFullTextConfigName();
+
         IOptimizableFuncExpr expr = analysisCtx.getMatchedFuncExpr(0);
         if (FullTextUtil.isFullTextContainsFunctionExpr(expr)) {
             // ftcontains()
@@ -327,6 +311,39 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
         while (indexExprAndVarIt.hasNext()) {
             Map.Entry<Index, List<Pair<Integer, Integer>>> indexExprAndVarEntry = indexExprAndVarIt.next();
             Index index = indexExprAndVarEntry.getKey();
+            IndexType indexType = index.getIndexType();
+            if (!accessMethod.matchIndexType(indexType)) {
+                indexExprAndVarIt.remove();
+                continue;
+            }
+            List<List<String>> keyFieldNames;
+            List<IAType> keyFieldTypes;
+            switch (Index.IndexCategory.of(indexType)) {
+                case ARRAY:
+                    Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) index.getIndexDetails();
+                    keyFieldNames = new ArrayList<>();
+                    keyFieldTypes = new ArrayList<>();
+                    for (Index.ArrayIndexElement e : arrayIndexDetails.getElementList()) {
+                        for (int i = 0; i < e.getProjectList().size(); i++) {
+                            List<String> project = e.getProjectList().get(i);
+                            keyFieldNames.add(ArrayIndexUtil.getFlattenedKeyFieldNames(e.getUnnestList(), project));
+                            keyFieldTypes.add(e.getTypeList().get(i));
+                        }
+                    }
+                    break;
+                case VALUE:
+                    Index.ValueIndexDetails valueIndexDetails = (Index.ValueIndexDetails) index.getIndexDetails();
+                    keyFieldNames = valueIndexDetails.getKeyFieldNames();
+                    keyFieldTypes = valueIndexDetails.getKeyFieldTypes();
+                    break;
+                case TEXT:
+                    Index.TextIndexDetails textIndexDetails = (Index.TextIndexDetails) index.getIndexDetails();
+                    keyFieldNames = textIndexDetails.getKeyFieldNames();
+                    keyFieldTypes = textIndexDetails.getKeyFieldTypes();
+                    break;
+                default:
+                    throw new CompilationException(ErrorCode.COMPILATION_UNKNOWN_INDEX_TYPE, String.valueOf(indexType));
+            }
 
             boolean allUsed = true;
             int lastFieldMatched = -1;
@@ -334,9 +351,9 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
             // Used to keep track of matched expressions (added for prefix search)
             int numMatchedKeys = 0;
 
-            for (int i = 0; i < index.getKeyFieldNames().size(); i++) {
-                List<String> keyField = index.getKeyFieldNames().get(i);
-                final IAType keyType = index.getKeyFieldTypes().get(i);
+            for (int i = 0; i < keyFieldNames.size(); i++) {
+                List<String> keyField = keyFieldNames.get(i);
+                final IAType keyType = keyFieldTypes.get(i);
                 boolean foundKeyField = false;
                 Iterator<Pair<Integer, Integer>> exprsAndVarIter = indexExprAndVarEntry.getValue().iterator();
                 while (exprsAndVarIter.hasNext()) {
@@ -631,15 +648,52 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
         List<Index> indexCandidates = new ArrayList<>();
         // Add an index to the candidates if one of the indexed fields is fieldName
         for (Index index : datasetIndexes) {
+            List<List<String>> keyFieldNames;
+            List<IAType> keyFieldTypes;
+            List<Integer> keySources;
+            boolean isOverridingKeyFieldTypes;
+            switch (Index.IndexCategory.of(index.getIndexType())) {
+                case ARRAY:
+                    Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) index.getIndexDetails();
+                    keyFieldNames = new ArrayList<>();
+                    keyFieldTypes = new ArrayList<>();
+                    keySources = new ArrayList<>();
+                    for (Index.ArrayIndexElement e : arrayIndexDetails.getElementList()) {
+                        for (int i = 0; i < e.getProjectList().size(); i++) {
+                            List<String> project = e.getProjectList().get(i);
+                            keyFieldNames.add(ArrayIndexUtil.getFlattenedKeyFieldNames(e.getUnnestList(), project));
+                            keyFieldTypes.add(e.getTypeList().get(i).getType());
+                            keySources.add(e.getSourceIndicator());
+                        }
+                    }
+                    isOverridingKeyFieldTypes = arrayIndexDetails.isOverridingKeyFieldTypes();
+                    break;
+                case VALUE:
+                    Index.ValueIndexDetails valueIndexDetails = (Index.ValueIndexDetails) index.getIndexDetails();
+                    keyFieldNames = valueIndexDetails.getKeyFieldNames();
+                    keyFieldTypes = valueIndexDetails.getKeyFieldTypes();
+                    keySources = valueIndexDetails.getKeyFieldSourceIndicators();
+                    isOverridingKeyFieldTypes = valueIndexDetails.isOverridingKeyFieldTypes();
+                    break;
+                case TEXT:
+                    Index.TextIndexDetails textIndexDetails = (Index.TextIndexDetails) index.getIndexDetails();
+                    keyFieldNames = textIndexDetails.getKeyFieldNames();
+                    keyFieldTypes = textIndexDetails.getKeyFieldTypes();
+                    keySources = textIndexDetails.getKeyFieldSourceIndicators();
+                    isOverridingKeyFieldTypes = textIndexDetails.isOverridingKeyFieldTypes();
+                    break;
+                default:
+                    throw new CompilationException(ErrorCode.COMPILATION_UNKNOWN_INDEX_TYPE,
+                            String.valueOf(index.getIndexType()));
+            }
             // Need to also verify the index is pending no op
-            int keyIdx = index.getKeyFieldNames().indexOf(fieldName);
-            List<Integer> keySources = index.getKeyFieldSourceIndicators();
+            int keyIdx = keyFieldNames.indexOf(fieldName);
             if (keyIdx >= 0 && keySourceMatches(keySources, keyIdx, fieldSource)
                     && index.getPendingOp() == MetadataUtil.PENDING_NO_OP) {
                 indexCandidates.add(index);
                 boolean isFieldTypeUnknown = fieldType == BuiltinType.AMISSING || fieldType == BuiltinType.ANY;
-                if (isFieldTypeUnknown && (!index.isOverridingKeyFieldTypes() || index.isEnforced())) {
-                    IAType indexedType = index.getKeyFieldTypes().get(keyIdx);
+                if (isFieldTypeUnknown && (!isOverridingKeyFieldTypes || index.isEnforced())) {
+                    IAType indexedType = keyFieldTypes.get(keyIdx);
                     optFuncExpr.setFieldType(varIdx, indexedType);
                 }
                 analysisCtx.addIndexExpr(matchedSubTree.getDataset(), index, matchedFuncExprIndex, varIdx);
@@ -731,9 +785,13 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
             varRef.setSourceLocation(unnestOp.getSourceLocation());
             optFuncExpr.setLogicalExpr(funcVarIndex, varRef);
         } else {
-            fieldName = getFieldNameFromSubTree(optFuncExpr, subTree, assignOrUnnestIndex, 0, subTree.getRecordType(),
-                    funcVarIndex, optFuncExpr.getFuncExpr().getArguments().get(funcVarIndex).getValue(),
-                    subTree.getMetaRecordType(), datasetMetaVar, fieldSource);
+            if (subTree.getDataSourceType() == DataSourceType.DATASOURCE_SCAN) {
+                subTree.setLastMatchedDataSourceVars(0, funcVarIndex);
+            }
+            fieldName = AccessMethodUtils.getFieldNameFromSubTree(optFuncExpr, subTree, assignOrUnnestIndex, 0,
+                    subTree.getRecordType(), funcVarIndex,
+                    optFuncExpr.getFuncExpr().getArguments().get(funcVarIndex).getValue(), subTree.getMetaRecordType(),
+                    datasetMetaVar, fieldSource, false);
             if (fieldName.isEmpty()) {
                 return;
             }
@@ -756,25 +814,40 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
             OptimizableOperatorSubTree subTree, int assignOrUnnestIndex, LogicalVariable datasetMetaVar,
             IOptimizationContext context, List<Index> datasetIndexes, int optFuncExprIndex,
             AccessMethodAnalysisContext analysisCtx) throws AlgebricksException {
+        boolean doesArrayIndexQualify = context.getPhysicalOptimizationConfig().isArrayIndexEnabled()
+                && datasetIndexes.stream().anyMatch(i -> i.getIndexType() == IndexType.ARRAY)
+                && assignOrUnnestIndex == subTree.getAssignsAndUnnests().size() - 1;
         List<LogicalVariable> varList = assignOp.getVariables();
         MutableInt fieldSource = new MutableInt(0);
         for (int varIndex = 0; varIndex < varList.size(); varIndex++) {
             LogicalVariable var = varList.get(varIndex);
             int optVarIndex = optFuncExpr.findLogicalVar(var);
-            // No matching var in optFuncExpr.
             if (optVarIndex == -1) {
+                if (doesArrayIndexQualify && subTree.getDataSourceType() == DataSourceType.DATASOURCE_SCAN) {
+                    // We may be able to apply an array index to this variable.
+                    Triple<Integer, List<String>, IAType> fieldTriplet =
+                            AccessMethodUtils.analyzeVarForArrayIndexes(assignOp, optFuncExpr, subTree, datasetMetaVar,
+                                    context, datasetIndexes, analysisCtx.getMatchedFuncExprs(), varIndex);
+                    if (fieldTriplet != null && subTree.hasDataSource()) {
+                        fillIndexExprs(datasetIndexes, fieldTriplet.second, fieldTriplet.third, optFuncExpr,
+                                optFuncExprIndex, fieldTriplet.first, subTree, analysisCtx, fieldSource.intValue());
+                    }
+                }
                 continue;
             }
             // At this point we have matched the optimizable func
             // expr at optFuncExprIndex to an assigned variable.
             // Remember matching subtree.
             optFuncExpr.setOptimizableSubTree(optVarIndex, subTree);
+            if (subTree.getDataSourceType() == DataSourceType.DATASOURCE_SCAN) {
+                subTree.setLastMatchedDataSourceVars(varIndex, optVarIndex);
+            }
 
             fieldSource.setValue(0);
-            List<String> fieldName = getFieldNameFromSubTree(optFuncExpr, subTree, assignOrUnnestIndex, varIndex,
-                    subTree.getRecordType(), optVarIndex,
+            List<String> fieldName = AccessMethodUtils.getFieldNameFromSubTree(optFuncExpr, subTree,
+                    assignOrUnnestIndex, varIndex, subTree.getRecordType(), optVarIndex,
                     optFuncExpr.getFuncExpr().getArguments().get(optVarIndex).getValue(), subTree.getMetaRecordType(),
-                    datasetMetaVar, fieldSource);
+                    datasetMetaVar, fieldSource, false);
 
             IAType fieldType = (IAType) context.getOutputTypeEnvironment(assignOp).getVarType(var);
             // Set the fieldName in the corresponding matched
@@ -870,220 +943,6 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
     }
 
     /**
-     * Returns the field name corresponding to the assigned variable at
-     * varIndex. Returns Collections.emptyList() if the expr at varIndex does not yield to a field
-     * access function after following a set of allowed functions.
-     *
-     * @throws AlgebricksException
-     */
-    protected List<String> getFieldNameFromSubTree(IOptimizableFuncExpr optFuncExpr, OptimizableOperatorSubTree subTree,
-            int opIndex, int assignVarIndex, ARecordType recordType, int funcVarIndex,
-            ILogicalExpression parentFuncExpr, ARecordType metaType, LogicalVariable metaVar, MutableInt fieldSource)
-            throws AlgebricksException {
-        // Get expression corresponding to opVar at varIndex.
-        AbstractLogicalExpression expr = null;
-        AbstractFunctionCallExpression childFuncExpr = null;
-        AbstractLogicalOperator op = subTree.getAssignsAndUnnests().get(opIndex);
-        if (op.getOperatorTag() == LogicalOperatorTag.ASSIGN) {
-            AssignOperator assignOp = (AssignOperator) op;
-            expr = (AbstractLogicalExpression) assignOp.getExpressions().get(assignVarIndex).getValue();
-            // Can't get a field name from a constant expression. So, return null.
-            if (expr.getExpressionTag() == LogicalExpressionTag.CONSTANT) {
-                return Collections.emptyList();
-            }
-            childFuncExpr = (AbstractFunctionCallExpression) expr;
-        } else {
-            UnnestOperator unnestOp = (UnnestOperator) op;
-            expr = (AbstractLogicalExpression) unnestOp.getExpressionRef().getValue();
-            if (expr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
-                return Collections.emptyList();
-            }
-            childFuncExpr = (AbstractFunctionCallExpression) expr;
-            if (childFuncExpr.getFunctionIdentifier() != BuiltinFunctions.SCAN_COLLECTION) {
-                return Collections.emptyList();
-            }
-            expr = (AbstractLogicalExpression) childFuncExpr.getArguments().get(0).getValue();
-        }
-        if (expr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
-            return Collections.emptyList();
-        }
-        AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) expr;
-        FunctionIdentifier funcIdent = funcExpr.getFunctionIdentifier();
-
-        boolean isByName = false;
-        boolean isFieldAccess = false;
-        String fieldName = null;
-        List<String> nestedAccessFieldName = null;
-        int fieldIndex = -1;
-        if (funcIdent == BuiltinFunctions.FIELD_ACCESS_BY_NAME) {
-            fieldName = ConstantExpressionUtil.getStringArgument(funcExpr, 1);
-            if (fieldName == null) {
-                return Collections.emptyList();
-            }
-            isFieldAccess = true;
-            isByName = true;
-        } else if (funcIdent == BuiltinFunctions.FIELD_ACCESS_BY_INDEX) {
-            Integer idx = ConstantExpressionUtil.getIntArgument(funcExpr, 1);
-            if (idx == null) {
-                return Collections.emptyList();
-            }
-            fieldIndex = idx;
-            isFieldAccess = true;
-        } else if (funcIdent == BuiltinFunctions.FIELD_ACCESS_NESTED) {
-            ILogicalExpression nameArg = funcExpr.getArguments().get(1).getValue();
-            if (nameArg.getExpressionTag() != LogicalExpressionTag.CONSTANT) {
-                return Collections.emptyList();
-            }
-            ConstantExpression constExpr = (ConstantExpression) nameArg;
-            AOrderedList orderedNestedFieldName =
-                    (AOrderedList) ((AsterixConstantValue) constExpr.getValue()).getObject();
-            nestedAccessFieldName = new ArrayList<>();
-            for (int i = 0; i < orderedNestedFieldName.size(); i++) {
-                nestedAccessFieldName.add(((AString) orderedNestedFieldName.getItem(i)).getStringValue());
-            }
-            isFieldAccess = true;
-            isByName = true;
-        }
-        if (isFieldAccess) {
-            LogicalVariable sourceVar =
-                    ((VariableReferenceExpression) funcExpr.getArguments().get(0).getValue()).getVariableReference();
-            if (sourceVar.equals(metaVar)) {
-                fieldSource.setValue(1);
-            } else {
-                fieldSource.setValue(0);
-            }
-            if (optFuncExpr != null) {
-                optFuncExpr.setLogicalExpr(funcVarIndex, parentFuncExpr);
-            }
-            int[] assignAndExpressionIndexes = null;
-
-            //go forward through nested assigns until you find the relevant one
-            for (int i = opIndex + 1; i < subTree.getAssignsAndUnnests().size(); i++) {
-                AbstractLogicalOperator subOp = subTree.getAssignsAndUnnests().get(i);
-                List<LogicalVariable> varList;
-
-                if (subOp.getOperatorTag() == LogicalOperatorTag.ASSIGN) {
-                    //Nested was an assign
-                    varList = ((AssignOperator) subOp).getVariables();
-                } else if (subOp.getOperatorTag() == LogicalOperatorTag.UNNEST) {
-                    //Nested is not an assign
-                    varList = ((UnnestOperator) subOp).getVariables();
-                } else {
-                    break;
-                }
-
-                //Go through variables in assign to check for match
-                for (int varIndex = 0; varIndex < varList.size(); varIndex++) {
-                    LogicalVariable var = varList.get(varIndex);
-                    ArrayList<LogicalVariable> parentVars = new ArrayList<>();
-                    expr.getUsedVariables(parentVars);
-
-                    if (parentVars.contains(var)) {
-                        //Found the variable we are looking for.
-                        //return assign and index of expression
-                        int[] returnValues = { i, varIndex };
-                        assignAndExpressionIndexes = returnValues;
-                    }
-                }
-            }
-            if (assignAndExpressionIndexes != null && assignAndExpressionIndexes[0] > -1) {
-                //We found the nested assign
-
-                //Recursive call on nested assign
-                List<String> parentFieldNames = getFieldNameFromSubTree(optFuncExpr, subTree,
-                        assignAndExpressionIndexes[0], assignAndExpressionIndexes[1], recordType, funcVarIndex,
-                        parentFuncExpr, metaType, metaVar, fieldSource);
-
-                if (parentFieldNames.isEmpty()) {
-                    //Nested assign was not a field access.
-                    //We will not use index
-                    return Collections.emptyList();
-                }
-
-                if (!isByName) {
-                    IAType subFieldType = sourceVar.equals(metaVar) ? metaType.getSubFieldType(parentFieldNames)
-                            : recordType.getSubFieldType(parentFieldNames);
-                    // Sub-field type can be AUnionType in case if it's optional. Thus, needs to get the actual type.
-                    subFieldType = TypeComputeUtils.getActualType(subFieldType);
-                    if (subFieldType.getTypeTag() != ATypeTag.OBJECT) {
-                        throw CompilationException.create(ErrorCode.TYPE_CONVERT, subFieldType,
-                                ARecordType.class.getName());
-                    }
-                    fieldName = ((ARecordType) subFieldType).getFieldNames()[fieldIndex];
-                }
-                if (optFuncExpr != null) {
-                    optFuncExpr.setSourceVar(funcVarIndex, ((AssignOperator) op).getVariables().get(assignVarIndex));
-                }
-                //add fieldName to the nested fieldName, return
-                if (nestedAccessFieldName != null) {
-                    for (int i = 0; i < nestedAccessFieldName.size(); i++) {
-                        parentFieldNames.add(nestedAccessFieldName.get(i));
-                    }
-                } else {
-                    parentFieldNames.add(fieldName);
-                }
-                return (parentFieldNames);
-            }
-
-            if (optFuncExpr != null) {
-                optFuncExpr.setSourceVar(funcVarIndex, ((AssignOperator) op).getVariables().get(assignVarIndex));
-            }
-            //no nested assign, we are at the lowest level.
-            if (isByName) {
-                if (nestedAccessFieldName != null) {
-                    return nestedAccessFieldName;
-                }
-                return new ArrayList<>(Arrays.asList(fieldName));
-            }
-            return new ArrayList<>(Arrays.asList(sourceVar.equals(metaVar) ? metaType.getFieldNames()[fieldIndex]
-                    : recordType.getFieldNames()[fieldIndex]));
-
-        }
-
-        if (!funcIDSetThatRetainFieldName.contains(funcIdent)) {
-            return Collections.emptyList();
-        }
-        // We use a part of the field in edit distance computation
-        if (optFuncExpr != null
-                && optFuncExpr.getFuncExpr().getFunctionIdentifier() == BuiltinFunctions.EDIT_DISTANCE_CHECK) {
-            optFuncExpr.setPartialField(true);
-        }
-        // We expect the function's argument to be a variable, otherwise we
-        // cannot apply an index.
-        ILogicalExpression argExpr = funcExpr.getArguments().get(0).getValue();
-        if (argExpr.getExpressionTag() != LogicalExpressionTag.VARIABLE) {
-            return Collections.emptyList();
-        }
-        LogicalVariable curVar = ((VariableReferenceExpression) argExpr).getVariableReference();
-        // We look for the assign or unnest operator that produces curVar below
-        // the current operator
-        for (int assignOrUnnestIndex = opIndex + 1; assignOrUnnestIndex < subTree.getAssignsAndUnnests()
-                .size(); assignOrUnnestIndex++) {
-            AbstractLogicalOperator curOp = subTree.getAssignsAndUnnests().get(assignOrUnnestIndex);
-            if (curOp.getOperatorTag() == LogicalOperatorTag.ASSIGN) {
-                AssignOperator assignOp = (AssignOperator) curOp;
-                List<LogicalVariable> varList = assignOp.getVariables();
-                for (int varIndex = 0; varIndex < varList.size(); varIndex++) {
-                    LogicalVariable var = varList.get(varIndex);
-                    if (var.equals(curVar) && optFuncExpr != null) {
-                        optFuncExpr.setSourceVar(funcVarIndex, var);
-                        return getFieldNameFromSubTree(optFuncExpr, subTree, assignOrUnnestIndex, varIndex, recordType,
-                                funcVarIndex, childFuncExpr, metaType, metaVar, fieldSource);
-                    }
-                }
-            } else {
-                UnnestOperator unnestOp = (UnnestOperator) curOp;
-                LogicalVariable var = unnestOp.getVariable();
-                if (var.equals(curVar)) {
-                    getFieldNameFromSubTree(optFuncExpr, subTree, assignOrUnnestIndex, 0, recordType, funcVarIndex,
-                            childFuncExpr, metaType, metaVar, fieldSource);
-                }
-            }
-        }
-        return Collections.emptyList();
-    }
-
-    /**
      * Finds the field name of each variable in the ASSIGN or UNNEST operators of the sub-tree.
      */
     protected void fillFieldNamesInTheSubTree(OptimizableOperatorSubTree subTree) throws AlgebricksException {
@@ -1107,9 +966,9 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
                     // funcVarIndex is not required. Thus, we set it to -1.
                     // optFuncExpr and parentFuncExpr are not required, too. Thus, we set them to null.
                     fieldSource.setValue(0);
-                    List<String> fieldName = getFieldNameFromSubTree(null, subTree, assignOrUnnestIndex, varIndex,
-                            subTree.getRecordType(), -1, null, subTree.getMetaRecordType(), datasetMetaVar,
-                            fieldSource);
+                    List<String> fieldName = AccessMethodUtils.getFieldNameFromSubTree(null, subTree,
+                            assignOrUnnestIndex, varIndex, subTree.getRecordType(), -1, null,
+                            subTree.getMetaRecordType(), datasetMetaVar, fieldSource, false);
                     if (fieldName != null && !fieldName.isEmpty()) {
                         subTree.getVarsToFieldNameMap().put(var, fieldName);
                     }
@@ -1122,8 +981,9 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
                     // funcVarIndex is not required. Thus, we set it to -1.
                     // optFuncExpr and parentFuncExpr are not required, too. Thus, we set them to null.
                     fieldSource.setValue(0);
-                    fieldName = getFieldNameFromSubTree(null, subTree, assignOrUnnestIndex, 0, subTree.getRecordType(),
-                            -1, null, subTree.getMetaRecordType(), datasetMetaVar, fieldSource);
+                    fieldName = AccessMethodUtils.getFieldNameFromSubTree(null, subTree, assignOrUnnestIndex, 0,
+                            subTree.getRecordType(), -1, null, subTree.getMetaRecordType(), datasetMetaVar, fieldSource,
+                            false);
                     if (fieldName != null && !fieldName.isEmpty()) {
                         subTree.getVarsToFieldNameMap().put(var, fieldName);
                     }
@@ -1149,9 +1009,9 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
                     // funcVarIndex is not required. Thus, we set it to -1.
                     // optFuncExpr and parentFuncExpr are not required, too. Thus, we set them to null.
                     fieldSource.setValue(0);
-                    List<String> fieldName = getFieldNameFromSubTree(null, subTree, assignOrUnnestIndex, varIndex,
-                            subTree.getRecordType(), -1, null, subTree.getMetaRecordType(), datasetMetaVar,
-                            fieldSource);
+                    List<String> fieldName = AccessMethodUtils.getFieldNameFromSubTree(null, subTree,
+                            assignOrUnnestIndex, varIndex, subTree.getRecordType(), -1, null,
+                            subTree.getMetaRecordType(), datasetMetaVar, fieldSource, false);
                     if (fieldName != null && !fieldName.isEmpty()) {
                         subTree.getVarsToFieldNameMap().put(var, fieldName);
                     }
@@ -1167,10 +1027,10 @@ public abstract class AbstractIntroduceAccessMethodRule implements IAlgebraicRew
                 subTree.getPrimaryKeyVars(null, primaryKeyVarList);
 
                 Index primaryIndex = getPrimaryIndexFromDataSourceScanOp(subTree.getDataSourceRef().getValue());
-
+                List<List<String>> keyFieldNames =
+                        ((Index.ValueIndexDetails) primaryIndex.getIndexDetails()).getKeyFieldNames();
                 for (int i = 0; i < primaryKeyVarList.size(); i++) {
-                    subTree.getVarsToFieldNameMap().put(primaryKeyVarList.get(i),
-                            primaryIndex.getKeyFieldNames().get(i));
+                    subTree.getVarsToFieldNameMap().put(primaryKeyVarList.get(i), keyFieldNames.get(i));
                 }
             }
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java
index e003ea5..09f9e41 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java
@@ -20,6 +20,7 @@
 package org.apache.asterix.optimizer.rules.am;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -27,6 +28,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Set;
+import java.util.Stack;
 
 import org.apache.asterix.algebra.operators.physical.ExternalDataLookupPOperator;
 import org.apache.asterix.common.annotations.AbstractExpressionAnnotationWithIndexNames;
@@ -41,9 +43,11 @@ import org.apache.asterix.metadata.declared.DataSourceId;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.ExternalDatasetDetails;
 import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.metadata.utils.ArrayIndexUtil;
 import org.apache.asterix.metadata.utils.KeyFieldTypeUtil;
 import org.apache.asterix.om.base.ABoolean;
 import org.apache.asterix.om.base.AInt32;
+import org.apache.asterix.om.base.AOrderedList;
 import org.apache.asterix.om.base.AString;
 import org.apache.asterix.om.base.IACursor;
 import org.apache.asterix.om.base.IAObject;
@@ -59,6 +63,7 @@ import org.apache.asterix.om.types.hierachy.ATypeHierarchy;
 import org.apache.asterix.om.types.hierachy.ATypeHierarchy.TypeCastingMathFunctionType;
 import org.apache.asterix.om.utils.ConstantExpressionUtil;
 import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableInt;
 import org.apache.commons.lang3.mutable.MutableObject;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.Pair;
@@ -72,6 +77,7 @@ import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractLogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
@@ -88,6 +94,7 @@ import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOper
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistinctOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.OrderOperator;
@@ -95,6 +102,7 @@ import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperat
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.SplitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnionAllOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestMapOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.WindowOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities;
 import org.apache.hyracks.algebricks.core.algebra.plan.ALogicalPlanImpl;
@@ -103,6 +111,8 @@ import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.api.exceptions.SourceLocation;
 import org.apache.hyracks.storage.am.lsm.invertedindex.tokenizers.DelimitedUTF8StringBinaryTokenizer;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * Static helper functions for rewriting plans using indexes.
  */
@@ -115,6 +125,15 @@ public class AccessMethodUtils {
         CONDITIONAL_SPLIT_VAR
     }
 
+    // Function Identifier sets that retain the original field variable through each function's arguments
+    private final static ImmutableSet<FunctionIdentifier> funcIDSetThatRetainFieldName =
+            ImmutableSet.of(BuiltinFunctions.WORD_TOKENS, BuiltinFunctions.GRAM_TOKENS, BuiltinFunctions.SUBSTRING,
+                    BuiltinFunctions.SUBSTRING_BEFORE, BuiltinFunctions.SUBSTRING_AFTER,
+                    BuiltinFunctions.CREATE_POLYGON, BuiltinFunctions.CREATE_MBR, BuiltinFunctions.CREATE_RECTANGLE,
+                    BuiltinFunctions.CREATE_CIRCLE, BuiltinFunctions.CREATE_LINE, BuiltinFunctions.CREATE_POINT,
+                    BuiltinFunctions.NUMERIC_ADD, BuiltinFunctions.NUMERIC_SUBTRACT, BuiltinFunctions.NUMERIC_MULTIPLY,
+                    BuiltinFunctions.NUMERIC_DIVIDE, BuiltinFunctions.NUMERIC_DIV, BuiltinFunctions.NUMERIC_MOD);
+
     public static void appendPrimaryIndexTypes(Dataset dataset, IAType itemType, IAType metaItemType,
             List<Object> target) throws AlgebricksException {
         ARecordType recordType = (ARecordType) itemType;
@@ -324,6 +343,9 @@ public class AccessMethodUtils {
         boolean primaryKeysOnly = isInvertedIndex(index);
         if (!primaryKeysOnly) {
             switch (index.getIndexType()) {
+                case ARRAY:
+                    dest.addAll(KeyFieldTypeUtil.getArrayBTreeIndexKeyTypes(index, recordType, metaRecordType));
+                    break;
                 case BTREE:
                     dest.addAll(KeyFieldTypeUtil.getBTreeIndexKeyTypes(index, recordType, metaRecordType));
                     break;
@@ -952,10 +974,40 @@ public class AccessMethodUtils {
 
     private static AbstractUnnestMapOperator createFinalNonIndexOnlySearchPlan(Dataset dataset,
             ILogicalOperator inputOp, IOptimizationContext context, boolean sortPrimaryKeys, boolean retainInput,
-            boolean retainMissing, boolean requiresBroadcast, List<LogicalVariable> primaryKeyVars,
-            List<LogicalVariable> primaryIndexUnnestVars, List<Object> primaryIndexOutputTypes)
-            throws AlgebricksException {
+            boolean retainMissing, boolean requiresBroadcast, boolean requiresDistinct,
+            List<LogicalVariable> primaryKeyVars, List<LogicalVariable> primaryIndexUnnestVars,
+            List<LogicalVariable> auxDistinctVars, List<Object> primaryIndexOutputTypes) throws AlgebricksException {
         SourceLocation sourceLoc = inputOp.getSourceLocation();
+
+        // Sanity check: requiresDistinct and sortPrimaryKeys are mutually exclusive.
+        if (requiresDistinct && sortPrimaryKeys) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc,
+                    "Non-index search plan " + "cannot include a DISTINCT and an ORDER.");
+        }
+
+        // If we have an array index, then we must only give unique keys to our primary-index scan.
+        DistinctOperator distinct = null;
+        if (requiresDistinct) {
+            List<Mutable<ILogicalExpression>> distinctExprs = new ArrayList<>();
+            for (LogicalVariable pkVar : primaryKeyVars) {
+                VariableReferenceExpression pkVarRef = new VariableReferenceExpression(pkVar);
+                pkVarRef.setSourceLocation(sourceLoc);
+                Mutable<ILogicalExpression> vRef = new MutableObject<>(pkVarRef);
+                distinctExprs.add(vRef);
+            }
+            for (LogicalVariable auxVar : auxDistinctVars) {
+                VariableReferenceExpression auxVarRef = new VariableReferenceExpression(auxVar);
+                auxVarRef.setSourceLocation(sourceLoc);
+                Mutable<ILogicalExpression> vRef = new MutableObject<>(auxVarRef);
+                distinctExprs.add(vRef);
+            }
+            distinct = new DistinctOperator(distinctExprs);
+            distinct.setSourceLocation(sourceLoc);
+            distinct.getInputs().add(new MutableObject<>(inputOp));
+            distinct.setExecutionMode(ExecutionMode.LOCAL);
+            context.computeAndSetTypeEnvironmentForOperator(distinct);
+        }
+
         // Optionally add a sort on the primary-index keys before searching the primary index.
         OrderOperator order = null;
         if (sortPrimaryKeys) {
@@ -977,7 +1029,9 @@ public class AccessMethodUtils {
         AbstractUnnestMapOperator primaryIndexUnnestMapOp =
                 createPrimaryIndexUnnestMapOp(dataset, retainInput, retainMissing, requiresBroadcast, primaryKeyVars,
                         primaryIndexUnnestVars, primaryIndexOutputTypes, sourceLoc);
-        if (sortPrimaryKeys) {
+        if (requiresDistinct) {
+            primaryIndexUnnestMapOp.getInputs().add(new MutableObject<ILogicalOperator>(distinct));
+        } else if (sortPrimaryKeys) {
             primaryIndexUnnestMapOp.getInputs().add(new MutableObject<ILogicalOperator>(order));
         } else {
             primaryIndexUnnestMapOp.getInputs().add(new MutableObject<>(inputOp));
@@ -1020,7 +1074,8 @@ public class AccessMethodUtils {
         // key search (SK, PK) and those in the original plan (datasource scan).
         LinkedHashMap<LogicalVariable, LogicalVariable> origVarToSIdxUnnestMapOpVarMap = new LinkedHashMap<>();
 
-        List<List<String>> chosenIndexFieldNames = secondaryIndex.getKeyFieldNames();
+        Index.ValueIndexDetails secondaryIndexDetails = (Index.ValueIndexDetails) secondaryIndex.getIndexDetails();
+        List<List<String>> chosenIndexFieldNames = secondaryIndexDetails.getKeyFieldNames();
         IndexType idxType = secondaryIndex.getIndexType();
 
         // variables used in SELECT or JOIN operator
@@ -1549,7 +1604,7 @@ public class AccessMethodUtils {
      * (i.e., we can guarantee the correctness of the result.)
      *
      * Case A) non-index-only plan
-     * sidx-search -> (optional) sort -> pdix-search
+     * sidx-search -> (optional) sort -> (optional) distinct -> pdix-search
      *
      * Case B) index-only plan
      * left path (an instantTryLock() on the PK fail path):
@@ -1563,8 +1618,8 @@ public class AccessMethodUtils {
             Dataset dataset, ARecordType recordType, ARecordType metaRecordType, ILogicalOperator inputOp,
             IOptimizationContext context, boolean sortPrimaryKeys, boolean retainInput, boolean retainMissing,
             boolean requiresBroadcast, Index secondaryIndex, AccessMethodAnalysisContext analysisCtx,
-            OptimizableOperatorSubTree subTree, LogicalVariable newMissingPlaceHolderForLOJ)
-            throws AlgebricksException {
+            OptimizableOperatorSubTree indexSubTree, OptimizableOperatorSubTree probeSubTree,
+            LogicalVariable newMissingPlaceHolderForLOJ) throws AlgebricksException {
         // Common part for the non-index-only plan and index-only plan
         // Variables and types for the primary-index search.
         List<LogicalVariable> primaryIndexUnnestVars = new ArrayList<>();
@@ -1577,21 +1632,32 @@ public class AccessMethodUtils {
         List<LogicalVariable> pkVarsFromSIdxUnnestMapOp = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset,
                 recordType, metaRecordType, inputOp, secondaryIndex, SecondaryUnnestMapOutputVarType.PRIMARY_KEY);
 
-        // Index-only plan or not?
+        // Index-only plan or not? Array-index involved or not?
         boolean isIndexOnlyPlan = analysisCtx.getIndexOnlyPlanInfo().getFirst();
+        boolean isArrayIndex = secondaryIndex.getIndexType() == IndexType.ARRAY;
 
-        // Non-index-only plan case: creates ORDER -> UNNEST-MAP(Primary-index search) and return that unnest-map op.
+        // Non-index-only plan case: creates (ORDER)? -> (DISTINCT)? -> UNNEST-MAP(PIDX) and return that unnest-map op.
         if (!isIndexOnlyPlan) {
-            return createFinalNonIndexOnlySearchPlan(dataset, inputOp, context, sortPrimaryKeys, retainInput,
-                    retainMissing, requiresBroadcast, pkVarsFromSIdxUnnestMapOp, primaryIndexUnnestVars,
-                    primaryIndexOutputTypes);
-        } else {
+            // If we have a join + an array index, we need add the join source PK to the DISTINCT + ORDER.
+            List<LogicalVariable> joinPKVars = Collections.emptyList();
+            if (isArrayIndex && probeSubTree != null) {
+                joinPKVars = probeSubTree.getDataSourceVariables().subList(0,
+                        probeSubTree.getDataSourceVariables().size() - 1);
+            }
+
+            return createFinalNonIndexOnlySearchPlan(dataset, inputOp, context, !isArrayIndex && sortPrimaryKeys,
+                    retainInput, retainMissing, requiresBroadcast, isArrayIndex, pkVarsFromSIdxUnnestMapOp,
+                    primaryIndexUnnestVars, joinPKVars, primaryIndexOutputTypes);
+        } else if (!isArrayIndex) {
             // Index-only plan case: creates a UNIONALL operator that has two paths after the secondary unnest-map op,
             // and returns it.
             return createFinalIndexOnlySearchPlan(afterTopOpRefs, topOpRef, conditionRef, assignsBeforeTopOpRef,
                     dataset, recordType, metaRecordType, inputOp, context, retainInput, retainMissing,
-                    requiresBroadcast, secondaryIndex, analysisCtx, subTree, newMissingPlaceHolderForLOJ,
+                    requiresBroadcast, secondaryIndex, analysisCtx, indexSubTree, newMissingPlaceHolderForLOJ,
                     pkVarsFromSIdxUnnestMapOp, primaryIndexUnnestVars, primaryIndexOutputTypes);
+        } else {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, inputOp.getSourceLocation(),
+                    "Cannot use index-only plan with array indexes.");
         }
     }
 
@@ -1890,19 +1956,19 @@ public class AccessMethodUtils {
         // Since index-only plan doesn't access the primary index, we can't get the actual value in this case.
         // Also, if no-index-only option is given, we stop here to honor that request.
         boolean noIndexOnlyPlanOption = !context.getPhysicalOptimizationConfig().isIndexOnly();
-        // TODO: For the inverted index access-method cases only:
+        // TODO: For the inverted index / array index access-method cases only:
         // Since an inverted index can contain multiple secondary key entries per one primary key,
         // Index-only plan can't be applied. For example, suppose there are two entries (SK1, SK2) for one PK.
         // Since we can't access <SK1, PK>, <SK2, PK> at the same time unless we use tryLock (we use instantTryLock),
         // right now, we can't support an index-only plan on an inverted index.
         // Once this issue is resolved, we can apply an index-only plan.
-        // One additional condition:
+        // One additional condition for inverted indexes:
         // Even if the above is resolved, if a secondary key field is used after
         // SELECT or JOIN operator, this can't be qualified as an index-only plan since
         // an inverted index contains a part of a field value, not all of it.
         if (noIndexOnlyPlanOption || dataset.getDatasetType() == DatasetType.EXTERNAL || chosenIndex.isPrimaryIndex()
-                || chosenIndex.isOverridingKeyFieldTypes() || chosenIndex.isEnforced()
-                || isInvertedIndex(chosenIndex)) {
+                || chosenIndex.getIndexDetails().isOverridingKeyFieldTypes() || chosenIndex.isEnforced()
+                || isInvertedIndex(chosenIndex) || chosenIndex.getIndexType() == IndexType.ARRAY) {
             indexOnlyPlanInfo.setFirst(false);
             return;
         }
@@ -2002,7 +2068,8 @@ public class AccessMethodUtils {
         // assign or data-source-scan in the subtree and the field-name of those variables are only PK or SK.
         // Needs to check whether variables from the given select (join) operator only contain SK and/or PK condition.
         List<List<String>> pkFieldNames = dataset.getPrimaryKeys();
-        List<List<String>> chosenIndexFieldNames = chosenIndex.getKeyFieldNames();
+        Index.ValueIndexDetails chosenIndexDetails = (Index.ValueIndexDetails) chosenIndex.getIndexDetails();
+        List<List<String>> chosenIndexFieldNames = chosenIndexDetails.getKeyFieldNames();
         List<LogicalVariable> chosenIndexVars = new ArrayList<>();
 
         // Collects variables that contain a CONSTANT expression in ASSIGN operators in the subtree.
@@ -2026,7 +2093,7 @@ public class AccessMethodUtils {
         }
 
         // For the composite index, a secondary-index search generates a superset of the results.
-        if (chosenIndex.getKeyFieldNames().size() > 1 && indexApplicableVarFoundCount > 1) {
+        if (chosenIndexDetails.getKeyFieldNames().size() > 1 && indexApplicableVarFoundCount > 1) {
             requireVerificationAfterSIdxSearch = true;
         }
 
@@ -2674,4 +2741,451 @@ public class AccessMethodUtils {
         AbstractExpressionAnnotationWithIndexNames ann = optFuncExpr.getFuncExpr().getAnnotation(annClass);
         return ann == null ? null : ann.getIndexNames();
     }
+
+    /**
+     * Returns the field name corresponding to the assigned variable at
+     * varIndex. Returns Collections.emptyList() if the expr at varIndex does not yield to a field
+     * access function after following a set of allowed functions.
+     *
+     * @throws AlgebricksException
+     */
+    public static List<String> getFieldNameFromSubTree(IOptimizableFuncExpr optFuncExpr,
+            OptimizableOperatorSubTree subTree, int opIndex, int assignVarIndex, ARecordType recordType,
+            int funcVarIndex, ILogicalExpression parentFuncExpr, ARecordType metaType, LogicalVariable metaVar,
+            MutableInt fieldSource, boolean isUnnestOverVarAllowed) throws AlgebricksException {
+        // Get expression corresponding to opVar at varIndex.
+        AbstractLogicalExpression expr = null;
+        AbstractFunctionCallExpression childFuncExpr = null;
+        AbstractLogicalOperator op = subTree.getAssignsAndUnnests().get(opIndex);
+        if (op.getOperatorTag() == LogicalOperatorTag.ASSIGN) {
+            AssignOperator assignOp = (AssignOperator) op;
+            expr = (AbstractLogicalExpression) assignOp.getExpressions().get(assignVarIndex).getValue();
+            // Can't get a field name from a constant expression. So, return null.
+            if (expr.getExpressionTag() == LogicalExpressionTag.CONSTANT) {
+                return Collections.emptyList();
+            }
+            childFuncExpr = (AbstractFunctionCallExpression) expr;
+        } else {
+            UnnestOperator unnestOp = (UnnestOperator) op;
+            expr = (AbstractLogicalExpression) unnestOp.getExpressionRef().getValue();
+            if (expr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+                return Collections.emptyList();
+            }
+            childFuncExpr = (AbstractFunctionCallExpression) expr;
+            if (childFuncExpr.getFunctionIdentifier() != BuiltinFunctions.SCAN_COLLECTION) {
+                return Collections.emptyList();
+            }
+            expr = (AbstractLogicalExpression) childFuncExpr.getArguments().get(0).getValue();
+        }
+        if (expr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+            return Collections.emptyList();
+        }
+        AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) expr;
+        FunctionIdentifier funcIdent = funcExpr.getFunctionIdentifier();
+
+        boolean isByName = false;
+        boolean isFieldAccess = false;
+        String fieldName = null;
+        List<String> nestedAccessFieldName = null;
+        int fieldIndex = -1;
+        if (funcIdent == BuiltinFunctions.FIELD_ACCESS_BY_NAME) {
+            fieldName = ConstantExpressionUtil.getStringArgument(funcExpr, 1);
+            if (fieldName == null) {
+                return Collections.emptyList();
+            }
+            isFieldAccess = true;
+            isByName = true;
+        } else if (funcIdent == BuiltinFunctions.FIELD_ACCESS_BY_INDEX) {
+            Integer idx = ConstantExpressionUtil.getIntArgument(funcExpr, 1);
+            if (idx == null) {
+                return Collections.emptyList();
+            }
+            fieldIndex = idx;
+            isFieldAccess = true;
+        } else if (funcIdent == BuiltinFunctions.FIELD_ACCESS_NESTED) {
+            ILogicalExpression nameArg = funcExpr.getArguments().get(1).getValue();
+            if (nameArg.getExpressionTag() != LogicalExpressionTag.CONSTANT) {
+                return Collections.emptyList();
+            }
+            ConstantExpression constExpr = (ConstantExpression) nameArg;
+            AOrderedList orderedNestedFieldName =
+                    (AOrderedList) ((AsterixConstantValue) constExpr.getValue()).getObject();
+            nestedAccessFieldName = new ArrayList<>();
+            for (int i = 0; i < orderedNestedFieldName.size(); i++) {
+                nestedAccessFieldName.add(((AString) orderedNestedFieldName.getItem(i)).getStringValue());
+            }
+            isFieldAccess = true;
+            isByName = true;
+        }
+        if (isFieldAccess) {
+            LogicalVariable sourceVar =
+                    ((VariableReferenceExpression) funcExpr.getArguments().get(0).getValue()).getVariableReference();
+            if (sourceVar.equals(metaVar)) {
+                fieldSource.setValue(1);
+            } else {
+                fieldSource.setValue(0);
+            }
+            if (optFuncExpr != null) {
+                optFuncExpr.setLogicalExpr(funcVarIndex, parentFuncExpr);
+            }
+            int[] assignAndExpressionIndexes = null;
+
+            //go forward through nested assigns until you find the relevant one
+            for (int i = opIndex + 1; i < subTree.getAssignsAndUnnests().size(); i++) {
+                AbstractLogicalOperator subOp = subTree.getAssignsAndUnnests().get(i);
+                List<LogicalVariable> varList;
+
+                if (subOp.getOperatorTag() == LogicalOperatorTag.ASSIGN) {
+                    //Nested was an assign
+                    varList = ((AssignOperator) subOp).getVariables();
+                } else if (subOp.getOperatorTag() == LogicalOperatorTag.UNNEST) {
+                    //Nested is not an assign
+                    varList = ((UnnestOperator) subOp).getVariables();
+                } else {
+                    break;
+                }
+
+                //Go through variables in assign to check for match
+                for (int varIndex = 0; varIndex < varList.size(); varIndex++) {
+                    LogicalVariable var = varList.get(varIndex);
+                    ArrayList<LogicalVariable> parentVars = new ArrayList<>();
+                    expr.getUsedVariables(parentVars);
+
+                    if (parentVars.contains(var)) {
+                        //Found the variable we are looking for.
+                        //return assign and index of expression
+                        int[] returnValues = { i, varIndex };
+                        assignAndExpressionIndexes = returnValues;
+                    }
+                }
+            }
+            if (assignAndExpressionIndexes != null && assignAndExpressionIndexes[0] > -1) {
+                //We found the nested assign
+
+                //Recursive call on nested assign
+                List<String> parentFieldNames = getFieldNameFromSubTree(optFuncExpr, subTree,
+                        assignAndExpressionIndexes[0], assignAndExpressionIndexes[1], recordType, funcVarIndex,
+                        parentFuncExpr, metaType, metaVar, fieldSource, isUnnestOverVarAllowed);
+
+                boolean isPreviousOperatorLegalUnnest = isUnnestOverVarAllowed && subTree.getAssignsAndUnnests()
+                        .get(assignAndExpressionIndexes[0]).getOperatorTag().equals(LogicalOperatorTag.UNNEST);
+                if (parentFieldNames.isEmpty() && !isPreviousOperatorLegalUnnest) {
+                    //Nested assign was not a field access.
+                    //We will not use index
+                    return Collections.emptyList();
+                } else if (isPreviousOperatorLegalUnnest) {
+                    parentFieldNames = new ArrayList<>();
+                }
+
+                if (!isByName) {
+                    IAType subFieldType;
+                    if (isUnnestOverVarAllowed && isPreviousOperatorLegalUnnest) {
+                        // In the case of UNNESTing over a variable, we use the record type given by our caller instead.
+                        subFieldType = sourceVar.equals(metaVar) ? metaType : recordType;
+                    } else {
+                        subFieldType = sourceVar.equals(metaVar) ? metaType.getSubFieldType(parentFieldNames)
+                                : recordType.getSubFieldType(parentFieldNames);
+                        // Sub-field type can be AUnionType in case if optional. Thus, needs to get the actual type.
+                        subFieldType = TypeComputeUtils.getActualType(subFieldType);
+                        if (subFieldType.getTypeTag() != ATypeTag.OBJECT) {
+                            throw CompilationException.create(ErrorCode.TYPE_CONVERT, subFieldType,
+                                    ARecordType.class.getName());
+                        }
+                    }
+                    fieldName = ((ARecordType) subFieldType).getFieldNames()[fieldIndex];
+
+                }
+                if (optFuncExpr != null) {
+                    optFuncExpr.setSourceVar(funcVarIndex, ((AssignOperator) op).getVariables().get(assignVarIndex));
+                }
+                //add fieldName to the nested fieldName, return
+                if (nestedAccessFieldName != null) {
+                    for (int i = 0; i < nestedAccessFieldName.size(); i++) {
+                        parentFieldNames.add(nestedAccessFieldName.get(i));
+                    }
+                } else {
+                    parentFieldNames.add(fieldName);
+                }
+                return (parentFieldNames);
+            }
+
+            if (optFuncExpr != null) {
+                optFuncExpr.setSourceVar(funcVarIndex, ((AssignOperator) op).getVariables().get(assignVarIndex));
+            }
+            //no nested assign, we are at the lowest level.
+            if (isByName) {
+                if (nestedAccessFieldName != null) {
+                    return nestedAccessFieldName;
+                }
+                return new ArrayList<>(Arrays.asList(fieldName));
+            }
+            return new ArrayList<>(Arrays.asList(sourceVar.equals(metaVar) ? metaType.getFieldNames()[fieldIndex]
+                    : recordType.getFieldNames()[fieldIndex]));
+
+        }
+
+        if (!funcIDSetThatRetainFieldName.contains(funcIdent)) {
+            return Collections.emptyList();
+        }
+        // We use a part of the field in edit distance computation
+        if (optFuncExpr != null
+                && optFuncExpr.getFuncExpr().getFunctionIdentifier() == BuiltinFunctions.EDIT_DISTANCE_CHECK) {
+            optFuncExpr.setPartialField(true);
+        }
+        // We expect the function's argument to be a variable, otherwise we
+        // cannot apply an index.
+        ILogicalExpression argExpr = funcExpr.getArguments().get(0).getValue();
+        if (argExpr.getExpressionTag() != LogicalExpressionTag.VARIABLE) {
+            return Collections.emptyList();
+        }
+        LogicalVariable curVar = ((VariableReferenceExpression) argExpr).getVariableReference();
+        // We look for the assign or unnest operator that produces curVar below
+        // the current operator
+        for (int assignOrUnnestIndex = opIndex + 1; assignOrUnnestIndex < subTree.getAssignsAndUnnests()
+                .size(); assignOrUnnestIndex++) {
+            AbstractLogicalOperator curOp = subTree.getAssignsAndUnnests().get(assignOrUnnestIndex);
+            if (curOp.getOperatorTag() == LogicalOperatorTag.ASSIGN) {
+                AssignOperator assignOp = (AssignOperator) curOp;
+                List<LogicalVariable> varList = assignOp.getVariables();
+                for (int varIndex = 0; varIndex < varList.size(); varIndex++) {
+                    LogicalVariable var = varList.get(varIndex);
+                    if (var.equals(curVar) && optFuncExpr != null) {
+                        optFuncExpr.setSourceVar(funcVarIndex, var);
+                        return getFieldNameFromSubTree(optFuncExpr, subTree, assignOrUnnestIndex, varIndex, recordType,
+                                funcVarIndex, childFuncExpr, metaType, metaVar, fieldSource, isUnnestOverVarAllowed);
+                    }
+                }
+            } else {
+                UnnestOperator unnestOp = (UnnestOperator) curOp;
+                LogicalVariable var = unnestOp.getVariable();
+                if (var.equals(curVar)) {
+                    getFieldNameFromSubTree(optFuncExpr, subTree, assignOrUnnestIndex, 0, recordType, funcVarIndex,
+                            childFuncExpr, metaType, metaVar, fieldSource, isUnnestOverVarAllowed);
+                }
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Determine whether an array index can be used for the given variable.
+     */
+    public static Triple<Integer, List<String>, IAType> analyzeVarForArrayIndexes(AssignOperator assignOp,
+            IOptimizableFuncExpr optFuncExpr, OptimizableOperatorSubTree subTree, LogicalVariable datasetMetaVar,
+            IOptimizationContext context, List<Index> datasetIndexes, List<IOptimizableFuncExpr> matchedFuncExprs,
+            int assignVarIndex) throws AlgebricksException {
+
+        for (Index index : datasetIndexes) {
+            if (index.getIndexType() != IndexType.ARRAY) {
+                continue;
+            }
+            Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) index.getIndexDetails();
+            for (Index.ArrayIndexElement e : arrayIndexDetails.getElementList()) {
+                if (e.getUnnestList().isEmpty()) {
+                    // Ignore the atomic part of this index (these are handled by the caller).
+                    continue;
+                }
+
+                // We have found the array field for an array index.
+                for (List<String> project : e.getProjectList()) {
+                    List<String> flattenedFieldName =
+                            ArrayIndexUtil.getFlattenedKeyFieldNames(e.getUnnestList(), project);
+                    List<Integer> arrayIndicator = ArrayIndexUtil.getArrayDepthIndicator(e.getUnnestList(), project);
+
+                    // Do not match a variable that we have previously matched.
+                    if (matchedFuncExprs.stream().anyMatch(f -> f.findFieldName(flattenedFieldName) != -1)) {
+                        continue;
+                    }
+
+                    Triple<Integer, List<String>, IAType> fieldTriplet =
+                            matchAssignFieldInUnnestAssignStack(assignOp.getVariables().get(assignVarIndex),
+                                    assignVarIndex, optFuncExpr, subTree, datasetMetaVar, context, arrayIndicator,
+                                    flattenedFieldName, arrayIndexDetails.isOverridingKeyFieldTypes());
+
+                    // This specific field aligns with our array index.
+                    if (fieldTriplet.first > -1) {
+                        int optVarIndex = fieldTriplet.first;
+                        List<String> fieldName = fieldTriplet.second;
+                        IAType fieldType = fieldTriplet.third;
+
+                        // Remember matching subtree.
+                        optFuncExpr.setOptimizableSubTree(optVarIndex, subTree);
+                        MutableInt fieldSource = new MutableInt(0);
+                        optFuncExpr.setFieldName(optVarIndex, fieldName, fieldSource.intValue());
+                        optFuncExpr.setFieldType(optVarIndex, fieldType);
+                        IAType type = (IAType) context.getOutputTypeEnvironment(subTree.getRoot())
+                                .getVarType(optFuncExpr.getLogicalVar(optVarIndex));
+                        optFuncExpr.setFieldType(optVarIndex, type);
+
+                        return fieldTriplet;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param assignVar Variable from lowest assign that we are trying to match (i.e. the first array step var).
+     * @param assignVarIndex Index of the variable from the lowest assign.
+     * @param optFuncExpr The function expression we are trying to optimize.
+     * @param subTree Subtree for the function expression {@code optFunExpr}.
+     * @param datasetMetaVar Meta-variable from our subtree, if any exist.
+     * @param context Context required to get the type of the found variable.
+     * @param indexArrayIndicators Depth indicators of index to match our unnest/assign stack to.
+     * @param indexFieldNames Field names of index to match our unnest/assign stack to.
+     * @param areFieldNamesInAssign True if we have an open index. False otherwise.
+     */
+    private static Triple<Integer, List<String>, IAType> matchAssignFieldInUnnestAssignStack(LogicalVariable assignVar,
+            int assignVarIndex, IOptimizableFuncExpr optFuncExpr, OptimizableOperatorSubTree subTree,
+            LogicalVariable datasetMetaVar, IOptimizationContext context, List<Integer> indexArrayIndicators,
+            List<String> indexFieldNames, boolean areFieldNamesInAssign) throws AlgebricksException {
+        Triple<Integer, List<String>, IAType> resultantTriple = new Triple<>(-1, new ArrayList<>(), null);
+        final int optVarIndex = subTree.getLastMatchedDataSourceVars().second;
+        if (optVarIndex < 0) {
+            return resultantTriple;
+        }
+        final ILogicalExpression optVarExpr = optFuncExpr.getFuncExpr().getArguments().get(optVarIndex).getValue();
+        optFuncExpr.setLogicalExpr(optVarIndex, optVarExpr);
+
+        // Build our assign / unnest stack. Do not include the very last assign (this is handled in the parent).
+        int indexOfWorkingOp = subTree.getAssignsAndUnnests().size() - 1;
+        Stack<AbstractLogicalOperator> logicalOperatorStack = new Stack<>();
+        logicalOperatorStack.addAll(subTree.getAssignsAndUnnests().subList(0, indexOfWorkingOp));
+        if (logicalOperatorStack.empty()) {
+            return resultantTriple;
+        }
+
+        // Aggregate our record paths, and pair these with their respective array indexes.
+        Pair<List<List<String>>, List<Integer>> unnestPairs =
+                ArrayIndexUtil.unnestComplexRecordPath(indexFieldNames, indexArrayIndicators);
+        AbstractLogicalOperator workingOp = null;
+        List<String> fieldNameForWorkingUnnest;
+        MutableInt fieldSource = new MutableInt(0);
+        ARecordType workingRecordType = subTree.getRecordType();
+
+        // TODO: (GLENN) Refactor this to use ArrayIndexUtil.
+        // Iterate through our array index structure. We must match the depth and field names for the caller's variable
+        // to qualify for an array-index optimization.
+        LogicalVariable varFromParent = assignVar;
+        for (int pairsIndex = 0; pairsIndex < unnestPairs.first.size(); pairsIndex++) {
+            if (logicalOperatorStack.empty()) {
+                return resultantTriple;
+            }
+            workingOp = logicalOperatorStack.pop();
+
+            // Explore our UNNEST path.
+            if (unnestPairs.second.get(pairsIndex) > 0) {
+                for (int i = (pairsIndex == 0) ? 1 : 0; i < unnestPairs.first.get(pairsIndex).size(); i++) {
+                    // Match our parent assign variable to a variable used in our working assign.
+                    assignVarIndex = findAssignVarIndex(workingOp, varFromParent);
+                    if (logicalOperatorStack.empty() || assignVarIndex == -1) {
+                        return resultantTriple;
+                    }
+                    varFromParent = ((AssignOperator) workingOp).getVariables().get(assignVarIndex);
+                    indexOfWorkingOp--;
+                    workingOp = logicalOperatorStack.pop();
+                }
+
+                // Get the field name associated with the current UNNEST.
+                if (workingOp.getOperatorTag() != LogicalOperatorTag.UNNEST) {
+                    return resultantTriple;
+                }
+                fieldNameForWorkingUnnest = getFieldNameFromSubTree(null, subTree, indexOfWorkingOp, assignVarIndex,
+                        workingRecordType, 0, null, subTree.getMetaRecordType(), datasetMetaVar, fieldSource, true);
+
+                if (!fieldNameForWorkingUnnest.equals(unnestPairs.first.get(pairsIndex))) {
+                    return resultantTriple;
+                }
+                resultantTriple.second.addAll(fieldNameForWorkingUnnest);
+
+                IAType typeIntermediate = workingRecordType.getSubFieldType(fieldNameForWorkingUnnest);
+                for (int i = 0; i < unnestPairs.second.get(pairsIndex); i++) {
+                    // If we are working with a closed index, then update our record type. For open types, we do not
+                    // need to do this as the field name is stored in the expression itself.
+                    if (!areFieldNamesInAssign && pairsIndex != unnestPairs.first.size() - 1) {
+                        typeIntermediate = TypeComputeUtils.extractListItemType(typeIntermediate);
+                        if (typeIntermediate == null) {
+                            return resultantTriple;
+                        }
+                    }
+                    boolean isIntermediateUnnestInPath = (i != unnestPairs.second.get(pairsIndex) - 1);
+                    if (!areFieldNamesInAssign && !isIntermediateUnnestInPath) {
+                        if (typeIntermediate.getTypeTag().equals(ATypeTag.OBJECT)) {
+                            workingRecordType = (ARecordType) typeIntermediate;
+                        } else if (!typeIntermediate.getTypeTag().isListType()) {
+                            return resultantTriple;
+                        }
+                    }
+
+                    // Update our parent variable. If we are in-between UNNESTs, we need to fetch the next UNNEST.
+                    if (isIntermediateUnnestInPath) {
+                        workingOp = logicalOperatorStack.pop();
+                        indexOfWorkingOp--;
+                    }
+                    varFromParent = ((UnnestOperator) workingOp).getVariable();
+                }
+            } else if (pairsIndex != 0) {
+                // We have explored an UNNEST array-path previously, and must now match a field name.
+                AssignOperator workingOpAsAssign = (AssignOperator) workingOp;
+                indexOfWorkingOp -= unnestPairs.first.get(pairsIndex).size();
+                for (assignVarIndex = 0; assignVarIndex < workingOpAsAssign.getVariables().size(); assignVarIndex++) {
+                    // Iterate through each of our ASSIGN's field names, and try to match the index field names.
+                    fieldNameForWorkingUnnest = getFieldNameFromSubTree(null, subTree, indexOfWorkingOp, assignVarIndex,
+                            workingRecordType, 0, null, subTree.getMetaRecordType(), datasetMetaVar, fieldSource, true);
+
+                    if (fieldNameForWorkingUnnest.equals(unnestPairs.first.get(pairsIndex))) {
+                        resultantTriple.second.addAll(fieldNameForWorkingUnnest);
+                        break;
+                    }
+                }
+
+                // We have exhausted all of our ASSIGN variables, but have not matched the field name. Exit early.
+                if (assignVarIndex == workingOpAsAssign.getVariables().size()) {
+                    return resultantTriple;
+                }
+            }
+
+            indexOfWorkingOp--;
+        }
+
+        // We have found an applicable array index. Determine our optFuncIndex and fieldType.
+        if (workingOp != null && workingOp.getOperatorTag() == LogicalOperatorTag.ASSIGN) {
+            AssignOperator workingOpAsAssign = (AssignOperator) workingOp;
+            LogicalVariable matchedVar = workingOpAsAssign.getVariables().get(assignVarIndex);
+            if (optFuncExpr.findLogicalVar(matchedVar) > -1) {
+                resultantTriple.first = optFuncExpr.findLogicalVar(matchedVar);
+                resultantTriple.third = (IAType) context.getOutputTypeEnvironment(workingOp).getVarType(matchedVar);
+                optFuncExpr.setSourceVar(resultantTriple.first, matchedVar);
+            }
+
+        } else if (workingOp != null) {
+            UnnestOperator workingOpAsUnnest = (UnnestOperator) workingOp;
+            resultantTriple.first = optFuncExpr.findLogicalVar(workingOpAsUnnest.getVariable());
+            resultantTriple.third =
+                    (IAType) context.getOutputTypeEnvironment(workingOp).getVarType(workingOpAsUnnest.getVariable());
+            optFuncExpr.setSourceVar(resultantTriple.first, workingOpAsUnnest.getVariable());
+        }
+
+        return resultantTriple;
+    }
+
+    private static int findAssignVarIndex(AbstractLogicalOperator workingOp, LogicalVariable varFromParentAssign) {
+        if (workingOp.getOperatorTag() != LogicalOperatorTag.ASSIGN) {
+            return -1;
+        }
+        AssignOperator workingOpAsAssign = (AssignOperator) workingOp;
+
+        // Match our parent assign variable to a variable used in our working assign.
+        List<LogicalVariable> variablesUsedInWorkingAssign = new ArrayList<>();
+        for (Mutable<ILogicalExpression> assignExpr : workingOpAsAssign.getExpressions()) {
+            assignExpr.getValue().getUsedVariables(variablesUsedInWorkingAssign);
+            int pos = variablesUsedInWorkingAssign.indexOf(varFromParentAssign);
+            if (pos != -1) {
+                return pos;
+            }
+        }
+        return -1;
+    }
 }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayBTreeAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayBTreeAccessMethod.java
new file mode 100644
index 0000000..44e4a18
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/ArrayBTreeAccessMethod.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.asterix.optimizer.rules.am;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.asterix.common.config.DatasetConfig.IndexType;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.metadata.utils.ArrayIndexUtil;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.om.utils.NonTaggedFormatUtil;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+
+// TODO (GLENN): Refactor the BTreeAccessMethod class and this class to extend a new "AbstractBTreeAccessMethod" class.
+/**
+ * Class for helping rewrite rules to choose and apply array BTree indexes.
+ */
+public class ArrayBTreeAccessMethod extends BTreeAccessMethod {
+    public static final ArrayBTreeAccessMethod INSTANCE = new ArrayBTreeAccessMethod();
+
+    @Override
+    public boolean matchAllIndexExprs(Index index) {
+        // Similar to BTree "matchAllIndexExprs", we only require all expressions to be matched if this is a composite
+        // key index with an unknowable field.
+        return ((Index.ArrayIndexDetails) index.getIndexDetails()).getElementList().stream()
+                .map(e -> e.getProjectList().size()).reduce(0, Integer::sum) > 1 && hasUnknownableField(index);
+    }
+
+    @Override
+    public boolean matchPrefixIndexExprs(Index index) {
+        return !matchAllIndexExprs(index);
+    }
+
+    private boolean hasUnknownableField(Index index) {
+        if (index.isSecondaryIndex() && index.getIndexDetails().isOverridingKeyFieldTypes() && !index.isEnforced()) {
+            return true;
+        }
+        for (Index.ArrayIndexElement e : ((Index.ArrayIndexDetails) index.getIndexDetails()).getElementList()) {
+            for (int i = 0; i < e.getProjectList().size(); i++) {
+                if (NonTaggedFormatUtil.isOptional(e.getTypeList().get(i))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public ILogicalOperator createIndexSearchPlan(List<Mutable<ILogicalOperator>> afterTopOpRefs,
+            Mutable<ILogicalOperator> topOpRef, Mutable<ILogicalExpression> conditionRef,
+            List<Mutable<ILogicalOperator>> assignBeforeTheOpRefs, OptimizableOperatorSubTree indexSubTree,
+            OptimizableOperatorSubTree probeSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx,
+            boolean retainInput, boolean retainMissing, boolean requiresBroadcast, IOptimizationContext context,
+            LogicalVariable newMissingPlaceHolderForLOJ) throws AlgebricksException {
+
+        Index.ArrayIndexDetails chosenIndexDetails = (Index.ArrayIndexDetails) chosenIndex.getIndexDetails();
+        List<List<String>> chosenIndexKeyFieldNames = new ArrayList<>();
+        List<IAType> chosenIndexKeyFieldTypes = new ArrayList<>();
+        List<Integer> chosenIndexKeyFieldSourceIndicators = new ArrayList<>();
+        for (Index.ArrayIndexElement e : chosenIndexDetails.getElementList()) {
+            for (int i = 0; i < e.getProjectList().size(); i++) {
+                chosenIndexKeyFieldNames
+                        .add(ArrayIndexUtil.getFlattenedKeyFieldNames(e.getUnnestList(), e.getProjectList().get(i)));
+                chosenIndexKeyFieldTypes.add(e.getTypeList().get(i));
+                chosenIndexKeyFieldSourceIndicators.add(e.getSourceIndicator());
+            }
+        }
+
+        return createBTreeIndexSearchPlan(afterTopOpRefs, topOpRef, conditionRef, assignBeforeTheOpRefs, indexSubTree,
+                probeSubTree, chosenIndex, analysisCtx, retainInput, retainMissing, requiresBroadcast, context,
+                newMissingPlaceHolderForLOJ, chosenIndexKeyFieldNames, chosenIndexKeyFieldTypes,
+                chosenIndexKeyFieldSourceIndicators);
+    }
+
+    @Override
+    protected IAType getIndexedKeyType(Index.IIndexDetails chosenIndexDetails, int keyPos) throws CompilationException {
+        // TODO (GLENN): This assumes a flattened key list. Refactor / clarify this when removing depth indicators.
+        Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) chosenIndexDetails;
+        int elementPos = 0;
+        for (Index.ArrayIndexElement e : arrayIndexDetails.getElementList()) {
+            for (int i = 0; i < e.getProjectList().size(); i++) {
+                if (elementPos == keyPos) {
+                    return e.getTypeList().get(i);
+                }
+                elementPos++;
+            }
+        }
+
+        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                "No array index element found, but using " + "an array access method.");
+    }
+
+    @Override
+    public boolean matchIndexType(IndexType indexType) {
+        return indexType == IndexType.ARRAY;
+    }
+
+    @Override
+    public String getName() {
+        return "ARRAY_BTREE_ACCESS_METHOD";
+    }
+
+    @Override
+    public int compareTo(IAccessMethod o) {
+        return this.getName().compareTo(o.getName());
+    }
+
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
index 21892cc..cad5f12 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
@@ -96,7 +96,8 @@ public class BTreeAccessMethod implements IAccessMethod {
     // That is, this function can produce false positive results if it is set to true.
     // In this case, an index-search alone cannot replace the given SELECT condition and
     // that SELECT condition needs to be applied after the index-search to get the correct results.
-    // For B+Tree indexes, there are no false positive results unless the given index is a composite index.
+    // For B+Tree indexes, there are no false positive results unless the given index is a composite index or an array
+    // index.
     private static final List<Pair<FunctionIdentifier, Boolean>> FUNC_IDENTIFIERS = Collections
             .unmodifiableList(Arrays.asList(new Pair<FunctionIdentifier, Boolean>(AlgebricksBuiltinFunctions.EQ, false),
                     new Pair<FunctionIdentifier, Boolean>(AlgebricksBuiltinFunctions.LE, false),
@@ -127,7 +128,8 @@ public class BTreeAccessMethod implements IAccessMethod {
     public boolean matchAllIndexExprs(Index index) {
         // require all expressions to be matched if this is a composite key index which has an unknownable key field.
         // because we only add a tuple to the index if all its key fields are not null/missing.
-        return index.getKeyFieldTypes().size() > 1 && hasUnknownableField(index);
+        return ((Index.ValueIndexDetails) index.getIndexDetails()).getKeyFieldTypes().size() > 1
+                && hasUnknownableField(index);
     }
 
     @Override
@@ -136,10 +138,10 @@ public class BTreeAccessMethod implements IAccessMethod {
     }
 
     private boolean hasUnknownableField(Index index) {
-        if (index.isSecondaryIndex() && index.isOverridingKeyFieldTypes() && !index.isEnforced()) {
+        if (index.isSecondaryIndex() && index.getIndexDetails().isOverridingKeyFieldTypes() && !index.isEnforced()) {
             return true;
         }
-        for (IAType fieldType : index.getKeyFieldTypes()) {
+        for (IAType fieldType : ((Index.ValueIndexDetails) index.getIndexDetails()).getKeyFieldTypes()) {
             if (NonTaggedFormatUtil.isOptional(fieldType)) {
                 return true;
             }
@@ -320,6 +322,26 @@ public class BTreeAccessMethod implements IAccessMethod {
             OptimizableOperatorSubTree probeSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx,
             boolean retainInput, boolean retainMissing, boolean requiresBroadcast, IOptimizationContext context,
             LogicalVariable newMissingPlaceHolderForLOJ) throws AlgebricksException {
+
+        Index.ValueIndexDetails chosenIndexDetails = (Index.ValueIndexDetails) chosenIndex.getIndexDetails();
+        List<List<String>> chosenIndexKeyFieldNames = chosenIndexDetails.getKeyFieldNames();
+        List<IAType> chosenIndexKeyFieldTypes = chosenIndexDetails.getKeyFieldTypes();
+        List<Integer> chosenIndexKeyFieldSourceIndicators = chosenIndexDetails.getKeyFieldSourceIndicators();
+
+        return createBTreeIndexSearchPlan(afterTopOpRefs, topOpRef, conditionRef, assignBeforeTheOpRefs, indexSubTree,
+                probeSubTree, chosenIndex, analysisCtx, retainInput, retainMissing, requiresBroadcast, context,
+                newMissingPlaceHolderForLOJ, chosenIndexKeyFieldNames, chosenIndexKeyFieldTypes,
+                chosenIndexKeyFieldSourceIndicators);
+    }
+
+    protected ILogicalOperator createBTreeIndexSearchPlan(List<Mutable<ILogicalOperator>> afterTopOpRefs,
+            Mutable<ILogicalOperator> topOpRef, Mutable<ILogicalExpression> conditionRef,
+            List<Mutable<ILogicalOperator>> assignBeforeTheOpRefs, OptimizableOperatorSubTree indexSubTree,
+            OptimizableOperatorSubTree probeSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx,
+            boolean retainInput, boolean retainMissing, boolean requiresBroadcast, IOptimizationContext context,
+            LogicalVariable newMissingPlaceHolderForLOJ, List<List<String>> chosenIndexKeyFieldNames,
+            List<IAType> chosenIndexKeyFieldTypes, List<Integer> chosenIndexKeyFieldSourceIndicators)
+            throws AlgebricksException {
         Dataset dataset = indexSubTree.getDataset();
         ARecordType recordType = indexSubTree.getRecordType();
         ARecordType metaRecordType = indexSubTree.getMetaRecordType();
@@ -370,12 +392,12 @@ public class BTreeAccessMethod implements IAccessMethod {
         for (Pair<Integer, Integer> exprIndex : exprAndVarList) {
             // Position of the field of matchedFuncExprs.get(exprIndex) in the chosen index's indexed exprs.
             IOptimizableFuncExpr optFuncExpr = analysisCtx.getMatchedFuncExpr(exprIndex.first);
-            int keyPos = indexOf(optFuncExpr.getFieldName(0), optFuncExpr.getFieldSource(0),
-                    chosenIndex.getKeyFieldNames(), chosenIndex.getKeyFieldSourceIndicators());
+            int keyPos = indexOf(optFuncExpr.getFieldName(0), optFuncExpr.getFieldSource(0), chosenIndexKeyFieldNames,
+                    chosenIndexKeyFieldSourceIndicators);
             if (keyPos < 0 && optFuncExpr.getNumLogicalVars() > 1) {
                 // If we are optimizing a join, the matching field may be the second field name.
-                keyPos = indexOf(optFuncExpr.getFieldName(1), optFuncExpr.getFieldSource(1),
-                        chosenIndex.getKeyFieldNames(), chosenIndex.getKeyFieldSourceIndicators());
+                keyPos = indexOf(optFuncExpr.getFieldName(1), optFuncExpr.getFieldSource(1), chosenIndexKeyFieldNames,
+                        chosenIndexKeyFieldSourceIndicators);
             }
             if (keyPos < 0) {
                 throw CompilationException.create(ErrorCode.NO_INDEX_FIELD_NAME_FOR_GIVEN_FUNC_EXPR,
@@ -385,7 +407,7 @@ public class BTreeAccessMethod implements IAccessMethod {
             // The second expression will not be null only if we are creating an EQ search predicate
             // with a FLOAT or a DOUBLE constant that will be fed into an INTEGER index.
             // This is required because of type-casting. Refer to AccessMethodUtils.createSearchKeyExpr for details.
-            IAType indexedFieldType = chosenIndex.getKeyFieldTypes().get(keyPos);
+            IAType indexedFieldType = chosenIndexKeyFieldTypes.get(keyPos);
             Triple<ILogicalExpression, ILogicalExpression, Boolean> returnedSearchKeyExpr =
                     AccessMethodUtils.createSearchKeyExpr(chosenIndex, optFuncExpr, indexedFieldType, probeSubTree);
             ILogicalExpression searchKeyExpr = returnedSearchKeyExpr.first;
@@ -669,7 +691,7 @@ public class BTreeAccessMethod implements IAccessMethod {
             indexSearchOp = AccessMethodUtils.createRestOfIndexSearchPlan(afterTopOpRefs, topOpRef, conditionRef,
                     assignBeforeTheOpRefs, dataSourceOp, dataset, recordType, metaRecordType, secondaryIndexUnnestOp,
                     context, true, retainInput, retainMissing, false, chosenIndex, analysisCtx, indexSubTree,
-                    newMissingPlaceHolderForLOJ);
+                    probeSubTree, newMissingPlaceHolderForLOJ);
 
             // Replaces the datasource scan with the new plan rooted at
             // Get dataSourceRef operator -
@@ -895,7 +917,8 @@ public class BTreeAccessMethod implements IAccessMethod {
         return limit;
     }
 
-    private boolean relaxLimitTypeToInclusive(Index chosenIndex, int keyPos, boolean realTypeConvertedToIntegerType) {
+    private boolean relaxLimitTypeToInclusive(Index chosenIndex, int keyPos, boolean realTypeConvertedToIntegerType)
+            throws CompilationException {
         // For a non-enforced index or an enforced index that stores a casted value on the given index,
         // we need to apply the following transformation.
         // For an index on a closed field, this transformation is not necessary since the value between
@@ -922,8 +945,8 @@ public class BTreeAccessMethod implements IAccessMethod {
             return true;
         }
 
-        if (chosenIndex.isOverridingKeyFieldTypes() && !chosenIndex.isEnforced()) {
-            IAType indexedKeyType = chosenIndex.getKeyFieldTypes().get(keyPos);
+        if (chosenIndex.getIndexDetails().isOverridingKeyFieldTypes() && !chosenIndex.isEnforced()) {
+            IAType indexedKeyType = getIndexedKeyType(chosenIndex.getIndexDetails(), keyPos);
             if (NonTaggedFormatUtil.isOptional(indexedKeyType)) {
                 indexedKeyType = ((AUnionType) indexedKeyType).getActualType();
             }
@@ -943,6 +966,10 @@ public class BTreeAccessMethod implements IAccessMethod {
         return false;
     }
 
+    protected IAType getIndexedKeyType(Index.IIndexDetails chosenIndexDetails, int keyPos) throws CompilationException {
+        return ((Index.ValueIndexDetails) chosenIndexDetails).getKeyFieldTypes().get(keyPos);
+    }
+
     private boolean probeIsOnLhs(IOptimizableFuncExpr optFuncExpr, OptimizableOperatorSubTree probeSubTree) {
         if (probeSubTree == null) {
             if (optFuncExpr.getConstantExpressions().length == 0) {
@@ -1011,6 +1038,11 @@ public class BTreeAccessMethod implements IAccessMethod {
     }
 
     @Override
+    public boolean matchIndexType(IndexType indexType) {
+        return indexType == IndexType.BTREE;
+    }
+
+    @Override
     public String getName() {
         return "BTREE_ACCESS_METHOD";
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java
index 84ee41e..b21cf12 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java
@@ -21,6 +21,7 @@ package org.apache.asterix.optimizer.rules.am;
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.asterix.common.config.DatasetConfig.IndexType;
 import org.apache.asterix.metadata.entities.Index;
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -66,6 +67,14 @@ public interface IAccessMethod extends Comparable<IAccessMethod> {
             IOptimizationContext context, IVariableTypeEnvironment typeEnvironment) throws AlgebricksException;
 
     /**
+     * Indicates whether this access method is applicable for the given index type.
+     *
+     * @return boolean
+     * @param indexType
+     */
+    public boolean matchIndexType(IndexType indexType);
+
+    /**
      * Indicates whether all index expressions must be matched in order for this
      * index to be applicable.
      *
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
index 199f878..6a5964d 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
@@ -94,6 +94,7 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
     protected static Map<FunctionIdentifier, List<IAccessMethod>> accessMethods = new HashMap<>();
 
     static {
+        registerAccessMethod(ArrayBTreeAccessMethod.INSTANCE, accessMethods);
         registerAccessMethod(BTreeAccessMethod.INSTANCE, accessMethods);
         registerAccessMethod(RTreeAccessMethod.INSTANCE, accessMethods);
         registerAccessMethod(InvertedIndexAccessMethod.INSTANCE, accessMethods);
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java
index bd5d82a..ab0ae5f 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java
@@ -34,6 +34,7 @@ import org.apache.asterix.metadata.declared.DatasetDataSource;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.metadata.utils.ArrayIndexUtil;
 import org.apache.asterix.metadata.utils.DatasetUtil;
 import org.apache.asterix.metadata.utils.KeyFieldTypeUtil;
 import org.apache.asterix.om.functions.BuiltinFunctions;
@@ -121,7 +122,7 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
 
             for (int i = 0; i < analysisCtx.getMatchedFuncExprs().size(); i++) {
                 IOptimizableFuncExpr optFuncExpr = analysisCtx.getMatchedFuncExpr(i);
-                boolean found = findMacthedExprFieldName(optFuncExpr, op, dataset, itemType, datasetIndexes, context,
+                boolean found = findMatchedExprFieldName(optFuncExpr, op, dataset, itemType, datasetIndexes, context,
                         filterSourceIndicator);
                 // the field name source should be consistent with the filter source indicator
                 if (found && optFuncExpr.getFieldName(0).equals(filterFieldName)
@@ -212,7 +213,9 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
                     AbstractFunctionCallExpression f = (AbstractFunctionCallExpression) unnestExpr;
                     FunctionIdentifier fid = f.getFunctionIdentifier();
                     if (!fid.equals(BuiltinFunctions.INDEX_SEARCH)) {
-                        throw new IllegalStateException();
+                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                unnestMapOp.getSourceLocation(),
+                                "Illegal function found, expected an " + "index-search.");
                     }
                     AccessMethodJobGenParams jobGenParams = new AccessMethodJobGenParams();
                     jobGenParams.readFromFuncArgs(f.getArguments());
@@ -308,14 +311,17 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
                     }
                     break;
                 case ORDER:
+                case DISTINCT:
                     ILogicalOperator child = intersectOrSortOrSplit.getValue().getInputs().get(0).getValue();
                     if (child.getOperatorTag().equals(LogicalOperatorTag.UNNEST_MAP)) {
                         UnnestMapOperator secondaryMap = (UnnestMapOperator) child;
 
-                        propagateFilterInSecondaryUnnsetMap(secondaryMap, filterType, context);
-
-                        setPrimaryFilterVar(primaryOp, secondaryMap.getPropagateIndexMinFilterVar(),
-                                secondaryMap.getPropagateIndexMaxFilterVar(), context);
+                        // If we are already propagating our index filter, do not repeat this action.
+                        if (!secondaryMap.propagateIndexFilter()) {
+                            propagateFilterInSecondaryUnnsetMap(secondaryMap, filterType, context);
+                            setPrimaryFilterVar(primaryOp, secondaryMap.getPropagateIndexMinFilterVar(),
+                                    secondaryMap.getPropagateIndexMaxFilterVar(), context);
+                        }
                     }
                     break;
 
@@ -495,7 +501,7 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
         }
     }
 
-    private boolean findMacthedExprFieldName(IOptimizableFuncExpr optFuncExpr, AbstractLogicalOperator op,
+    private boolean findMatchedExprFieldName(IOptimizableFuncExpr optFuncExpr, AbstractLogicalOperator op,
             Dataset dataset, ARecordType filterSourceType, List<Index> datasetIndexes, IOptimizationContext context,
             Integer filterSourceIndicator) throws AlgebricksException {
         AbstractLogicalOperator descendantOp = (AbstractLogicalOperator) op.getInputs().get(0).getValue();
@@ -557,7 +563,9 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
                         AbstractFunctionCallExpression f = (AbstractFunctionCallExpression) unnestExpr;
                         FunctionIdentifier fid = f.getFunctionIdentifier();
                         if (!fid.equals(BuiltinFunctions.INDEX_SEARCH)) {
-                            throw new IllegalStateException();
+                            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                    unnestMapOp.getSourceLocation(),
+                                    "Illegal function found, expected an " + "index-search.");
                         }
                         AccessMethodJobGenParams jobGenParams = new AccessMethodJobGenParams();
                         jobGenParams.readFromFuncArgs(f.getArguments());
@@ -569,6 +577,11 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
                             }
                         }
                     }
+                    if (index == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                unnestMapOp.getSourceLocation(),
+                                "Could not find the corresponding index for an" + " index search.");
+                    }
 
                     IAType metaItemType = ((MetadataProvider) context.getMetadataProvider())
                             .findType(dataset.getMetaItemTypeDataverseName(), dataset.getMetaItemTypeName());
@@ -584,8 +597,40 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
                         fieldName = dataset.getPrimaryKeys().get(idx);
                         keySource = getKeySource(DatasetUtil.getKeySourceIndicators(dataset), idx);
                     } else {
-                        fieldName = index.getKeyFieldNames().get(varIndex);
-                        keySource = getKeySource(index.getKeyFieldSourceIndicators(), varIndex);
+                        List<List<String>> keyFieldNames;
+                        List<Integer> keySources;
+                        switch (Index.IndexCategory.of(index.getIndexType())) {
+                            case ARRAY:
+                                Index.ArrayIndexDetails arrayIndexDetails =
+                                        (Index.ArrayIndexDetails) index.getIndexDetails();
+                                keyFieldNames = new ArrayList<>();
+                                keySources = new ArrayList<>();
+                                for (Index.ArrayIndexElement e : arrayIndexDetails.getElementList()) {
+                                    for (List<String> project : e.getProjectList()) {
+                                        keyFieldNames.add(
+                                                ArrayIndexUtil.getFlattenedKeyFieldNames(e.getUnnestList(), project));
+                                        keySources.add(e.getSourceIndicator());
+                                    }
+                                }
+                                break;
+                            case VALUE:
+                                Index.ValueIndexDetails valueIndexDetails =
+                                        (Index.ValueIndexDetails) index.getIndexDetails();
+                                keyFieldNames = valueIndexDetails.getKeyFieldNames();
+                                keySources = valueIndexDetails.getKeyFieldSourceIndicators();
+                                break;
+                            case TEXT:
+                                Index.TextIndexDetails textIndexDetails =
+                                        (Index.TextIndexDetails) index.getIndexDetails();
+                                keyFieldNames = textIndexDetails.getKeyFieldNames();
+                                keySources = textIndexDetails.getKeyFieldSourceIndicators();
+                                break;
+                            default:
+                                throw new CompilationException(ErrorCode.COMPILATION_UNKNOWN_INDEX_TYPE,
+                                        String.valueOf(index.getIndexType()));
+                        }
+                        fieldName = keyFieldNames.get(varIndex);
+                        keySource = getKeySource(keySources, varIndex);
                     }
                     if (fieldName == null) {
                         return false;
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java
index 64f9068..d221fd1 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java
@@ -284,7 +284,7 @@ public class IntroducePrimaryIndexForAggregationRule implements IAlgebraicRewrit
         // #2. get all indexes and look for the primary one
         List<Index> indexes = mp.getDatasetIndexes(dataset.getDataverseName(), dataset.getDatasetName());
         for (Index index : indexes) {
-            if (index.getKeyFieldNames().isEmpty()) {
+            if (index.isPrimaryKeyIndex()) {
                 return Pair.of(dataset, index);
             }
         }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
index 4199ece..c64e517 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
@@ -30,6 +30,7 @@ import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.metadata.entities.Index;
+import org.apache.asterix.optimizer.rules.util.SelectInSubplanBranchCreator;
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.commons.lang3.mutable.MutableObject;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -122,11 +123,13 @@ public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMeth
     protected IVariableTypeEnvironment typeEnvironment = null;
     protected final OptimizableOperatorSubTree subTree = new OptimizableOperatorSubTree();
     protected List<Mutable<ILogicalOperator>> afterSelectRefs = null;
+    private final SelectInSubplanBranchCreator selectInSubplanBranchCreator = new SelectInSubplanBranchCreator();
 
     // Register access methods.
     protected static Map<FunctionIdentifier, List<IAccessMethod>> accessMethods = new HashMap<>();
 
     static {
+        registerAccessMethod(ArrayBTreeAccessMethod.INSTANCE, accessMethods);
         registerAccessMethod(BTreeAccessMethod.INSTANCE, accessMethods);
         registerAccessMethod(RTreeAccessMethod.INSTANCE, accessMethods);
         registerAccessMethod(InvertedIndexAccessMethod.INSTANCE, accessMethods);
@@ -370,6 +373,25 @@ public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMeth
                 analyzedAMs = new TreeMap<>();
             }
 
+            // If there exists a SUBPLAN in our plan, and we are conditioning on a variable,
+            // attempt to rewrite this subplan to allow an array-index AM to be introduced.
+            // This rewrite is to be used **solely** for the purpose of changing a DATA-SCAN into a
+            // non-index-only plan branch. No nodes from this rewrite will be used beyond this point. 
+            // If successful, this will create a non-index only plan that replaces the subplan's
+            // DATA-SCAN with a PIDX SEARCH <- DISTINCT <- ORDER <- SIDX SEARCH.
+            if (continueCheck && context.getPhysicalOptimizationConfig().isArrayIndexEnabled()) {
+                SelectOperator selectRewrite = selectInSubplanBranchCreator.createSelect(selectOp, context);
+                if (selectRewrite != null
+                        && checkAndApplyTheSelectTransformation(new MutableObject<>(selectRewrite), context)) {
+                    return true;
+
+                } else {
+                    // If this optimization or temp-branch creation was not successful, restore our state.
+                    selectRef = selectRefFromThisOp;
+                    selectOp = selectInSubplanBranchCreator.getOriginalSelect();
+                }
+            }
+
             // Check the condition of SELECT operator is a function call since
             // only function call can be transformed using available indexes.
             // If so, initialize the subtree information that will be used later to decide whether
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java
index 31245fb..8530dee 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java
@@ -455,7 +455,7 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
         ILogicalOperator primaryIndexUnnestOp = AccessMethodUtils.createRestOfIndexSearchPlan(afterTopOpRefs, topOpRef,
                 conditionRef, assignBeforeTopOpRefs, dataSourceScan, dataset, recordType, metaRecordType,
                 secondaryIndexUnnestOp, context, true, retainInput, retainNull, false, chosenIndex, analysisCtx,
-                indexSubTree, newNullPlaceHolderForLOJ);
+                indexSubTree, null, newNullPlaceHolderForLOJ);
 
         return primaryIndexUnnestOp;
     }
@@ -801,8 +801,8 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
                 isFilterableArgs.add(new MutableObject<ILogicalExpression>(inputSearchVarRef));
                 // Since we are optimizing a join, the similarity threshold should be the only constant in the optimizable function expression.
                 isFilterableArgs.add(new MutableObject<ILogicalExpression>(optFuncExpr.getConstantExpr(0)));
-                isFilterableArgs.add(new MutableObject<ILogicalExpression>(
-                        AccessMethodUtils.createInt32Constant(chosenIndex.getGramLength())));
+                isFilterableArgs.add(new MutableObject<ILogicalExpression>(AccessMethodUtils.createInt32Constant(
+                        ((Index.TextIndexDetails) chosenIndex.getIndexDetails()).getGramLength())));
                 boolean usePrePost = optFuncExpr.containsPartialField() ? false : true;
                 isFilterableArgs.add(
                         new MutableObject<ILogicalExpression>(AccessMethodUtils.createBooleanConstant(usePrePost)));
@@ -977,7 +977,9 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
 
     private boolean isEditDistanceFuncJoinOptimizable(Index index, IOptimizableFuncExpr optFuncExpr) {
         if (index.isEnforced()) {
-            return isEditDistanceFuncCompatible(index.getKeyFieldTypes().get(0).getTypeTag(), index.getIndexType());
+            return isEditDistanceFuncCompatible(
+                    ((Index.TextIndexDetails) index.getIndexDetails()).getKeyFieldTypes().get(0).getTypeTag(),
+                    index.getIndexType());
         } else {
             return isEditDistanceFuncCompatible(optFuncExpr.getFieldType(0).getTypeTag(), index.getIndexType());
         }
@@ -1026,13 +1028,14 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
 
         if (typeTag == ATypeTag.STRING) {
             AString astr = (AString) listOrStrObj;
+            int gramLength = ((Index.TextIndexDetails) index.getIndexDetails()).getGramLength();
             // Compute merge threshold depending on the query grams contain pre- and postfixing
             if (optFuncExpr.containsPartialField()) {
-                mergeThreshold = (astr.getStringValue().length() - index.getGramLength() + 1)
-                        - edThresh.getIntegerValue() * index.getGramLength();
+                mergeThreshold =
+                        (astr.getStringValue().length() - gramLength + 1) - edThresh.getIntegerValue() * gramLength;
             } else {
-                mergeThreshold = (astr.getStringValue().length() + index.getGramLength() - 1)
-                        - edThresh.getIntegerValue() * index.getGramLength();
+                mergeThreshold =
+                        (astr.getStringValue().length() + gramLength - 1) - edThresh.getIntegerValue() * gramLength;
             }
         }
 
@@ -1120,7 +1123,9 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
 
     private boolean isFullTextContainsFuncJoinOptimizable(Index index, IOptimizableFuncExpr optFuncExpr) {
         if (index.isEnforced()) {
-            return isFullTextContainsFuncCompatible(index.getKeyFieldTypes().get(0).getTypeTag(), index.getIndexType());
+            return isFullTextContainsFuncCompatible(
+                    ((Index.TextIndexDetails) index.getIndexDetails()).getKeyFieldTypes().get(0).getTypeTag(),
+                    index.getIndexType());
         } else {
             return isFullTextContainsFuncCompatible(optFuncExpr.getFieldType(0).getTypeTag(), index.getIndexType());
         }
@@ -1212,7 +1217,7 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
         // Check that the constant search string has at least gramLength characters.
         if (strObj.getType().getTypeTag() == ATypeTag.STRING) {
             AString astr = (AString) strObj;
-            if (astr.getStringValue().length() >= index.getGramLength()) {
+            if (astr.getStringValue().length() >= ((Index.TextIndexDetails) index.getIndexDetails()).getGramLength()) {
                 return true;
             }
         }
@@ -1221,7 +1226,9 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
 
     private boolean isContainsFuncJoinOptimizable(Index index, IOptimizableFuncExpr optFuncExpr) {
         if (index.isEnforced()) {
-            return isContainsFuncCompatible(index.getKeyFieldTypes().get(0).getTypeTag(), index.getIndexType());
+            return isContainsFuncCompatible(
+                    ((Index.TextIndexDetails) index.getIndexDetails()).getKeyFieldTypes().get(0).getTypeTag(),
+                    index.getIndexType());
         } else {
             return isContainsFuncCompatible(optFuncExpr.getFieldType(0).getTypeTag(), index.getIndexType());
         }
@@ -1249,7 +1256,7 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
                 boolean prePost = (searchModifierType == SearchModifierType.CONJUNCTIVE
                         || searchModifierType == SearchModifierType.CONJUNCTIVE_EDIT_DISTANCE) ? false : true;
                 return BinaryTokenizerFactoryProvider.INSTANCE.getNGramTokenizerFactory(searchKeyType,
-                        index.getGramLength(), prePost, false);
+                        ((Index.TextIndexDetails) index.getIndexDetails()).getGramLength(), prePost, false);
             }
             default: {
                 throw new CompilationException(ErrorCode.NO_TOKENIZER_FOR_TYPE, index.getIndexType());
@@ -1280,11 +1287,12 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
                 switch (index.getIndexType()) {
                     case SINGLE_PARTITION_NGRAM_INVIX:
                     case LENGTH_PARTITIONED_NGRAM_INVIX: {
+                        int gramLength = ((Index.TextIndexDetails) index.getIndexDetails()).getGramLength();
                         // Edit distance on strings, filtered with overlapping grams.
                         if (searchModifierType == SearchModifierType.EDIT_DISTANCE) {
-                            return new EditDistanceSearchModifierFactory(index.getGramLength(), edThresh);
+                            return new EditDistanceSearchModifierFactory(gramLength, edThresh);
                         } else {
-                            return new ConjunctiveEditDistanceSearchModifierFactory(index.getGramLength(), edThresh);
+                            return new ConjunctiveEditDistanceSearchModifierFactory(gramLength, edThresh);
                         }
                     }
                     case SINGLE_PARTITION_WORD_INVIX:
@@ -1320,6 +1328,19 @@ public class InvertedIndexAccessMethod implements IAccessMethod {
     }
 
     @Override
+    public boolean matchIndexType(IndexType indexType) {
+        switch (indexType) {
+            case SINGLE_PARTITION_WORD_INVIX:
+            case SINGLE_PARTITION_NGRAM_INVIX:
+            case LENGTH_PARTITIONED_NGRAM_INVIX:
+            case LENGTH_PARTITIONED_WORD_INVIX:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
     public String getName() {
         return "INVERTED_INDEX_ACCESS_METHOD";
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/OptimizableOperatorSubTree.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/OptimizableOperatorSubTree.java
index 3cbba0d..b765bd0 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/OptimizableOperatorSubTree.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/OptimizableOperatorSubTree.java
@@ -72,6 +72,7 @@ public class OptimizableOperatorSubTree {
     private Mutable<ILogicalOperator> rootRef = null;
     private final List<Mutable<ILogicalOperator>> assignsAndUnnestsRefs = new ArrayList<>();
     private final List<AbstractLogicalOperator> assignsAndUnnests = new ArrayList<>();
+    private final Pair<Integer, Integer> lastMatchedDataSourceVars = new Pair<>(-1, -1);
     private Mutable<ILogicalOperator> dataSourceRef = null;
     private DataSourceType dataSourceType = DataSourceType.NO_DATASOURCE;
 
@@ -389,6 +390,8 @@ public class OptimizableOperatorSubTree {
         setRecordType(null);
         setMetaRecordType(null);
         setIxJoinOuterAdditionalRecordTypes(null);
+        lastMatchedDataSourceVars.first = -1;
+        lastMatchedDataSourceVars.second = -1;
     }
 
     /**
@@ -590,4 +593,12 @@ public class OptimizableOperatorSubTree {
         return varsToFieldNameMap;
     }
 
+    public Pair<Integer, Integer> getLastMatchedDataSourceVars() {
+        return lastMatchedDataSourceVars;
+    }
+
+    public void setLastMatchedDataSourceVars(int varIndex, int optVarIndex) {
+        this.lastMatchedDataSourceVars.first = varIndex;
+        this.lastMatchedDataSourceVars.second = optVarIndex;
+    }
 }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java
index c1ae61e..b28c15e 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java
@@ -308,7 +308,7 @@ public class RTreeAccessMethod implements IAccessMethod {
                         secondaryIndexUnnestOp, context, chosenIndex, retainInput, retainNull)
                 : AccessMethodUtils.createRestOfIndexSearchPlan(afterTopRefs, topRef, conditionRef, assignBeforeTopRefs,
                         dataSourceOp, dataset, recordType, metaRecordType, secondaryIndexUnnestOp, context, true,
-                        retainInput, retainNull, false, chosenIndex, analysisCtx, indexSubTree,
+                        retainInput, retainNull, false, chosenIndex, analysisCtx, indexSubTree, null,
                         newNullPlaceHolderForLOJ);
     }
 
@@ -385,6 +385,11 @@ public class RTreeAccessMethod implements IAccessMethod {
     }
 
     @Override
+    public boolean matchIndexType(IndexType indexType) {
+        return indexType == IndexType.RTREE;
+    }
+
+    @Override
     public String getName() {
         return "RTREE_ACCESS_METHOD";
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/SelectInSubplanBranchCreator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/SelectInSubplanBranchCreator.java
new file mode 100644
index 0000000..6efef16
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/SelectInSubplanBranchCreator.java
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.asterix.optimizer.rules.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.om.base.AInt16;
+import org.apache.asterix.om.base.AInt32;
+import org.apache.asterix.om.base.AInt64;
+import org.apache.asterix.om.base.AInt8;
+import org.apache.asterix.om.constants.AsterixConstantValue;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AggregateFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IAlgebricksConstantValue;
+import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SubplanOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities;
+import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+/**
+ * For use in writing a "throwaway" branch which removes NTS and subplan operators. The result of this invocation is to
+ * be given to the {@code IntroduceSelectAccessMethodRule} to check if an array index can be used.
+ * <br>
+ * If we are given the pattern (an existential query):
+ * <pre>
+ * SELECT_1(some variable)
+ * SUBPLAN_1 -------------------------------|
+ * (parent branch input)        AGGREGATE(NON-EMPTY-STREAM)
+ *                              SELECT_2(some predicate)
+ *                              (UNNEST/ASSIGN)*
+ *                              UNNEST(on variable)
+ *                              NESTED-TUPLE-SOURCE
+ * </pre>
+ * We return the following branch:
+ * <pre>
+ * SELECT_2(some predicate)
+ * (UNNEST/ASSIGN)*
+ * UNNEST(on variable)
+ * (parent branch input)
+ * </pre>
+ *
+ * If we are given the pattern (a universal query):
+ * <pre>
+ * SELECT_1(some variable AND array is not empty)
+ * SUBPLAN_1 -------------------------------|
+ * (parent branch input)        AGGREGATE(EMPTY-STREAM)
+ *                              SELECT_2(NOT(IF-MISSING-OR-NULL(some predicate)))
+ *                              (UNNEST/ASSIGN)*
+ *                              UNNEST(on variable)
+ *                              NESTED-TUPLE-SOURCE
+ * </pre>
+ * We return the following branch:
+ * <pre>
+ * SELECT_2(some predicate)  <--- removed the NOT(IF-MISSING-OR-NULL(...))!
+ * (UNNEST/ASSIGN)*
+ * UNNEST(on variable)
+ * (parent branch input)
+ * </pre>
+ *
+ * In the case of nested-subplans, we return a copy of the innermost SELECT followed by all relevant UNNEST/ASSIGNs.
+ */
+public class SelectInSubplanBranchCreator {
+    private final static List<IAlgebricksConstantValue> zerosAsAsterixConstants =
+            Arrays.asList(new IAlgebricksConstantValue[] { new AsterixConstantValue(new AInt64(0)),
+                    new AsterixConstantValue(new AInt32(0)), new AsterixConstantValue(new AInt16((short) 0)),
+                    new AsterixConstantValue(new AInt8((byte) 0)) });
+
+    private IOptimizationContext context;
+    private SourceLocation sourceLocation;
+    private SelectOperator originalSelectRoot;
+
+    /**
+     * Create a new branch to match that of the form:
+     *
+     * <pre>
+     * SELECT (...)
+     * (UNNEST/ASSIGN)*
+     * UNNEST
+     * ...
+     * </pre>
+     *
+     * Operators are *created* here, rather than just reconnected from the original branch.
+     */
+    public SelectOperator createSelect(SelectOperator originalSelect, IOptimizationContext context)
+            throws AlgebricksException {
+        // Reset our context.
+        this.sourceLocation = originalSelect.getSourceLocation();
+        this.originalSelectRoot = originalSelect;
+        this.context = context;
+
+        // We expect a) a SUBPLAN as input to this SELECT, and b) our SELECT to be conditioning on a variable.
+        if (!originalSelect.getInputs().get(0).getValue().getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)
+                || !originalSelect.getCondition().getValue().getExpressionTag().equals(LogicalExpressionTag.VARIABLE)) {
+            return null;
+        }
+        LogicalVariable originalSelectVar =
+                ((VariableReferenceExpression) originalSelect.getCondition().getValue()).getVariableReference();
+
+        // Additionally, verify that the subplan does not produce any other variable other than the SELECT var above.
+        SubplanOperator subplanOperator = (SubplanOperator) originalSelect.getInputs().get(0).getValue();
+        List<LogicalVariable> subplanProducedVars = new ArrayList<>();
+        VariableUtilities.getProducedVariables(subplanOperator, subplanProducedVars);
+        if (subplanProducedVars.size() != 1 || !subplanProducedVars.get(0).equals(originalSelectVar)) {
+            return null;
+        }
+
+        return traverseSubplanBranch(subplanOperator);
+    }
+
+    /**
+     * To undo this process is to return what was passed to us at {@code createSelect} time.
+     */
+    public SelectOperator getOriginalSelect() {
+        return originalSelectRoot;
+    }
+
+    private SelectOperator traverseSubplanBranch(SubplanOperator subplanOperator) throws AlgebricksException {
+        // We only expect one plan, and one root.
+        if (subplanOperator.getNestedPlans().size() > 1
+                || subplanOperator.getNestedPlans().get(0).getRoots().size() > 1) {
+            return null;
+        }
+
+        // This root of our "subplan" should always be an aggregate.
+        ILogicalOperator workingSubplanRoot = subplanOperator.getNestedPlans().get(0).getRoots().get(0).getValue();
+        AggregateOperator workingSubplanRootAsAggregate;
+        if (!workingSubplanRoot.getOperatorTag().equals(LogicalOperatorTag.AGGREGATE)) {
+            return null;
+        }
+        workingSubplanRootAsAggregate = (AggregateOperator) workingSubplanRoot;
+
+        // Try to find a SELECT that we can optimize (i.e. has a function call).
+        SelectOperator optimizableSelect = null;
+        for (Mutable<ILogicalOperator> opInput : workingSubplanRoot.getInputs()) {
+            ILogicalOperator subplanOrSelect = findSubplanOrSelect(opInput.getValue());
+            if (subplanOrSelect == null) {
+                return null;
+
+            } else if (subplanOrSelect.getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
+                optimizableSelect = traverseSubplanBranch((SubplanOperator) subplanOrSelect);
+
+            } else {
+                optimizableSelect = (SelectOperator) subplanOrSelect;
+                break;
+            }
+        }
+        if (optimizableSelect == null) {
+            return null;
+        }
+
+        // We have found a SELECT with a variable. Create a copy, and set this to our rewrite root.
+        SelectOperator newSelectOperator = new SelectOperator(optimizableSelect.getCondition(),
+                optimizableSelect.getRetainMissing(), optimizableSelect.getMissingPlaceholderVariable());
+
+        // Ensure that this SELECT represents a predicate for an existential query, and is a query we can optimize.
+        newSelectOperator = normalizeSelectCondition(workingSubplanRootAsAggregate, newSelectOperator,
+                subplanOperator.getInputs().get(0).getValue());
+        if (newSelectOperator == null) {
+            return null;
+        }
+        newSelectOperator.setSourceLocation(sourceLocation);
+        newSelectOperator.setExecutionMode(optimizableSelect.getExecutionMode());
+
+        // Follow this SELECT to the root of our nested-plan branch (i.e. the NESTED-TUPLE-SOURCE).
+        ILogicalOperator workingOriginalOperator = optimizableSelect, workingNewOperator = newSelectOperator;
+        UnnestOperator bottommostNewUnnest = null;
+        while (!workingOriginalOperator.getOperatorTag().equals(LogicalOperatorTag.NESTEDTUPLESOURCE)) {
+            if (workingOriginalOperator.getInputs().isEmpty()) {
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                        workingSubplanRoot.getSourceLocation(),
+                        "NESTED-TUPLE-SOURCE expected in nested plan branch," + " but not found.");
+            }
+
+            switch (workingOriginalOperator.getOperatorTag()) {
+                case UNNEST:
+                    UnnestOperator originalUnnest = (UnnestOperator) workingOriginalOperator;
+                    UnnestOperator newUnnest =
+                            new UnnestOperator(originalUnnest.getVariable(), originalUnnest.getExpressionRef());
+                    newUnnest.setSourceLocation(sourceLocation);
+                    workingNewOperator.getInputs().add(new MutableObject<>(newUnnest));
+                    workingNewOperator = newUnnest;
+                    bottommostNewUnnest = (UnnestOperator) workingNewOperator;
+                    break;
+
+                case ASSIGN:
+                    AssignOperator originalAssign = (AssignOperator) workingOriginalOperator;
+                    AssignOperator newAssign =
+                            new AssignOperator(originalAssign.getVariables(), originalAssign.getExpressions());
+                    newAssign.setSourceLocation(sourceLocation);
+                    workingNewOperator.getInputs().add(new MutableObject<>(newAssign));
+                    workingNewOperator = newAssign;
+                    break;
+
+                case SUBPLAN:
+                    // TODO (GLENN): Work on supporting nested universal quantification.
+                    return null;
+
+                case AGGREGATE:
+                case SELECT:
+                    break;
+
+                default:
+                    return null;
+            }
+
+            workingOriginalOperator = workingOriginalOperator.getInputs().get(0).getValue();
+        }
+
+        // If we are working with universal quantification, then we must also check whether or not we have a conjunct 
+        // that asserts that the array should also be non-empty.
+        if (isUniversalQuantification(workingSubplanRootAsAggregate)
+                && !isArrayNonEmptyConjunctIncluded(bottommostNewUnnest, subplanOperator)) {
+            return null;
+        }
+
+        // We have added everything we need in our nested-plan branch. Now, connect the input of our SUBPLAN to our
+        // current working branch.
+        bottommostNewUnnest.getInputs().addAll(subplanOperator.getInputs());
+        OperatorManipulationUtil.computeTypeEnvironmentBottomUp(newSelectOperator, context);
+
+        return newSelectOperator;
+    }
+
+    private boolean isUniversalQuantification(AggregateOperator workingSubplanRoot) throws CompilationException {
+        AggregateFunctionCallExpression aggregateFunctionCallExpression =
+                (AggregateFunctionCallExpression) workingSubplanRoot.getExpressions().get(0).getValue();
+        if (aggregateFunctionCallExpression.getFunctionIdentifier().equals(BuiltinFunctions.EMPTY_STREAM)) {
+            return true;
+        } else if (aggregateFunctionCallExpression.getFunctionIdentifier().equals(BuiltinFunctions.NON_EMPTY_STREAM)) {
+            return false;
+        } else {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, workingSubplanRoot.getSourceLocation(),
+                    "Unexpected aggregate function: " + aggregateFunctionCallExpression.getFunctionIdentifier());
+        }
+    }
+
+    private boolean isArrayNonEmptyConjunctIncluded(UnnestOperator firstUnnestInNTS, SubplanOperator subplanOperator) {
+        UnnestingFunctionCallExpression unnestFunction =
+                (UnnestingFunctionCallExpression) firstUnnestInNTS.getExpressionRef().getValue();
+        VariableReferenceExpression unnestVarExpr =
+                (VariableReferenceExpression) unnestFunction.getArguments().get(0).getValue();
+        LogicalVariable arrayVariable = unnestVarExpr.getVariableReference();
+
+        // TODO (GLENN): The SELECT directly below the SUBPLAN is the only operator we explore. This does not cover
+        //  all predicates where the array may be non-empty (say, having an existential predicate located after this 
+        //  subplan).
+        if (!subplanOperator.getInputs().get(0).getValue().getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+            return false;
+        }
+        SelectOperator subplanInputOperator = (SelectOperator) subplanOperator.getInputs().get(0).getValue();
+        ILogicalExpression selectCondExpr = subplanInputOperator.getCondition().getValue();
+        List<Mutable<ILogicalExpression>> conjunctsFromSelect = new ArrayList<>();
+        if (selectCondExpr.splitIntoConjuncts(conjunctsFromSelect)) {
+            // We have a collection of conjuncts. Analyze each conjunct w/ a function.
+            for (Mutable<ILogicalExpression> mutableConjuct : conjunctsFromSelect) {
+                ILogicalExpression workingConjunct = mutableConjuct.getValue();
+                if (workingConjunct.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+                        && analyzeConjunctForArrayNonEmptiness(arrayVariable,
+                                (ScalarFunctionCallExpression) workingConjunct)) {
+                    return true;
+                }
+            }
+
+            // No such conjunct found.
+            return false;
+        }
+
+        if (!selectCondExpr.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)) {
+            return false;
+        }
+        return analyzeConjunctForArrayNonEmptiness(arrayVariable, (ScalarFunctionCallExpression) selectCondExpr);
+    }
+
+    private boolean analyzeConjunctForArrayNonEmptiness(LogicalVariable arrayVariable,
+            ScalarFunctionCallExpression workingSelectCondExpr) {
+        // Handle the conjunct: LEN(arrayVar) > 0
+        if (workingSelectCondExpr.getFunctionIdentifier().equals(BuiltinFunctions.GT)) {
+            ILogicalExpression firstArg = workingSelectCondExpr.getArguments().get(0).getValue();
+            ILogicalExpression secondArg = workingSelectCondExpr.getArguments().get(1).getValue();
+
+            if (firstArg.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+                    && ((ScalarFunctionCallExpression) firstArg).getFunctionIdentifier().equals(BuiltinFunctions.LEN)) {
+                ScalarFunctionCallExpression lenFunction = (ScalarFunctionCallExpression) firstArg;
+                List<LogicalVariable> usedVariables = new ArrayList<>();
+                lenFunction.getUsedVariables(usedVariables);
+
+                return usedVariables.contains(arrayVariable)
+                        && secondArg.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
+                        && zerosAsAsterixConstants.contains(((ConstantExpression) secondArg).getValue());
+            }
+        }
+
+        // Handle the conjunct: 0 < LEN(arrayVar)
+        else if (workingSelectCondExpr.getFunctionIdentifier().equals(BuiltinFunctions.LT)) {
+            ILogicalExpression firstArg = workingSelectCondExpr.getArguments().get(0).getValue();
+            ILogicalExpression secondArg = workingSelectCondExpr.getArguments().get(1).getValue();
+
+            if (secondArg.getExpressionTag().equals(LogicalExpressionTag.FUNCTION_CALL)
+                    && ((ScalarFunctionCallExpression) secondArg).getFunctionIdentifier()
+                            .equals(BuiltinFunctions.LEN)) {
+                ScalarFunctionCallExpression lenFunction = (ScalarFunctionCallExpression) secondArg;
+                List<LogicalVariable> usedVariables = new ArrayList<>();
+                lenFunction.getUsedVariables(usedVariables);
+
+                return usedVariables.contains(arrayVariable)
+                        && firstArg.getExpressionTag().equals(LogicalExpressionTag.CONSTANT)
+                        && zerosAsAsterixConstants.contains(((ConstantExpression) firstArg).getValue());
+            }
+        }
+
+        // TODO (GLENN): Handle the cases 1) where the arrayVar is explicitly indexed, 2) the NOT function.
+        return false;
+    }
+
+    private SelectOperator normalizeSelectCondition(AggregateOperator aggregateOperator, SelectOperator selectOperator,
+            ILogicalOperator subplanInputOperator) throws AlgebricksException {
+        // The purpose of this function is to remove the NOT(IF-MISSING-OR-NULL(...)) functions for a universal
+        // quantification query. The {@code ArrayBTreeAccessMethod} does not recognize the former as optimizable
+        // functions, so we remove them here. This SELECT will never make it to the final query plan (after the
+        // {@code IntroduceSelectAccessMethodRule}), which allows us to get away with this logically incorrect branch.
+        if (!isUniversalQuantification(aggregateOperator)) {
+            // We are working with an existential quantification query. Do not modify the SELECT.
+            return selectOperator;
+
+        } else {
+            // We are working with a universal quantification query.
+            if (!subplanInputOperator.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+                return null;
+            }
+
+            ScalarFunctionCallExpression notFunction =
+                    (ScalarFunctionCallExpression) selectOperator.getCondition().getValue();
+            if (!notFunction.getFunctionIdentifier().equals(BuiltinFunctions.NOT)) {
+                return selectOperator;
+            }
+
+            ScalarFunctionCallExpression ifMissingOrNullFunction =
+                    (ScalarFunctionCallExpression) notFunction.getArguments().get(0).getValue();
+            if (!ifMissingOrNullFunction.getFunctionIdentifier().equals(BuiltinFunctions.IF_MISSING_OR_NULL)) {
+                return selectOperator;
+            }
+
+            Mutable<ILogicalExpression> newSelectCondition =
+                    new MutableObject<>(ifMissingOrNullFunction.getArguments().get(0).getValue().cloneExpression());
+            return new SelectOperator(newSelectCondition, selectOperator.getRetainMissing(),
+                    selectOperator.getMissingPlaceholderVariable());
+
+        }
+    }
+
+    private ILogicalOperator findSubplanOrSelect(ILogicalOperator operator) {
+        // We are trying to find a SELECT operator with a function call that is not "NOT(IF-MISSING-OR-NULL(...))".
+        if (operator.getOperatorTag().equals(LogicalOperatorTag.SELECT)) {
+            SelectOperator selectOperator = (SelectOperator) operator;
+            ILogicalExpression selectCondExpr = selectOperator.getCondition().getValue();
+            if (selectCondExpr.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
+
+                // Follow the chain of NOT(IF-MISSING-OR-NULL(...)) to see if we have a variable at the end.
+                ScalarFunctionCallExpression notFunction =
+                        (ScalarFunctionCallExpression) selectOperator.getCondition().getValue();
+                if (notFunction.getFunctionIdentifier().equals(BuiltinFunctions.NOT)) {
+                    ScalarFunctionCallExpression ifMissingOrNullFunction =
+                            (ScalarFunctionCallExpression) notFunction.getArguments().get(0).getValue();
+                    if (ifMissingOrNullFunction.getFunctionIdentifier().equals(BuiltinFunctions.IF_MISSING_OR_NULL)) {
+                        ILogicalExpression finalExpr = ifMissingOrNullFunction.getArguments().get(0).getValue();
+                        if (finalExpr.getExpressionTag() != LogicalExpressionTag.VARIABLE) {
+                            return selectOperator;
+                        }
+                    }
+
+                } else {
+                    return selectOperator;
+                }
+            }
+        } else if (operator.getOperatorTag().equals(LogicalOperatorTag.SUBPLAN)) {
+            // We have found an additional SUBPLAN branch to explore. Recurse w/ caller function.
+            return operator;
+        }
+
+        // No matching operator found. Recurse on current operator input.
+        if (operator.getInputs().isEmpty()) {
+            return null;
+        } else {
+            return findSubplanOrSelect(operator.getInputs().get(0).getValue());
+        }
+    }
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
index b32b05e..13bcfb6 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
@@ -27,7 +27,6 @@ import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.metadata.utils.KeyFieldTypeUtil;
 import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.om.types.ATypeTag;
-import org.apache.asterix.om.types.BuiltinType;
 import org.apache.asterix.om.types.IAType;
 import org.apache.asterix.om.utils.RecordUtil;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -184,137 +183,111 @@ public class ValidateUtil {
     /**
      * Validates the key fields that will be used as keys of an index.
      *
-     * @param recType
-     *            the record type
-     * @param keyFieldNames
-     *            a map of key fields that will be validated
-     * @param keyFieldTypes
-     *            a map of key types (if provided) that will be validated
      * @param indexType
      *            the type of the index that its key fields is being validated
+     * @param fieldType
+     *            a key field type
+     * @param displayFieldName
+     *            a field name to use for error reporting
      * @param sourceLoc
      *            the source location
      * @throws AlgebricksException
      */
-    public static void validateKeyFields(ARecordType recType, ARecordType metaRecType, List<List<String>> keyFieldNames,
-            List<Integer> keySourceIndicators, List<IAType> keyFieldTypes, IndexType indexType,
+    public static void validateIndexFieldType(IndexType indexType, IAType fieldType, List<String> displayFieldName,
             SourceLocation sourceLoc) throws AlgebricksException {
-        List<IAType> fieldTypes =
-                KeyFieldTypeUtil.getKeyTypes(recType, metaRecType, keyFieldNames, keySourceIndicators);
-        int pos = 0;
-        boolean openFieldCompositeIdx = false;
-        for (IAType fieldType : fieldTypes) {
-            List<String> fieldName = keyFieldNames.get(pos);
-            if (fieldType == null) {
-                fieldType = keyFieldTypes.get(pos);
-                if (keyFieldTypes.get(pos) == BuiltinType.AMISSING) {
-                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                            "A field with this name  \"" + fieldName + "\" could not be found.");
+        switch (indexType) {
+            case ARRAY:
+            case BTREE:
+                switch (fieldType.getTypeTag()) {
+                    case TINYINT:
+                    case SMALLINT:
+                    case INTEGER:
+                    case BIGINT:
+                    case FLOAT:
+                    case DOUBLE:
+                    case STRING:
+                    case BINARY:
+                    case DATE:
+                    case TIME:
+                    case DATETIME:
+                    case UNION:
+                    case UUID:
+                    case YEARMONTHDURATION:
+                    case DAYTIMEDURATION:
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "The field \"" + displayFieldName + "\" which is of type " + fieldType.getTypeTag()
+                                        + " cannot be indexed using the BTree index.");
                 }
-            } else if (openFieldCompositeIdx) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc, "A closed field \"" + fieldName
-                        + "\" could be only in a prefix part of the composite index, containing opened field.");
-            }
-            if (keyFieldTypes.get(pos) != BuiltinType.AMISSING
-                    && fieldType.getTypeTag() != keyFieldTypes.get(pos).getTypeTag()) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                        "A field \"" + fieldName + "\" is already defined with the type \"" + fieldType + "\"");
-            }
-            switch (indexType) {
-                case BTREE:
-                    switch (fieldType.getTypeTag()) {
-                        case TINYINT:
-                        case SMALLINT:
-                        case INTEGER:
-                        case BIGINT:
-                        case FLOAT:
-                        case DOUBLE:
-                        case STRING:
-                        case BINARY:
-                        case DATE:
-                        case TIME:
-                        case DATETIME:
-                        case UNION:
-                        case UUID:
-                        case YEARMONTHDURATION:
-                        case DAYTIMEDURATION:
-                            break;
-                        default:
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                    "The field \"" + fieldName + "\" which is of type " + fieldType.getTypeTag()
-                                            + " cannot be indexed using the BTree index.");
-                    }
-                    break;
-                case RTREE:
-                    switch (fieldType.getTypeTag()) {
-                        case POINT:
-                        case LINE:
-                        case RECTANGLE:
-                        case CIRCLE:
-                        case POLYGON:
-                        case GEOMETRY:
-                        case UNION:
-                            break;
-                        default:
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                    "The field \"" + fieldName + "\" which is of type " + fieldType.getTypeTag()
-                                            + " cannot be indexed using the RTree index.");
-                    }
-                    break;
-                case LENGTH_PARTITIONED_NGRAM_INVIX:
-                    switch (fieldType.getTypeTag()) {
-                        case STRING:
-                        case UNION:
-                            break;
-                        default:
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                    "The field \"" + fieldName + "\" which is of type " + fieldType.getTypeTag()
-                                            + " cannot be indexed using the Length Partitioned N-Gram index.");
-                    }
-                    break;
-                case LENGTH_PARTITIONED_WORD_INVIX:
-                    switch (fieldType.getTypeTag()) {
-                        case STRING:
-                        case MULTISET:
-                        case ARRAY:
-                        case UNION:
-                            break;
-                        default:
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                    "The field \"" + fieldName + "\" which is of type " + fieldType.getTypeTag()
-                                            + " cannot be indexed using the Length Partitioned Keyword index.");
-                    }
-                    break;
-                case SINGLE_PARTITION_NGRAM_INVIX:
-                    switch (fieldType.getTypeTag()) {
-                        case STRING:
-                        case UNION:
-                            break;
-                        default:
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                    "The field \"" + fieldName + "\" which is of type " + fieldType.getTypeTag()
-                                            + " cannot be indexed using the N-Gram index.");
-                    }
-                    break;
-                case SINGLE_PARTITION_WORD_INVIX:
-                    switch (fieldType.getTypeTag()) {
-                        case STRING:
-                        case MULTISET:
-                        case ARRAY:
-                        case UNION:
-                            break;
-                        default:
-                            throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                    "The field \"" + fieldName + "\" which is of type " + fieldType.getTypeTag()
-                                            + " cannot be indexed using the Keyword index.");
-                    }
-                    break;
-                default:
-                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                            "Invalid index type: " + indexType + ".");
-            }
-            pos++;
+                break;
+            case RTREE:
+                switch (fieldType.getTypeTag()) {
+                    case POINT:
+                    case LINE:
+                    case RECTANGLE:
+                    case CIRCLE:
+                    case POLYGON:
+                    case GEOMETRY:
+                    case UNION:
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "The field \"" + displayFieldName + "\" which is of type " + fieldType.getTypeTag()
+                                        + " cannot be indexed using the RTree index.");
+                }
+                break;
+            case LENGTH_PARTITIONED_NGRAM_INVIX:
+                switch (fieldType.getTypeTag()) {
+                    case STRING:
+                    case UNION:
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "The field \"" + displayFieldName + "\" which is of type " + fieldType.getTypeTag()
+                                        + " cannot be indexed using the Length Partitioned N-Gram index.");
+                }
+                break;
+            case LENGTH_PARTITIONED_WORD_INVIX:
+                switch (fieldType.getTypeTag()) {
+                    case STRING:
+                    case MULTISET:
+                    case ARRAY:
+                    case UNION:
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "The field \"" + displayFieldName + "\" which is of type " + fieldType.getTypeTag()
+                                        + " cannot be indexed using the Length Partitioned Keyword index.");
+                }
+                break;
+            case SINGLE_PARTITION_NGRAM_INVIX:
+                switch (fieldType.getTypeTag()) {
+                    case STRING:
+                    case UNION:
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "The field \"" + displayFieldName + "\" which is of type " + fieldType.getTypeTag()
+                                        + " cannot be indexed using the N-Gram index.");
+                }
+                break;
+            case SINGLE_PARTITION_WORD_INVIX:
+                switch (fieldType.getTypeTag()) {
+                    case STRING:
+                    case MULTISET:
+                    case ARRAY:
+                    case UNION:
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "The field \"" + displayFieldName + "\" which is of type " + fieldType.getTypeTag()
+                                        + " cannot be indexed using the Keyword index.");
+                }
+                break;
+            default:
+                throw new CompilationException(ErrorCode.COMPILATION_UNKNOWN_INDEX_TYPE, sourceLoc,
+                        String.valueOf(indexType));
         }
     }
-
 }
diff --git a/asterixdb/asterix-app/data/yelp-checkin/use-case-1.json b/asterixdb/asterix-app/data/yelp-checkin/use-case-1.json
new file mode 100644
index 0000000..4d66342
--- /dev/null
+++ b/asterixdb/asterix-app/data/yelp-checkin/use-case-1.json
@@ -0,0 +1,30 @@
+{ "business_id": "--1UhMGODdWsrMastO9DZw", "dates": [ "2016-04-26 19:49:16", "2016-08-30 18:36:57", "2016-10-15 02:45:18", "2016-11-18 01:54:50", "2017-04-20 18:39:06", "2017-05-03 17:58:02", "2019-03-19 22:04:48" ] }
+{ "business_id": "--EF5N7P70J_UYBTPypYlA", "dates": [ "2018-05-25 19:52:07", "2018-09-18 16:09:44", "2019-10-18 21:29:09" ] }
+{ "business_id": "--Ni3oJ4VOqfOEu7Sj2Vzg", "dates": [ "2019-06-07 17:54:58" ] }
+{ "business_id": "--Y1Adl1YUWfYIRSd8vkmA", "dates": [ "2011-05-03 20:54:05", "2011-08-23 20:49:45", "2014-12-04 06:13:01", "2016-11-16 19:25:55" ] }
+{ "business_id": "--YPwqIlRJrhHkJcjY3eiA", "dates": [ "2016-06-18 21:35:45", "2016-10-15 18:17:51" ] }
+{ "business_id": "--e8PjCNhEz32pprnPhCwQ", "dates": [ "2015-04-02 21:45:17" ] }
+{ "business_id": "--kinfHwmtdjz03g8B8z8Q", "dates": [ "2014-08-27 17:49:18", "2015-12-19 21:30:31", "2018-11-27 15:53:50" ] }
+{ "business_id": "--q6datkI-f0EoVheXNEeQ", "dates": [ "2014-01-28 20:56:04", "2014-11-16 16:11:58", "2015-11-15 19:21:53", "2015-11-15 19:33:39" ] }
+{ "business_id": "--qvQS4MigHPykD2GV0-zw", "dates": [ "2019-04-11 18:30:12" ] }
+{ "business_id": "--wIGbLEhlpl_UeAIyDmZQ", "dates": [ "2015-06-06 20:01:06", "2019-03-14 22:01:52" ] }
+{ "business_id": "-0FA-Qdi3SPYIoJz9UQw-A", "dates": [ "2018-09-29 18:55:17", "2018-10-20 16:48:05", "2018-10-20 22:20:24" ] }
+{ "business_id": "-0Hj1hb_XW6ybWq2M7QhGA", "dates": [ "2011-04-23 21:11:22", "2014-05-04 19:42:48", "2014-05-11 19:16:08", "2014-06-04 19:14:18", "2015-12-05 19:22:42", "2017-05-15 23:19:00" ] }
+{ "business_id": "-0KMvRFwDWdVBeTpT11iHw", "dates": [ "2012-07-13 21:43:57", "2016-12-24 02:27:31", "2017-08-31 00:35:26" ] }
+{ "business_id": "-0LPtgJC31FWMrMv317p0Q", "dates": [ "2013-04-13 12:35:33", "2013-08-19 23:35:49", "2013-10-04 19:14:56" ] }
+{ "business_id": "-0M3o2uWBnQZwd3hmfEwuw", "dates": [ "2016-09-10 19:26:19", "2018-09-08 14:15:37", "2019-09-13 22:47:25" ] }
+{ "business_id": "-0RRiWDtfnS16AKCtfvBZg", "dates": [ "2017-05-19 14:30:16", "2017-05-19 14:30:25", "2017-08-28 15:49:37", "2017-09-20 20:19:51", "2017-10-01 16:31:05", "2017-10-01 16:56:27", "2017-12-27 23:33:20" ] }
+{ "business_id": "-0Soj75v-XoRcf2ERr8Bmg", "dates": [ "2019-06-05 18:22:49" ] }
+{ "business_id": "-0ZumLlFjMh4ZW1z2nXGug", "dates": [ "2011-09-24 21:37:32", "2014-03-10 20:20:07", "2015-05-27 00:40:24", "2015-08-29 17:58:15", "2018-03-16 15:03:26" ] }
+{ "business_id": "-0aOudcaAyac0VJbMX-L1g", "dates": [ "2015-03-16 23:51:16", "2015-12-21 04:48:01", "2016-10-28 20:22:42", "2016-10-28 20:23:00" ] }
+{ "business_id": "-0b86isaXMY0v4g-V8GZ9Q", "dates": [ "2013-10-22 16:49:21", "2014-11-21 17:39:24" ] }
+{ "business_id": "-0d-BfFSU0bwLcnMaGRxYw", "dates": [ "2014-08-07 18:30:48", "2014-09-16 20:41:45", "2014-10-12 23:22:27", "2015-07-21 20:43:56", "2015-07-21 20:45:07" ] }
+{ "business_id": "-0jz6c3C6i7RG7Ag22K-Pg", "dates": [ "2015-05-02 19:49:05", "2015-05-06 03:52:18", "2015-09-26 01:13:19" ] }
+{ "business_id": "-0y3MZU2oYP8r1ruDP1bfQ", "dates": [ "2015-04-11 13:14:14", "2015-11-21 16:05:56", "2016-05-06 14:10:04", "2017-08-09 15:15:10", "2017-10-21 15:12:56" ] }
+{ "business_id": "-1BPe8UjF2_l3nVk-DFUjA", "dates": [ "2015-12-03 18:44:00", "2016-03-17 18:19:21", "2016-11-02 15:58:38" ] }
+{ "business_id": "-1E2CQu_38mkghvmZgCCRw", "dates": [ "2019-04-04 22:02:37" ] }
+{ "business_id": "-1wzk43IZ5D9Ysu6kzb5xA", "dates": [ "2019-02-27 14:03:08" ] }
+{ "business_id": "-23R9P2eG7VTc6DVLjFKzA", "dates": [ "2011-12-21 19:02:51", "2012-04-15 04:21:39", "2012-04-15 14:23:56", "2013-06-30 22:39:51", "2013-10-04 20:34:13", "2014-07-16 02:28:40" ] }
+{ "business_id": "-26MGfikhJiTfCI-GqmzhQ", "dates": [ "2018-06-13 20:16:07" ] }
+{ "business_id": "-2bLuJsMZ0WhI9daurVQNQ", "dates": [ "2015-05-29 16:46:17", "2015-06-01 15:03:53" ] }
+{ "business_id": "-2hDBMaza_ldqnZdiU06LQ", "dates": [ "2011-10-08 12:02:23", "2014-08-18 02:11:11", "2016-01-07 05:27:51", "2016-10-21 20:15:55", "2016-12-01 03:57:10", "2016-12-29 01:54:42", "2018-07-22 19:55:31", "2018-09-07 01:42:54", "2019-03-08 03:41:06" ] }
diff --git a/asterixdb/asterix-app/data/yelp-checkin/use-case-2.json b/asterixdb/asterix-app/data/yelp-checkin/use-case-2.json
new file mode 100644
index 0000000..2179005
--- /dev/null
+++ b/asterixdb/asterix-app/data/yelp-checkin/use-case-2.json
@@ -0,0 +1,31 @@
+{ "business_id": "--1UhMGODdWsrMastO9DZw", "checkin_times": { "dates": [ "2016-04-26", "2016-08-30", "2016-10-15", "2016-11-18", "2017-04-20", "2017-05-03", "2019-03-19" ], "times": [ "19:49:16", "18:36:57", "02:45:18", "01:54:50", "18:39:06", "17:58:02", "22:04:48" ] } }
+{ "business_id": "--EF5N7P70J_UYBTPypYlA", "checkin_times": { "dates": [ "2018-05-25", "2018-09-18", "2019-10-18" ], "times": [ "19:52:07", "16:09:44", "21:29:09" ] } }
+{ "business_id": "--Ni3oJ4VOqfOEu7Sj2Vzg", "checkin_times": { "dates": [ "2019-06-07" ], "times": [ "17:54:58" ] } }
+{ "business_id": "--Y1Adl1YUWfYIRSd8vkmA", "checkin_times": { "dates": [ "2011-05-03", "2011-08-23", "2014-12-04", "2016-11-16" ], "times": [ "20:54:05", "20:49:45", "06:13:01", "19:25:55" ] } }
+{ "business_id": "--YPwqIlRJrhHkJcjY3eiA", "checkin_times": { "dates": [ "2016-06-18", "2016-10-15" ], "times": [ "21:35:45", "18:17:51" ] } }
+{ "business_id": "--e8PjCNhEz32pprnPhCwQ", "checkin_times": { "dates": [ "2015-04-02" ], "times": [ "21:45:17" ] } }
+{ "business_id": "--kinfHwmtdjz03g8B8z8Q", "checkin_times": { "dates": [ "2014-08-27", "2015-12-19", "2018-11-27" ], "times": [ "17:49:18", "21:30:31", "15:53:50" ] } }
+{ "business_id": "--q6datkI-f0EoVheXNEeQ", "checkin_times": { "dates": [ "2014-01-28", "2014-11-16", "2015-11-15", "2015-11-15" ], "times": [ "20:56:04", "16:11:58", "19:21:53", "19:33:39" ] } }
+{ "business_id": "--qvQS4MigHPykD2GV0-zw", "checkin_times": { "dates": [ "2019-04-11" ], "times": [ "18:30:12" ] } }
+{ "business_id": "--wIGbLEhlpl_UeAIyDmZQ", "checkin_times": { "dates": [ "2015-06-06", "2019-03-14" ], "times": [ "20:01:06", "22:01:52" ] } }
+{ "business_id": "-0FA-Qdi3SPYIoJz9UQw-A", "checkin_times": { "dates": [ "2018-09-29", "2018-10-20", "2018-10-20" ], "times": [ "18:55:17", "16:48:05", "22:20:24" ] } }
+{ "business_id": "-0Hj1hb_XW6ybWq2M7QhGA", "checkin_times": { "dates": [ "2011-04-23", "2014-05-04", "2014-05-11", "2014-06-04", "2015-12-05", "2017-05-15" ], "times": [ "21:11:22", "19:42:48", "19:16:08", "19:14:18", "19:22:42", "23:19:00" ] } }
+{ "business_id": "-0KMvRFwDWdVBeTpT11iHw", "checkin_times": { "dates": [ "2012-07-13", "2016-12-24", "2017-08-31" ], "times": [ "21:43:57", "02:27:31", "00:35:26" ] } }
+{ "business_id": "-0LPtgJC31FWMrMv317p0Q", "checkin_times": { "dates": [ "2013-04-13", "2013-08-19", "2013-10-04" ], "times": [ "12:35:33", "23:35:49", "19:14:56" ] } }
+{ "business_id": "-0M3o2uWBnQZwd3hmfEwuw", "checkin_times": { "dates": [ "2016-09-10", "2018-09-08", "2019-09-13" ], "times": [ "19:26:19", "14:15:37", "22:47:25" ] } }
+{ "business_id": "-0RRiWDtfnS16AKCtfvBZg", "checkin_times": { "dates": [ "2017-05-19", "2017-05-19", "2017-08-28", "2017-09-20", "2017-10-01", "2017-10-01", "2017-12-27" ], "times": [ "14:30:16", "14:30:25", "15:49:37", "20:19:51", "16:31:05", "16:56:27", "23:33:20" ] } }
+{ "business_id": "-0Soj75v-XoRcf2ERr8Bmg", "checkin_times": { "dates": [ "2019-06-05" ], "times": [ "18:22:49" ] } }
+{ "business_id": "-0ZumLlFjMh4ZW1z2nXGug", "checkin_times": { "dates": [ "2011-09-24", "2014-03-10", "2015-05-27", "2015-08-29", "2018-03-16" ], "times": [ "21:37:32", "20:20:07", "00:40:24", "17:58:15", "15:03:26" ] } }
+{ "business_id": "-0aOudcaAyac0VJbMX-L1g", "checkin_times": { "dates": [ "2015-03-16", "2015-12-21", "2016-10-28", "2016-10-28" ], "times": [ "23:51:16", "04:48:01", "20:22:42", "20:23:00" ] } }
+{ "business_id": "-0b86isaXMY0v4g-V8GZ9Q", "checkin_times": { "dates": [ "2013-10-22", "2014-11-21" ], "times": [ "16:49:21", "17:39:24" ] } }
+{ "business_id": "-0d-BfFSU0bwLcnMaGRxYw", "checkin_times": { "dates": [ "2014-08-07", "2014-09-16", "2014-10-12", "2015-07-21", "2015-07-21" ], "times": [ "18:30:48", "20:41:45", "23:22:27", "20:43:56", "20:45:07" ] } }
+{ "business_id": "-0jz6c3C6i7RG7Ag22K-Pg", "checkin_times": { "dates": [ "2015-05-02", "2015-05-06", "2015-09-26" ], "times": [ "19:49:05", "03:52:18", "01:13:19" ] } }
+{ "business_id": "-0y3MZU2oYP8r1ruDP1bfQ", "checkin_times": { "dates": [ "2015-04-11", "2015-11-21", "2016-05-06", "2017-08-09", "2017-10-21" ], "times": [ "13:14:14", "16:05:56", "14:10:04", "15:15:10", "15:12:56" ] } }
+{ "business_id": "-1BPe8UjF2_l3nVk-DFUjA", "checkin_times": { "dates": [ "2015-12-03", "2016-03-17", "2016-11-02" ], "times": [ "18:44:00", "18:19:21", "15:58:38" ] } }
+{ "business_id": "-1E2CQu_38mkghvmZgCCRw", "checkin_times": { "dates": [ "2019-04-04" ], "times": [ "22:02:37" ] } }
+{ "business_id": "-1wzk43IZ5D9Ysu6kzb5xA", "checkin_times": { "dates": [ "2019-02-27" ], "times": [ "14:03:08" ] } }
+{ "business_id": "-23R9P2eG7VTc6DVLjFKzA", "checkin_times": { "dates": [ "2011-12-21", "2012-04-15", "2012-04-15", "2013-06-30", "2013-10-04", "2014-07-16" ], "times": [ "19:02:51", "04:21:39", "14:23:56", "22:39:51", "20:34:13", "02:28:40" ] } }
+{ "business_id": "-26MGfikhJiTfCI-GqmzhQ", "checkin_times": { "dates": [ "2018-06-13" ], "times": [ "20:16:07" ] } }
+{ "business_id": "-2bLuJsMZ0WhI9daurVQNQ", "checkin_times": { "dates": [ "2015-05-29", "2015-06-01" ], "times": [ "16:46:17", "15:03:53" ] } }
+{ "business_id": "-2hDBMaza_ldqnZdiU06LQ", "checkin_times": { "dates": [ "2011-10-08", "2014-08-18", "2016-01-07", "2016-10-21", "2016-12-01", "2016-12-29", "2018-07-22", "2018-09-07", "2019-03-08" ], "times": [ "12:02:23", "02:11:11", "05:27:51", "20:15:55", "03:57:10", "01:54:42", "19:55:31", "01:42:54", "03:41:06" ] } }
+
diff --git a/asterixdb/asterix-app/data/yelp-checkin/use-case-3.json b/asterixdb/asterix-app/data/yelp-checkin/use-case-3.json
new file mode 100644
index 0000000..c5a711f
--- /dev/null
+++ b/asterixdb/asterix-app/data/yelp-checkin/use-case-3.json
@@ -0,0 +1,30 @@
+{ "business_id": "--1UhMGODdWsrMastO9DZw", "checkin_times": [ {"date": "2016-04-26", "time": "19:49:16"}, {"date": "2016-08-30", "time": "18:36:57"}, {"date": "2016-10-15", "time": "02:45:18"}, {"date": "2016-11-18", "time": "01:54:50"}, {"date": "2017-04-20", "time": "18:39:06"}, {"date": "2017-05-03", "time": "17:58:02"}, {"date": "2019-03-19", "time": "22:04:48"} ] }
+{ "business_id": "--EF5N7P70J_UYBTPypYlA", "checkin_times": [ {"date": "2018-05-25", "time": "19:52:07"}, {"date": "2018-09-18", "time": "16:09:44"}, {"date": "2019-10-18", "time": "21:29:09"} ] }
+{ "business_id": "--Ni3oJ4VOqfOEu7Sj2Vzg", "checkin_times": [ {"date": "2019-06-07", "time": "17:54:58"} ] }
+{ "business_id": "--Y1Adl1YUWfYIRSd8vkmA", "checkin_times": [ {"date": "2011-05-03", "time": "20:54:05"}, {"date": "2011-08-23", "time": "20:49:45"}, {"date": "2014-12-04", "time": "06:13:01"}, {"date": "2016-11-16", "time": "19:25:55"} ] }
+{ "business_id": "--YPwqIlRJrhHkJcjY3eiA", "checkin_times": [ {"date": "2016-06-18", "time": "21:35:45"}, {"date": "2016-10-15", "time": "18:17:51"} ] }
+{ "business_id": "--e8PjCNhEz32pprnPhCwQ", "checkin_times": [ {"date": "2015-04-02", "time": "21:45:17"} ] }
+{ "business_id": "--kinfHwmtdjz03g8B8z8Q", "checkin_times": [ {"date": "2014-08-27", "time": "17:49:18"}, {"date": "2015-12-19", "time": "21:30:31"}, {"date": "2018-11-27", "time": "15:53:50"} ] }
+{ "business_id": "--q6datkI-f0EoVheXNEeQ", "checkin_times": [ {"date": "2014-01-28", "time": "20:56:04"}, {"date": "2014-11-16", "time": "16:11:58"}, {"date": "2015-11-15", "time": "19:21:53"}, {"date": "2015-11-15", "time": "19:33:39"} ] }
+{ "business_id": "--qvQS4MigHPykD2GV0-zw", "checkin_times": [ {"date": "2019-04-11", "time": "18:30:12"} ] }
+{ "business_id": "--wIGbLEhlpl_UeAIyDmZQ", "checkin_times": [ {"date": "2015-06-06", "time": "20:01:06"}, {"date": "2019-03-14", "time": "22:01:52"} ] }
+{ "business_id": "-0FA-Qdi3SPYIoJz9UQw-A", "checkin_times": [ {"date": "2018-09-29", "time": "18:55:17"}, {"date": "2018-10-20", "time": "16:48:05"}, {"date": "2018-10-20", "time": "22:20:24"} ] }
+{ "business_id": "-0Hj1hb_XW6ybWq2M7QhGA", "checkin_times": [ {"date": "2011-04-23", "time": "21:11:22"}, {"date": "2014-05-04", "time": "19:42:48"}, {"date": "2014-05-11", "time": "19:16:08"}, {"date": "2014-06-04", "time": "19:14:18"}, {"date": "2015-12-05", "time": "19:22:42"}, {"date": "2017-05-15", "time": "23:19:00"} ] }
+{ "business_id": "-0KMvRFwDWdVBeTpT11iHw", "checkin_times": [ {"date": "2012-07-13", "time": "21:43:57"}, {"date": "2016-12-24", "time": "02:27:31"}, {"date": "2017-08-31", "time": "00:35:26"} ] }
+{ "business_id": "-0LPtgJC31FWMrMv317p0Q", "checkin_times": [ {"date": "2013-04-13", "time": "12:35:33"}, {"date": "2013-08-19", "time": "23:35:49"}, {"date": "2013-10-04", "time": "19:14:56"} ] }
+{ "business_id": "-0M3o2uWBnQZwd3hmfEwuw", "checkin_times": [ {"date": "2016-09-10", "time": "19:26:19"}, {"date": "2018-09-08", "time": "14:15:37"}, {"date": "2019-09-13", "time": "22:47:25"} ] }
+{ "business_id": "-0RRiWDtfnS16AKCtfvBZg", "checkin_times": [ {"date": "2017-05-19", "time": "14:30:16"}, {"date": "2017-05-19", "time": "14:30:25"}, {"date": "2017-08-28", "time": "15:49:37"}, {"date": "2017-09-20", "time": "20:19:51"}, {"date": "2017-10-01", "time": "16:31:05"}, {"date": "2017-10-01", "time": "16:56:27"}, {"date": "2017-12-27", "time": "23:33:20"} ] }
+{ "business_id": "-0Soj75v-XoRcf2ERr8Bmg", "checkin_times": [ {"date": "2019-06-05", "time": "18:22:49"} ] }
+{ "business_id": "-0ZumLlFjMh4ZW1z2nXGug", "checkin_times": [ {"date": "2011-09-24", "time": "21:37:32"}, {"date": "2014-03-10", "time": "20:20:07"}, {"date": "2015-05-27", "time": "00:40:24"}, {"date": "2015-08-29", "time": "17:58:15"}, {"date": "2018-03-16", "time": "15:03:26"} ] }
+{ "business_id": "-0aOudcaAyac0VJbMX-L1g", "checkin_times": [ {"date": "2015-03-16", "time": "23:51:16"}, {"date": "2015-12-21", "time": "04:48:01"}, {"date": "2016-10-28", "time": "20:22:42"}, {"date": "2016-10-28", "time": "20:23:00"} ] }
+{ "business_id": "-0b86isaXMY0v4g-V8GZ9Q", "checkin_times": [ {"date": "2013-10-22", "time": "16:49:21"}, {"date": "2014-11-21", "time": "17:39:24"} ] }
+{ "business_id": "-0d-BfFSU0bwLcnMaGRxYw", "checkin_times": [ {"date": "2014-08-07", "time": "18:30:48"}, {"date": "2014-09-16", "time": "20:41:45"}, {"date": "2014-10-12", "time": "23:22:27"}, {"date": "2015-07-21", "time": "20:43:56"}, {"date": "2015-07-21", "time": "20:45:07"} ] }
+{ "business_id": "-0jz6c3C6i7RG7Ag22K-Pg", "checkin_times": [ {"date": "2015-05-02", "time": "19:49:05"}, {"date": "2015-05-06", "time": "03:52:18"}, {"date": "2015-09-26", "time": "01:13:19"} ] }
+{ "business_id": "-0y3MZU2oYP8r1ruDP1bfQ", "checkin_times": [ {"date": "2015-04-11", "time": "13:14:14"}, {"date": "2015-11-21", "time": "16:05:56"}, {"date": "2016-05-06", "time": "14:10:04"}, {"date": "2017-08-09", "time": "15:15:10"}, {"date": "2017-10-21", "time": "15:12:56"} ] }
+{ "business_id": "-1BPe8UjF2_l3nVk-DFUjA", "checkin_times": [ {"date": "2015-12-03", "time": "18:44:00"}, {"date": "2016-03-17", "time": "18:19:21"}, {"date": "2016-11-02", "time": "15:58:38"} ] }
+{ "business_id": "-1E2CQu_38mkghvmZgCCRw", "checkin_times": [ {"date": "2019-04-04", "time": "22:02:37"} ] }
+{ "business_id": "-1wzk43IZ5D9Ysu6kzb5xA", "checkin_times": [ {"date": "2019-02-27", "time": "14:03:08"} ] }
+{ "business_id": "-23R9P2eG7VTc6DVLjFKzA", "checkin_times": [ {"date": "2011-12-21", "time": "19:02:51"}, {"date": "2012-04-15", "time": "04:21:39"}, {"date": "2012-04-15", "time": "14:23:56"}, {"date": "2013-06-30", "time": "22:39:51"}, {"date": "2013-10-04", "time": "20:34:13"}, {"date": "2014-07-16", "time": "02:28:40"} ] }
+{ "business_id": "-26MGfikhJiTfCI-GqmzhQ", "checkin_times": [ {"date": "2018-06-13", "time": "20:16:07"} ] }
+{ "business_id": "-2bLuJsMZ0WhI9daurVQNQ", "checkin_times": [ {"date": "2015-05-29", "time": "16:46:17"}, {"date": "2015-06-01", "time": "15:03:53"} ] }
+{ "business_id": "-2hDBMaza_ldqnZdiU06LQ", "checkin_times": [ {"date": "2011-10-08", "time": "12:02:23"}, {"date": "2014-08-18", "time": "02:11:11"}, {"date": "2016-01-07", "time": "05:27:51"}, {"date": "2016-10-21", "time": "20:15:55"}, {"date": "2016-12-01", "time": "03:57:10"}, {"date": "2016-12-29", "time": "01:54:42"}, {"date": "2018-07-22", "time": "19:55:31"}, {"date": "2018-09-07", "time": "01:42:54"}, {"date": "2019-03-08", "time": "03:41:06"} ] }
diff --git a/asterixdb/asterix-app/data/yelp-checkin/use-case-4.json b/asterixdb/asterix-app/data/yelp-checkin/use-case-4.json
new file mode 100644
index 0000000..ec90bb4
--- /dev/null
+++ b/asterixdb/asterix-app/data/yelp-checkin/use-case-4.json
@@ -0,0 +1,30 @@
+{ "business_id": "--1UhMGODdWsrMastO9DZw", "checkin_times": [ { "dates": ["2016-04-26", "2016-08-30", "2016-10-15", "2016-11-18"], "times": ["19:49:16", "18:36:57", "02:45:18", "01:54:50"] }, { "dates": ["2017-04-20", "2017-05-03"], "times": ["18:39:06", "17:58:02"] }, { "dates": ["2019-03-19"], "times": ["22:04:48"] } ] }
+{ "business_id": "--EF5N7P70J_UYBTPypYlA", "checkin_times": [ { "dates": ["2018-05-25", "2018-09-18"], "times": ["19:52:07", "16:09:44"] }, { "dates": ["2019-10-18"], "times": ["21:29:09"] } ] }
+{ "business_id": "--Ni3oJ4VOqfOEu7Sj2Vzg", "checkin_times": [ { "dates": ["2019-06-07"], "times": ["17:54:58"] } ] }
+{ "business_id": "--Y1Adl1YUWfYIRSd8vkmA", "checkin_times": [ { "dates": ["2011-05-03", "2011-08-23"], "times": ["20:54:05", "20:49:45"] }, { "dates": ["2014-12-04"], "times": ["06:13:01"] }, { "dates": ["2016-11-16"], "times": ["19:25:55"] } ] }
+{ "business_id": "--YPwqIlRJrhHkJcjY3eiA", "checkin_times": [ { "dates": ["2016-06-18", "2016-10-15"], "times": ["21:35:45", "18:17:51"] } ] }
+{ "business_id": "--e8PjCNhEz32pprnPhCwQ", "checkin_times": [ { "dates": ["2015-04-02"], "times": ["21:45:17"] } ] }
+{ "business_id": "--kinfHwmtdjz03g8B8z8Q", "checkin_times": [ { "dates": ["2014-08-27"], "times": ["17:49:18"] }, { "dates": ["2015-12-19"], "times": ["21:30:31"] }, { "dates": ["2018-11-27"], "times": ["15:53:50"] } ] }
+{ "business_id": "--q6datkI-f0EoVheXNEeQ", "checkin_times": [ { "dates": ["2014-01-28", "2014-11-16"], "times": ["20:56:04", "16:11:58"] }, { "dates": ["2015-11-15", "2015-11-15"], "times": ["19:21:53", "19:33:39"] } ] }
+{ "business_id": "--qvQS4MigHPykD2GV0-zw", "checkin_times": [ { "dates": ["2019-04-11"], "times": ["18:30:12"] } ] }
+{ "business_id": "--wIGbLEhlpl_UeAIyDmZQ", "checkin_times": [ { "dates": ["2015-06-06"], "times": ["20:01:06"] }, { "dates": ["2019-03-14"], "times": ["22:01:52"] } ] }
+{ "business_id": "-0FA-Qdi3SPYIoJz9UQw-A", "checkin_times": [ { "dates": ["2018-09-29", "2018-10-20", "2018-10-20"], "times": ["18:55:17", "16:48:05", "22:20:24"] } ] }
+{ "business_id": "-0Hj1hb_XW6ybWq2M7QhGA", "checkin_times": [ { "dates": ["2011-04-23"], "times": ["21:11:22"] }, { "dates": ["2014-05-04", "2014-05-11", "2014-06-04"], "times": ["19:42:48", "19:16:08", "19:14:18"] }, { "dates": ["2015-12-05"], "times": ["19:22:42"] }, { "dates": ["2017-05-15"], "times": ["23:19:00"] } ] }
+{ "business_id": "-0KMvRFwDWdVBeTpT11iHw", "checkin_times": [ { "dates": ["2012-07-13"], "times": ["21:43:57"] }, { "dates": ["2016-12-24"], "times": ["02:27:31"] }, { "dates": ["2017-08-31"], "times": ["00:35:26"] } ] }
+{ "business_id": "-0LPtgJC31FWMrMv317p0Q", "checkin_times": [ { "dates": ["2013-04-13", "2013-08-19", "2013-10-04"], "times": ["12:35:33", "23:35:49", "19:14:56"] } ] }
+{ "business_id": "-0M3o2uWBnQZwd3hmfEwuw", "checkin_times": [ { "dates": ["2016-09-10"], "times": ["19:26:19"] }, { "dates": ["2018-09-08"], "times": ["14:15:37"] }, { "dates": ["2019-09-13"], "times": ["22:47:25"] } ] }
+{ "business_id": "-0RRiWDtfnS16AKCtfvBZg", "checkin_times": [ { "dates": ["2017-05-19", "2017-05-19", "2017-08-28", "2017-09-20", "2017-10-01", "2017-10-01", "2017-12-27"], "times": ["14:30:16", "14:30:25", "15:49:37", "20:19:51", "16:31:05", "16:56:27", "23:33:20"] } ] }
+{ "business_id": "-0Soj75v-XoRcf2ERr8Bmg", "checkin_times": [ { "dates": ["2019-06-05"], "times": ["18:22:49"] } ] }
+{ "business_id": "-0ZumLlFjMh4ZW1z2nXGug", "checkin_times": [ { "dates": ["2011-09-24"], "times": ["21:37:32"] }, { "dates": ["2014-03-10"], "times": ["20:20:07"] }, { "dates": ["2015-05-27", "2015-08-29"], "times": ["00:40:24", "17:58:15"] }, { "dates": ["2018-03-16"], "times": ["15:03:26"] } ] }
+{ "business_id": "-0aOudcaAyac0VJbMX-L1g", "checkin_times": [ { "dates": ["2015-03-16", "2015-12-21"], "times": ["23:51:16", "04:48:01"] }, { "dates": ["2016-10-28", "2016-10-28"], "times": ["20:22:42", "20:23:00"] } ] }
+{ "business_id": "-0b86isaXMY0v4g-V8GZ9Q", "checkin_times": [ { "dates": ["2013-10-22"], "times": ["16:49:21"] }, { "dates": ["2014-11-21"], "times": ["17:39:24"] } ] }
+{ "business_id": "-0d-BfFSU0bwLcnMaGRxYw", "checkin_times": [ { "dates": ["2014-08-07", "2014-09-16", "2014-10-12"], "times": ["18:30:48", "20:41:45", "23:22:27"] }, { "dates": ["2015-07-21", "2015-07-21"], "times": ["20:43:56", "20:45:07"] } ] }
+{ "business_id": "-0jz6c3C6i7RG7Ag22K-Pg", "checkin_times": [ { "dates": ["2015-05-02", "2015-05-06", "2015-09-26"], "times": ["19:49:05", "03:52:18", "01:13:19"] } ] }
+{ "business_id": "-0y3MZU2oYP8r1ruDP1bfQ", "checkin_times": [ { "dates": ["2015-04-11", "2015-11-21"], "times": ["13:14:14", "16:05:56"] }, { "dates": ["2016-05-06"], "times": ["14:10:04"] }, { "dates": ["2017-08-09", "2017-10-21"], "times": ["15:15:10", "15:12:56"] } ] }
+{ "business_id": "-1BPe8UjF2_l3nVk-DFUjA", "checkin_times": [ { "dates": ["2015-12-03"], "times": ["18:44:00"] }, { "dates": ["2016-03-17", "2016-11-02"], "times": ["18:19:21", "15:58:38"] } ] }
+{ "business_id": "-1E2CQu_38mkghvmZgCCRw", "checkin_times": [ { "dates": ["2019-04-04"], "times": ["22:02:37"] } ] }
+{ "business_id": "-1wzk43IZ5D9Ysu6kzb5xA", "checkin_times": [ { "dates": ["2019-02-27"], "times": ["14:03:08"] } ] }
+{ "business_id": "-23R9P2eG7VTc6DVLjFKzA", "checkin_times": [ { "dates": ["2011-12-21"], "times": ["19:02:51"] }, { "dates": ["2012-04-15", "2012-04-15"], "times": ["04:21:39", "14:23:56"] }, { "dates": ["2013-06-30", "2013-10-04"], "times": ["22:39:51", "20:34:13"] }, { "dates": ["2014-07-16"], "times": ["02:28:40"] } ] }
+{ "business_id": "-26MGfikhJiTfCI-GqmzhQ", "checkin_times": [ { "dates": ["2018-06-13"], "times": ["20:16:07"] } ] }
+{ "business_id": "-2bLuJsMZ0WhI9daurVQNQ", "checkin_times": [ { "dates": ["2015-05-29", "2015-06-01"], "times": ["16:46:17", "15:03:53"] } ] }
+{ "business_id": "-2hDBMaza_ldqnZdiU06LQ", "checkin_times": [ { "dates": ["2011-10-08"], "times": ["12:02:23"] }, { "dates": ["2014-08-18"], "times": ["02:11:11"] }, { "dates": ["2016-01-07", "2016-10-21", "2016-12-01", "2016-12-29"], "times": ["05:27:51", "20:15:55", "03:57:10", "01:54:42"] }, { "dates": ["2018-07-22", "2018-09-07"], "times": ["19:55:31", "01:42:54"] }, { "dates": ["2019-03-08"], "times": ["03:41:06"] } ] }
diff --git a/asterixdb/asterix-app/data/yelp-checkin/with-3-level-record-path.json b/asterixdb/asterix-app/data/yelp-checkin/with-3-level-record-path.json
new file mode 100644
index 0000000..39505f0
--- /dev/null
+++ b/asterixdb/asterix-app/data/yelp-checkin/with-3-level-record-path.json
@@ -0,0 +1,31 @@
+{ "business_id": "--1UhMGODdWsrMastO9DZw", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2016-04-26", "2016-08-30", "2016-10-15", "2016-11-18", "2017-04-20", "2017-05-03", "2019-03-19" ], "times": [ "19:49:16", "18:36:57", "02:45:18", "01:54:50", "18:39:06", "17:58:02", "22:04:48" ] } } } }
+{ "business_id": "--EF5N7P70J_UYBTPypYlA", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2018-05-25", "2018-09-18", "2019-10-18" ], "times": [ "19:52:07", "16:09:44", "21:29:09" ] } } } }
+{ "business_id": "--Ni3oJ4VOqfOEu7Sj2Vzg", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2019-06-07" ], "times": [ "17:54:58" ] } } } }
+{ "business_id": "--Y1Adl1YUWfYIRSd8vkmA", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2011-05-03", "2011-08-23", "2014-12-04", "2016-11-16" ], "times": [ "20:54:05", "20:49:45", "06:13:01", "19:25:55" ] } } } }
+{ "business_id": "--YPwqIlRJrhHkJcjY3eiA", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2016-06-18", "2016-10-15" ], "times": [ "21:35:45", "18:17:51" ] } } } }
+{ "business_id": "--e8PjCNhEz32pprnPhCwQ", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2015-04-02" ], "times": [ "21:45:17" ] } } } }
+{ "business_id": "--kinfHwmtdjz03g8B8z8Q", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2014-08-27", "2015-12-19", "2018-11-27" ], "times": [ "17:49:18", "21:30:31", "15:53:50" ] } } } }
+{ "business_id": "--q6datkI-f0EoVheXNEeQ", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2014-01-28", "2014-11-16", "2015-11-15", "2015-11-15" ], "times": [ "20:56:04", "16:11:58", "19:21:53", "19:33:39" ] } } } }
+{ "business_id": "--qvQS4MigHPykD2GV0-zw", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2019-04-11" ], "times": [ "18:30:12" ] } } } }
+{ "business_id": "--wIGbLEhlpl_UeAIyDmZQ", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2015-06-06", "2019-03-14" ], "times": [ "20:01:06", "22:01:52" ] } } } }
+{ "business_id": "-0FA-Qdi3SPYIoJz9UQw-A", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2018-09-29", "2018-10-20", "2018-10-20" ], "times": [ "18:55:17", "16:48:05", "22:20:24" ] } } } }
+{ "business_id": "-0Hj1hb_XW6ybWq2M7QhGA", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2011-04-23", "2014-05-04", "2014-05-11", "2014-06-04", "2015-12-05", "2017-05-15" ], "times": [ "21:11:22", "19:42:48", "19:16:08", "19:14:18", "19:22:42", "23:19:00" ] } } } }
+{ "business_id": "-0KMvRFwDWdVBeTpT11iHw", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2012-07-13", "2016-12-24", "2017-08-31" ], "times": [ "21:43:57", "02:27:31", "00:35:26" ] } } } }
+{ "business_id": "-0LPtgJC31FWMrMv317p0Q", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2013-04-13", "2013-08-19", "2013-10-04" ], "times": [ "12:35:33", "23:35:49", "19:14:56" ] } } } }
+{ "business_id": "-0M3o2uWBnQZwd3hmfEwuw", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2016-09-10", "2018-09-08", "2019-09-13" ], "times": [ "19:26:19", "14:15:37", "22:47:25" ] } } } }
+{ "business_id": "-0RRiWDtfnS16AKCtfvBZg", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2017-05-19", "2017-05-19", "2017-08-28", "2017-09-20", "2017-10-01", "2017-10-01", "2017-12-27" ], "times": [ "14:30:16", "14:30:25", "15:49:37", "20:19:51", "16:31:05", "16:56:27", "23:33:20" ] } } } }
+{ "business_id": "-0Soj75v-XoRcf2ERr8Bmg", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2019-06-05" ], "times": [ "18:22:49" ] } } } }
+{ "business_id": "-0ZumLlFjMh4ZW1z2nXGug", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2011-09-24", "2014-03-10", "2015-05-27", "2015-08-29", "2018-03-16" ], "times": [ "21:37:32", "20:20:07", "00:40:24", "17:58:15", "15:03:26" ] } } } }
+{ "business_id": "-0aOudcaAyac0VJbMX-L1g", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2015-03-16", "2015-12-21", "2016-10-28", "2016-10-28" ], "times": [ "23:51:16", "04:48:01", "20:22:42", "20:23:00" ] } } } }
+{ "business_id": "-0b86isaXMY0v4g-V8GZ9Q", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2013-10-22", "2014-11-21" ], "times": [ "16:49:21", "17:39:24" ] } } } }
+{ "business_id": "-0d-BfFSU0bwLcnMaGRxYw", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2014-08-07", "2014-09-16", "2014-10-12", "2015-07-21", "2015-07-21" ], "times": [ "18:30:48", "20:41:45", "23:22:27", "20:43:56", "20:45:07" ] } } } }
+{ "business_id": "-0jz6c3C6i7RG7Ag22K-Pg", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2015-05-02", "2015-05-06", "2015-09-26" ], "times": [ "19:49:05", "03:52:18", "01:13:19" ] } } } }
+{ "business_id": "-0y3MZU2oYP8r1ruDP1bfQ", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2015-04-11", "2015-11-21", "2016-05-06", "2017-08-09", "2017-10-21" ], "times": [ "13:14:14", "16:05:56", "14:10:04", "15:15:10", "15:12:56" ] } } } }
+{ "business_id": "-1BPe8UjF2_l3nVk-DFUjA", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2015-12-03", "2016-03-17", "2016-11-02" ], "times": [ "18:44:00", "18:19:21", "15:58:38" ] } } } }
+{ "business_id": "-1E2CQu_38mkghvmZgCCRw", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2019-04-04" ], "times": [ "22:02:37" ] } } } }
+{ "business_id": "-1wzk43IZ5D9Ysu6kzb5xA", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2019-02-27" ], "times": [ "14:03:08" ] } } } }
+{ "business_id": "-23R9P2eG7VTc6DVLjFKzA", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2011-12-21", "2012-04-15", "2012-04-15", "2013-06-30", "2013-10-04", "2014-07-16" ], "times": [ "19:02:51", "04:21:39", "14:23:56", "22:39:51", "20:34:13", "02:28:40" ] } } } }
+{ "business_id": "-26MGfikhJiTfCI-GqmzhQ", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2018-06-13" ], "times": [ "20:16:07" ] } } } }
+{ "business_id": "-2bLuJsMZ0WhI9daurVQNQ", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2015-05-29", "2015-06-01" ], "times": [ "16:46:17", "15:03:53" ] } } } }
+{ "business_id": "-2hDBMaza_ldqnZdiU06LQ", "checkin_data": { "checkin_temporal": { "checkin_times": { "dates": [ "2011-10-08", "2014-08-18", "2016-01-07", "2016-10-21", "2016-12-01", "2016-12-29", "2018-07-22", "2018-09-07", "2019-03-08" ], "times": [ "12:02:23", "02:11:11", "05:27:51", "20:15:55", "03:57:10", "01:54:42", "19:55:31", "01:42:54", "03:41:06" ] } } } }
+
diff --git a/asterixdb/asterix-app/data/yelp-checkin/with-composite-pk.json b/asterixdb/asterix-app/data/yelp-checkin/with-composite-pk.json
new file mode 100644
index 0000000..1611114
--- /dev/null
+++ b/asterixdb/asterix-app/data/yelp-checkin/with-composite-pk.json
@@ -0,0 +1,30 @@
+{ "checkin_id": 1, "business_id": "--1UhMGODdWsrMastO9DZw", "dates": [ "2016-04-26 19:49:16", "2016-08-30 18:36:57", "2016-10-15 02:45:18", "2016-11-18 01:54:50", "2017-04-20 18:39:06", "2017-05-03 17:58:02", "2019-03-19 22:04:48" ] }
+{ "checkin_id": 2, "business_id": "--EF5N7P70J_UYBTPypYlA", "dates": [ "2018-05-25 19:52:07", "2018-09-18 16:09:44", "2019-10-18 21:29:09" ] }
+{ "checkin_id": 3, "business_id": "--Ni3oJ4VOqfOEu7Sj2Vzg", "dates": [ "2019-06-07 17:54:58" ] }
+{ "checkin_id": 4, "business_id": "--Y1Adl1YUWfYIRSd8vkmA", "dates": [ "2011-05-03 20:54:05", "2011-08-23 20:49:45", "2014-12-04 06:13:01", "2016-11-16 19:25:55" ] }
+{ "checkin_id": 5, "business_id": "--YPwqIlRJrhHkJcjY3eiA", "dates": [ "2016-06-18 21:35:45", "2016-10-15 18:17:51" ] }
+{ "checkin_id": 6, "business_id": "--e8PjCNhEz32pprnPhCwQ", "dates": [ "2015-04-02 21:45:17" ] }
+{ "checkin_id": 7, "business_id": "--kinfHwmtdjz03g8B8z8Q", "dates": [ "2014-08-27 17:49:18", "2015-12-19 21:30:31", "2018-11-27 15:53:50" ] }
+{ "checkin_id": 8, "business_id": "--q6datkI-f0EoVheXNEeQ", "dates": [ "2014-01-28 20:56:04", "2014-11-16 16:11:58", "2015-11-15 19:21:53", "2015-11-15 19:33:39" ] }
+{ "checkin_id": 9, "business_id": "--qvQS4MigHPykD2GV0-zw", "dates": [ "2019-04-11 18:30:12" ] }
+{ "checkin_id": 10, "business_id": "--wIGbLEhlpl_UeAIyDmZQ", "dates": [ "2015-06-06 20:01:06", "2019-03-14 22:01:52" ] }
+{ "checkin_id": 11, "business_id": "-0FA-Qdi3SPYIoJz9UQw-A", "dates": [ "2018-09-29 18:55:17", "2018-10-20 16:48:05", "2018-10-20 22:20:24" ] }
+{ "checkin_id": 12, "business_id": "-0Hj1hb_XW6ybWq2M7QhGA", "dates": [ "2011-04-23 21:11:22", "2014-05-04 19:42:48", "2014-05-11 19:16:08", "2014-06-04 19:14:18", "2015-12-05 19:22:42", "2017-05-15 23:19:00" ] }
+{ "checkin_id": 13, "business_id": "-0KMvRFwDWdVBeTpT11iHw", "dates": [ "2012-07-13 21:43:57", "2016-12-24 02:27:31", "2017-08-31 00:35:26" ] }
+{ "checkin_id": 14, "business_id": "-0LPtgJC31FWMrMv317p0Q", "dates": [ "2013-04-13 12:35:33", "2013-08-19 23:35:49", "2013-10-04 19:14:56" ] }
+{ "checkin_id": 15, "business_id": "-0M3o2uWBnQZwd3hmfEwuw", "dates": [ "2016-09-10 19:26:19", "2018-09-08 14:15:37", "2019-09-13 22:47:25" ] }
+{ "checkin_id": 16, "business_id": "-0RRiWDtfnS16AKCtfvBZg", "dates": [ "2017-05-19 14:30:16", "2017-05-19 14:30:25", "2017-08-28 15:49:37", "2017-09-20 20:19:51", "2017-10-01 16:31:05", "2017-10-01 16:56:27", "2017-12-27 23:33:20" ] }
+{ "checkin_id": 17, "business_id": "-0Soj75v-XoRcf2ERr8Bmg", "dates": [ "2019-06-05 18:22:49" ] }
+{ "checkin_id": 18, "business_id": "-0ZumLlFjMh4ZW1z2nXGug", "dates": [ "2011-09-24 21:37:32", "2014-03-10 20:20:07", "2015-05-27 00:40:24", "2015-08-29 17:58:15", "2018-03-16 15:03:26" ] }
+{ "checkin_id": 19, "business_id": "-0aOudcaAyac0VJbMX-L1g", "dates": [ "2015-03-16 23:51:16", "2015-12-21 04:48:01", "2016-10-28 20:22:42", "2016-10-28 20:23:00" ] }
+{ "checkin_id": 20, "business_id": "-0b86isaXMY0v4g-V8GZ9Q", "dates": [ "2013-10-22 16:49:21", "2014-11-21 17:39:24" ] }
+{ "checkin_id": 21, "business_id": "-0d-BfFSU0bwLcnMaGRxYw", "dates": [ "2014-08-07 18:30:48", "2014-09-16 20:41:45", "2014-10-12 23:22:27", "2015-07-21 20:43:56", "2015-07-21 20:45:07" ] }
+{ "checkin_id": 22, "business_id": "-0jz6c3C6i7RG7Ag22K-Pg", "dates": [ "2015-05-02 19:49:05", "2015-05-06 03:52:18", "2015-09-26 01:13:19" ] }
+{ "checkin_id": 23, "business_id": "-0y3MZU2oYP8r1ruDP1bfQ", "dates": [ "2015-04-11 13:14:14", "2015-11-21 16:05:56", "2016-05-06 14:10:04", "2017-08-09 15:15:10", "2017-10-21 15:12:56" ] }
+{ "checkin_id": 24, "business_id": "-1BPe8UjF2_l3nVk-DFUjA", "dates": [ "2015-12-03 18:44:00", "2016-03-17 18:19:21", "2016-11-02 15:58:38" ] }
+{ "checkin_id": 25, "business_id": "-1E2CQu_38mkghvmZgCCRw", "dates": [ "2019-04-04 22:02:37" ] }
+{ "checkin_id": 26, "business_id": "-1wzk43IZ5D9Ysu6kzb5xA", "dates": [ "2019-02-27 14:03:08" ] }
+{ "checkin_id": 27, "business_id": "-23R9P2eG7VTc6DVLjFKzA", "dates": [ "2011-12-21 19:02:51", "2012-04-15 04:21:39", "2012-04-15 14:23:56", "2013-06-30 22:39:51", "2013-10-04 20:34:13", "2014-07-16 02:28:40" ] }
+{ "checkin_id": 28, "business_id": "-26MGfikhJiTfCI-GqmzhQ", "dates": [ "2018-06-13 20:16:07" ] }
+{ "checkin_id": 29, "business_id": "-2bLuJsMZ0WhI9daurVQNQ", "dates": [ "2015-05-29 16:46:17", "2015-06-01 15:03:53" ] }
+{ "checkin_id": 30, "business_id": "-2hDBMaza_ldqnZdiU06LQ", "dates": [ "2011-10-08 12:02:23", "2014-08-18 02:11:11", "2016-01-07 05:27:51", "2016-10-21 20:15:55", "2016-12-01 03:57:10", "2016-12-29 01:54:42", "2018-07-22 19:55:31", "2018-09-07 01:42:54", "2019-03-08 03:41:06" ] }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
index 9088db6..270f7d2 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
@@ -135,7 +135,7 @@ public class APIFramework {
             CompilerProperties.COMPILER_SORT_PARALLEL_KEY, CompilerProperties.COMPILER_SORT_SAMPLES_KEY,
             CompilerProperties.COMPILER_INDEXONLY_KEY, CompilerProperties.COMPILER_INTERNAL_SANITYCHECK_KEY,
             CompilerProperties.COMPILER_EXTERNAL_FIELD_PUSHDOWN_KEY, CompilerProperties.COMPILER_SUBPLAN_MERGE_KEY,
-            CompilerProperties.COMPILER_SUBPLAN_NESTEDPUSHDOWN_KEY,
+            CompilerProperties.COMPILER_SUBPLAN_NESTEDPUSHDOWN_KEY, CompilerProperties.COMPILER_ARRAYINDEX_KEY,
             CompilerProperties.COMPILER_MIN_MEMORY_ALLOCATION_KEY, FunctionUtil.IMPORT_PRIVATE_FUNCTIONS,
             FuzzyUtils.SIM_FUNCTION_PROP_NAME, FuzzyUtils.SIM_THRESHOLD_PROP_NAME,
             StartFeedStatement.WAIT_FOR_COMPLETION, FeedActivityDetails.FEED_POLICY_NAME,
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index 8251241..778e79a 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -39,6 +39,7 @@ import java.util.Objects;
 import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
 
 import org.apache.asterix.active.ActivityState;
 import org.apache.asterix.active.EntityId;
@@ -1041,7 +1042,9 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
 
             DatasetType datasetType = ds.getDatasetType();
             IndexType indexType = stmtCreateIndex.getIndexType();
-            boolean isSecondaryPrimary = stmtCreateIndex.getFieldExprs().isEmpty();
+            List<CreateIndexStatement.IndexedElement> indexedElements = stmtCreateIndex.getIndexedElements();
+            int indexedElementsCount = indexedElements.size();
+            boolean isSecondaryPrimary = indexedElementsCount == 0;
             validateIndexType(datasetType, indexType, isSecondaryPrimary, sourceLoc);
 
             String indexName = stmtCreateIndex.getIndexName().getValue();
@@ -1056,111 +1059,234 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                 }
             }
 
-            List<Integer> keySourceIndicators;
-            if (isSecondaryPrimary && datasetType == DatasetType.INTERNAL) {
-                // find keySourceIndicators for secondary primary index since the parser isn't aware of them
-                keySourceIndicators = ((InternalDatasetDetails) ds.getDatasetDetails()).getKeySourceIndicator();
-            } else {
-                keySourceIndicators = stmtCreateIndex.getFieldSourceIndicators();
-            }
-            // disable creating an index on meta fields (fields with source indicator == 1 are meta fields)
-            if (keySourceIndicators.stream().anyMatch(fieldSource -> fieldSource == 1) && !isSecondaryPrimary) {
-                throw new AsterixException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                        "Cannot create index on meta fields");
-            }
             Datatype dt = MetadataManager.INSTANCE.getDatatype(metadataProvider.getMetadataTxnContext(),
                     ds.getItemTypeDataverseName(), ds.getItemTypeName());
             ARecordType aRecordType = (ARecordType) dt.getDatatype();
+            /* TODO: unused for now becase indexes on meta are disabled -- see below
             ARecordType metaRecordType = null;
             if (ds.hasMetaPart()) {
                 Datatype metaDt = MetadataManager.INSTANCE.getDatatype(metadataProvider.getMetadataTxnContext(),
                         ds.getMetaItemTypeDataverseName(), ds.getMetaItemTypeName());
                 metaRecordType = (ARecordType) metaDt.getDatatype();
             }
+            */
 
-            List<List<String>> indexFields = new ArrayList<>();
-            List<IAType> indexFieldTypes = new ArrayList<>();
-            int keyIndex = 0;
+            List<List<IAType>> indexFieldTypes = new ArrayList<>(indexedElementsCount);
+            boolean hadUnnest = false;
             boolean overridesFieldTypes = false;
 
             // this set is used to detect duplicates in the specified keys in the create
             // index statement
             // e.g. CREATE INDEX someIdx on dataset(id,id).
-            // checking only the names is not enough. Need also to check the source
-            // indicators for cases like:
-            // CREATE INDEX someIdx on dataset(meta().id, id)
-            Set<Pair<List<String>, Integer>> indexKeysSet = new HashSet<>();
-
-            for (Pair<List<String>, IndexedTypeExpression> fieldExpr : stmtCreateIndex.getFieldExprs()) {
-                IAType fieldType = null;
-                ARecordType subType =
-                        KeyFieldTypeUtil.chooseSource(keySourceIndicators, keyIndex, aRecordType, metaRecordType);
-                boolean isOpen = subType.isOpen();
-                int i = 0;
-                if (fieldExpr.first.size() > 1 && !isOpen) {
-                    while (i < fieldExpr.first.size() - 1 && !isOpen) {
-                        subType = (ARecordType) subType.getFieldType(fieldExpr.first.get(i));
-                        i++;
-                        isOpen = subType.isOpen();
-                    }
+            // checking only the names is not enough.
+            // Need also to check the source indicators for the most general case
+            // (even though indexes on meta fields are curently disabled -- see below)
+            Set<Triple<Integer, List<List<String>>, List<List<String>>>> indexKeysSet = new HashSet<>();
+
+            for (CreateIndexStatement.IndexedElement indexedElement : indexedElements) {
+                // disable creating an index on meta fields (fields with source indicator == 1 are meta fields)
+                if (indexedElement.getSourceIndicator() != Index.RECORD_INDICATOR) {
+                    throw new AsterixException(ErrorCode.COMPILATION_ERROR, indexedElement.getSourceLocation(),
+                            "Cannot create index on meta fields");
                 }
-                if (fieldExpr.second == null) {
-                    fieldType = subType.getSubFieldType(fieldExpr.first.subList(i, fieldExpr.first.size()));
-                } else {
-                    if (!stmtCreateIndex.isEnforced() && indexType != IndexType.BTREE) {
-                        throw new AsterixException(ErrorCode.INDEX_ILLEGAL_NON_ENFORCED_TYPED, sourceLoc, indexType);
+                ARecordType sourceRecordType = aRecordType;
+                IAType inputTypePrime;
+                boolean inputTypeNullable, inputTypeMissable;
+                List<Pair<List<String>, IndexedTypeExpression>> projectList = indexedElement.getProjectList();
+                int projectCount = projectList.size();
+                if (indexedElement.hasUnnest()) {
+                    if (indexType != IndexType.ARRAY) {
+                        throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_INDEX_TYPE,
+                                indexedElement.getSourceLocation(), String.valueOf(indexType));
                     }
-                    if (stmtCreateIndex.isEnforced() && !fieldExpr.second.isUnknownable()) {
-                        throw new AsterixException(ErrorCode.INDEX_ILLEGAL_ENFORCED_NON_OPTIONAL, sourceLoc,
-                                String.valueOf(fieldExpr.first));
+                    // allow only 1 unnesting element in ARRAY index
+                    if (hadUnnest) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, indexedElement.getSourceLocation(),
+                                "Cannot create composite index with multiple array fields using different arrays");
                     }
-                    // don't allow creating an enforced index on a closed-type field, fields that
-                    // are part of schema.
-                    // get the field type, if it's not null, then the field is closed-type
-                    if (stmtCreateIndex.isEnforced()
-                            && subType.getSubFieldType(fieldExpr.first.subList(i, fieldExpr.first.size())) != null) {
-                        throw new AsterixException(ErrorCode.INDEX_ILLEGAL_ENFORCED_ON_CLOSED_FIELD, sourceLoc,
-                                String.valueOf(fieldExpr.first));
+                    hadUnnest = true;
+                    if (projectCount == 0) {
+                        // Note. UNNEST with no SELECT is supposed to have 1 project element with 'null' path
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, indexedElement.getSourceLocation(),
+                                "Invalid index element");
                     }
-                    if (!isOpen) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc, "Typed index on \""
-                                + fieldExpr.first + "\" field could be created only for open datatype");
+                    Triple<IAType, Boolean, Boolean> unnestTypeResult = KeyFieldTypeUtil.getKeyUnnestType(
+                            sourceRecordType, indexedElement.getUnnestList(), indexedElement.getSourceLocation());
+                    if (unnestTypeResult == null) {
+                        inputTypePrime = null; // = ANY
+                        inputTypeNullable = inputTypeMissable = true;
+                    } else {
+                        inputTypePrime = unnestTypeResult.first;
+                        inputTypeNullable = unnestTypeResult.second;
+                        inputTypeMissable = unnestTypeResult.third;
                     }
-                    if (stmtCreateIndex.hasMetaField()) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                "Typed open index can only be created on the record part");
+                } else {
+                    if (projectCount != 1) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, indexedElement.getSourceLocation(),
+                                "Invalid index element");
                     }
-                    Map<TypeSignature, IAType> typeMap = TypeTranslator.computeTypes(dataverseName, indexName,
-                            fieldExpr.second.getType(), dataverseName, mdTxnCtx);
-                    TypeSignature typeSignature = new TypeSignature(dataverseName, indexName);
-                    fieldType = typeMap.get(typeSignature);
-                    overridesFieldTypes = true;
+                    inputTypePrime = sourceRecordType;
+                    inputTypeNullable = inputTypeMissable = false;
                 }
-                if (fieldType == null) {
-                    throw new CompilationException(ErrorCode.UNKNOWN_TYPE, sourceLoc, fieldExpr.second == null
-                            ? String.valueOf(fieldExpr.first) : String.valueOf(fieldExpr.second));
+
+                // at this point 'inputTypePrime' is either a record, or if we had unnest then it could also be anything else.
+                List<IAType> fieldTypes = new ArrayList<>(projectCount);
+                for (int i = 0; i < projectCount; i++) {
+                    Pair<List<String>, IndexedTypeExpression> projectPair = projectList.get(i);
+                    List<String> projectPath = projectPair.first;
+                    IndexedTypeExpression projectTypeExpr = projectPair.second;
+                    IAType projectTypePrime;
+                    boolean projectTypeNullable, projectTypeMissable;
+                    if (projectPath == null) {
+                        boolean emptyPathOk = indexedElement.hasUnnest() && i == 0;
+                        if (!emptyPathOk) {
+                            throw new CompilationException(ErrorCode.COMPILATION_ERROR,
+                                    indexedElement.getSourceLocation(), "Invalid index element");
+                        }
+                        projectTypePrime = inputTypePrime;
+                        projectTypeNullable = inputTypeNullable;
+                        projectTypeMissable = inputTypeMissable;
+                    } else if (inputTypePrime == null) {
+                        projectTypePrime = null; // ANY
+                        projectTypeNullable = projectTypeMissable = true;
+                    } else {
+                        if (inputTypePrime.getTypeTag() != ATypeTag.OBJECT) {
+                            throw new CompilationException(ErrorCode.TYPE_MISMATCH_GENERIC, sourceLoc, ATypeTag.OBJECT,
+                                    inputTypePrime.getTypeTag());
+                        }
+                        ARecordType inputTypePrimeRecord = (ARecordType) inputTypePrime;
+                        Triple<IAType, Boolean, Boolean> projectTypeResult = KeyFieldTypeUtil.getKeyProjectType(
+                                inputTypePrimeRecord, projectPath, indexedElement.getSourceLocation());
+                        if (projectTypeResult != null) {
+                            projectTypePrime = projectTypeResult.first;
+                            projectTypeNullable = inputTypeNullable || projectTypeResult.second;
+                            projectTypeMissable = inputTypeMissable || projectTypeResult.third;
+                        } else {
+                            projectTypePrime = null; // ANY
+                            projectTypeNullable = projectTypeMissable = true;
+                        }
+                    }
+
+                    IAType fieldTypePrime;
+                    boolean fieldTypeNullable, fieldTypeMissable;
+                    if (projectTypeExpr == null) {
+                        fieldTypePrime = projectTypePrime;
+                        fieldTypeNullable = projectTypeNullable;
+                        fieldTypeMissable = projectTypeMissable;
+                    } else {
+                        if (stmtCreateIndex.isEnforced()) {
+                            if (!projectTypeExpr.isUnknownable()) {
+                                throw new CompilationException(ErrorCode.INDEX_ILLEGAL_ENFORCED_NON_OPTIONAL,
+                                        indexedElement.getSourceLocation(), String.valueOf(projectPath));
+                            }
+                            // don't allow creating an enforced index on a closed-type field, fields that
+                            // are part of schema get the field type, if it's not null, then the field is closed-type
+                            if (projectTypePrime != null) {
+                                throw new CompilationException(ErrorCode.INDEX_ILLEGAL_ENFORCED_ON_CLOSED_FIELD,
+                                        indexedElement.getSourceLocation(), String.valueOf(projectPath));
+                            }
+                        } else {
+                            if (indexType != IndexType.BTREE && indexType != IndexType.ARRAY) {
+                                throw new CompilationException(ErrorCode.INDEX_ILLEGAL_NON_ENFORCED_TYPED,
+                                        indexedElement.getSourceLocation(), indexType);
+                            }
+                            if (projectTypePrime != null) {
+                                throw new CompilationException(ErrorCode.COMPILATION_ERROR,
+                                        indexedElement.getSourceLocation(), "Typed index on \"" + projectPath
+                                                + "\" field could be created only for open datatype");
+                            }
+                        }
+
+                        Map<TypeSignature, IAType> typeMap = TypeTranslator.computeTypes(dataverseName, indexName,
+                                projectTypeExpr.getType(), dataverseName, mdTxnCtx);
+                        TypeSignature typeSignature = new TypeSignature(dataverseName, indexName);
+                        fieldTypePrime = typeMap.get(typeSignature);
+                        // BACK-COMPAT: keep prime type only if we're overriding field types
+                        fieldTypeNullable = fieldTypeMissable = false;
+                        overridesFieldTypes = true;
+                    }
+
+                    if (fieldTypePrime == null) {
+                        throw new CompilationException(ErrorCode.UNKNOWN_TYPE, indexedElement.getSourceLocation(),
+                                String.valueOf(projectPath));
+                    }
+                    validateIndexFieldType(indexType, fieldTypePrime, projectPath, indexedElement.getSourceLocation());
+
+                    IAType fieldType =
+                            KeyFieldTypeUtil.makeUnknownableType(fieldTypePrime, fieldTypeNullable, fieldTypeMissable);
+                    fieldTypes.add(fieldType);
                 }
 
-                // try to add the key & its source to the set of keys, if key couldn't be added,
-                // there is a duplicate
-                if (!indexKeysSet
-                        .add(new Pair<>(fieldExpr.first, stmtCreateIndex.getFieldSourceIndicators().get(keyIndex)))) {
-                    throw new AsterixException(ErrorCode.INDEX_ILLEGAL_REPETITIVE_FIELD, sourceLoc,
-                            String.valueOf(fieldExpr.first));
+                // Try to add the key & its source to the set of keys for duplicate detection.
+                if (!indexKeysSet.add(indexedElement.toIdentifier())) {
+                    throw new AsterixException(ErrorCode.INDEX_ILLEGAL_REPETITIVE_FIELD,
+                            indexedElement.getSourceLocation(), indexedElement.getProjectListDisplayForm());
                 }
 
-                indexFields.add(fieldExpr.first);
-                indexFieldTypes.add(fieldType);
-                ++keyIndex;
+                indexFieldTypes.add(fieldTypes);
             }
 
-            validateIndexKeyFields(stmtCreateIndex, keySourceIndicators, aRecordType, metaRecordType, indexFields,
-                    indexFieldTypes);
+            Index.IIndexDetails indexDetails;
+            if (Index.IndexCategory.of(indexType) == Index.IndexCategory.ARRAY) {
+                if (!hadUnnest) {
+                    // prohibited by the grammar
+                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc,
+                            String.valueOf(indexType));
+                }
+                if (stmtCreateIndex.isEnforced()) {
+                    // not supported yet.
+                    throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_INDEX_TYPE, sourceLoc,
+                            String.valueOf(indexType));
+                }
+                if (indexedElementsCount > 1) {
+                    // TODO (GLENN): Add in support for composite atomic / array indexes.
+                    throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_INDEX_TYPE, sourceLoc,
+                            String.valueOf(indexType));
+                }
 
-            Index newIndex =
-                    new Index(dataverseName, datasetName, indexName, indexType, indexFields, keySourceIndicators,
-                            indexFieldTypes, stmtCreateIndex.getGramLength(), stmtCreateIndex.getFullTextConfigName(),
-                            overridesFieldTypes, stmtCreateIndex.isEnforced(), false, MetadataUtil.PENDING_ADD_OP);
+                List<Index.ArrayIndexElement> indexElementList = new ArrayList<>(indexedElementsCount);
+                for (int i = 0; i < indexedElementsCount; i++) {
+                    CreateIndexStatement.IndexedElement indexedElement = indexedElements.get(i);
+                    List<List<String>> projectList =
+                            indexedElement.getProjectList().stream().map(Pair::getFirst).collect(Collectors.toList());
+                    indexElementList.add(new Index.ArrayIndexElement(indexedElement.getUnnestList(), projectList,
+                            indexFieldTypes.get(i), indexedElement.getSourceIndicator()));
+                }
+                indexDetails = new Index.ArrayIndexDetails(indexElementList, overridesFieldTypes);
+            } else {
+                List<List<String>> keyFieldNames = new ArrayList<>(indexedElementsCount);
+                List<IAType> keyFieldTypes = new ArrayList<>(indexedElementsCount);
+                List<Integer> keyFieldSourceIndicators = new ArrayList<>(indexedElementsCount);
+                if (isSecondaryPrimary) {
+                    // BACK-COMPAT: secondary primary index has one source indicator
+                    // which is set to META_RECORD_INDICATOR
+                    keyFieldSourceIndicators.add(Index.META_RECORD_INDICATOR);
+                } else {
+                    for (int i = 0; i < indexedElementsCount; i++) {
+                        CreateIndexStatement.IndexedElement indexedElement = indexedElements.get(i);
+                        keyFieldNames.add(indexedElement.getProjectList().get(0).first);
+                        keyFieldTypes.add(indexFieldTypes.get(i).get(0));
+                        keyFieldSourceIndicators.add(indexedElement.getSourceIndicator());
+                    }
+                }
+                switch (Index.IndexCategory.of(indexType)) {
+                    case VALUE:
+                        indexDetails = new Index.ValueIndexDetails(keyFieldNames, keyFieldSourceIndicators,
+                                keyFieldTypes, overridesFieldTypes);
+                        break;
+                    case TEXT:
+                        indexDetails = new Index.TextIndexDetails(keyFieldNames, keyFieldSourceIndicators,
+                                keyFieldTypes, overridesFieldTypes, stmtCreateIndex.getGramLength(),
+                                stmtCreateIndex.getFullTextConfigName());
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc,
+                                String.valueOf(indexType));
+                }
+            }
+
+            Index newIndex = new Index(dataverseName, datasetName, indexName, indexType, indexDetails,
+                    stmtCreateIndex.isEnforced(), false, MetadataUtil.PENDING_ADD_OP);
 
             bActiveTxn = false; // doCreateIndexImpl() takes over the current transaction
             doCreateIndexImpl(hcc, metadataProvider, ds, newIndex, jobFlags, sourceLoc);
@@ -1356,9 +1482,9 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                     // Add an entry for the files index
                     filesIndex = new Index(index.getDataverseName(), index.getDatasetName(),
                             IndexingConstants.getFilesIndexName(index.getDatasetName()), IndexType.BTREE,
-                            ExternalIndexingOperations.FILE_INDEX_FIELD_NAMES, null,
-                            ExternalIndexingOperations.FILE_INDEX_FIELD_TYPES, false, false, false,
-                            MetadataUtil.PENDING_ADD_OP);
+                            new Index.ValueIndexDetails(ExternalIndexingOperations.FILE_INDEX_FIELD_NAMES, null,
+                                    ExternalIndexingOperations.FILE_INDEX_FIELD_TYPES, false),
+                            false, false, MetadataUtil.PENDING_ADD_OP);
                     MetadataManager.INSTANCE.addIndex(metadataProvider.getMetadataTxnContext(), filesIndex);
                     // Add files to the external files index
                     for (ExternalFile file : externalFilesSnapshot) {
@@ -1367,10 +1493,6 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                     // This is the first index for the external dataset, replicate the files index
                     spec = ExternalIndexingOperations.buildFilesIndexCreateJobSpec(ds, externalFilesSnapshot,
                             metadataProvider);
-                    if (spec == null) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                                "Failed to create job spec for replicating Files Index For external dataset");
-                    }
                     filesIndexReplicated = true;
                     runJob(hcc, spec, jobFlags);
                 }
@@ -1378,16 +1500,54 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
 
             // check whether there exists another enforced index on the same field
             if (index.isEnforced()) {
+                List<List<String>> indexKeyFieldNames;
+                List<IAType> indexKeyFieldTypes;
+                switch (Index.IndexCategory.of(index.getIndexType())) {
+                    case VALUE:
+                        Index.ValueIndexDetails valueIndexDetails = (Index.ValueIndexDetails) index.getIndexDetails();
+                        indexKeyFieldNames = valueIndexDetails.getKeyFieldNames();
+                        indexKeyFieldTypes = valueIndexDetails.getKeyFieldTypes();
+                        break;
+                    case TEXT:
+                        Index.TextIndexDetails textIndexDetails = (Index.TextIndexDetails) index.getIndexDetails();
+                        indexKeyFieldNames = textIndexDetails.getKeyFieldNames();
+                        indexKeyFieldTypes = textIndexDetails.getKeyFieldTypes();
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc, "");
+                }
                 List<Index> indexes = MetadataManager.INSTANCE.getDatasetIndexes(
                         metadataProvider.getMetadataTxnContext(), index.getDataverseName(), index.getDatasetName());
                 for (Index existingIndex : indexes) {
-                    if (existingIndex.getKeyFieldNames().equals(index.getKeyFieldNames())
-                            && !existingIndex.getKeyFieldTypes().equals(index.getKeyFieldTypes())
-                            && existingIndex.isEnforced()) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc, "Cannot create index "
-                                + index.getIndexName() + " , enforced index " + existingIndex.getIndexName()
-                                + " on field \"" + StringUtils.join(index.getKeyFieldNames(), ',')
-                                + "\" is already defined with type \"" + existingIndex.getKeyFieldTypes() + "\"");
+                    if (!existingIndex.isEnforced()) {
+                        continue;
+                    }
+                    List<List<String>> existingIndexKeyFieldNames;
+                    List<IAType> existingIndexKeyFieldTypes;
+                    switch (Index.IndexCategory.of(existingIndex.getIndexType())) {
+                        case VALUE:
+                            Index.ValueIndexDetails valueIndexDetails =
+                                    (Index.ValueIndexDetails) existingIndex.getIndexDetails();
+                            existingIndexKeyFieldNames = valueIndexDetails.getKeyFieldNames();
+                            existingIndexKeyFieldTypes = valueIndexDetails.getKeyFieldTypes();
+                            break;
+                        case TEXT:
+                            Index.TextIndexDetails textIndexDetails =
+                                    (Index.TextIndexDetails) existingIndex.getIndexDetails();
+                            existingIndexKeyFieldNames = textIndexDetails.getKeyFieldNames();
+                            existingIndexKeyFieldTypes = textIndexDetails.getKeyFieldTypes();
+                            break;
+                        default:
+                            // ARRAY indexed cannot be enforced yet.
+                            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, sourceLoc, "");
+                    }
+                    if (existingIndexKeyFieldNames.equals(indexKeyFieldNames)
+                            && !existingIndexKeyFieldTypes.equals(indexKeyFieldTypes)) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                                "Cannot create index " + index.getIndexName() + " , enforced index "
+                                        + existingIndex.getIndexName() + " on field \""
+                                        + StringUtils.join(indexKeyFieldNames, ',')
+                                        + "\" is already defined with type \"" + existingIndexKeyFieldTypes + "\"");
                     }
                 }
             }
@@ -1552,13 +1712,15 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
         if (datasetType == DatasetType.EXTERNAL && isSecondaryPrimaryIndex) {
             throw new CompilationException(ErrorCode.CANNOT_CREATE_SEC_PRIMARY_IDX_ON_EXT_DATASET);
         }
+        if (indexType != IndexType.BTREE && isSecondaryPrimaryIndex) {
+            throw new CompilationException(ErrorCode.COMPILATION_INCOMPATIBLE_INDEX_TYPE, sourceLoc,
+                    String.valueOf(indexType));
+        }
     }
 
-    protected void validateIndexKeyFields(CreateIndexStatement stmtCreateIndex, List<Integer> keySourceIndicators,
-            ARecordType aRecordType, ARecordType metaRecordType, List<List<String>> indexFields,
-            List<IAType> indexFieldTypes) throws AlgebricksException {
-        ValidateUtil.validateKeyFields(aRecordType, metaRecordType, indexFields, keySourceIndicators, indexFieldTypes,
-                stmtCreateIndex.getIndexType(), stmtCreateIndex.getSourceLocation());
+    protected void validateIndexFieldType(IndexType indexType, IAType fieldType, List<String> displayFieldName,
+            SourceLocation sourceLoc) throws AlgebricksException {
+        ValidateUtil.validateIndexFieldType(indexType, fieldType, displayFieldName, sourceLoc);
     }
 
     protected void handleCreateTypeStatement(MetadataProvider metadataProvider, Statement stmt) throws Exception {
@@ -1950,10 +2112,8 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                 // #. mark PendingDropOp on the existing index
                 MetadataManager.INSTANCE.dropIndex(mdTxnCtx, dataverseName, datasetName, indexName);
                 MetadataManager.INSTANCE.addIndex(mdTxnCtx,
-                        new Index(dataverseName, datasetName, indexName, index.getIndexType(), index.getKeyFieldNames(),
-                                index.getKeyFieldSourceIndicators(), index.getKeyFieldTypes(),
-                                index.isOverridingKeyFieldTypes(), index.isEnforced(), index.isPrimaryIndex(),
-                                MetadataUtil.PENDING_DROP_OP));
+                        new Index(dataverseName, datasetName, indexName, index.getIndexType(), index.getIndexDetails(),
+                                index.isEnforced(), index.isPrimaryIndex(), MetadataUtil.PENDING_DROP_OP));
 
                 // #. commit the existing transaction before calling runJob.
                 MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
@@ -2003,10 +2163,9 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                                     externalIndex.getIndexName());
                             MetadataManager.INSTANCE.addIndex(mdTxnCtx,
                                     new Index(dataverseName, datasetName, externalIndex.getIndexName(),
-                                            externalIndex.getIndexType(), externalIndex.getKeyFieldNames(),
-                                            externalIndex.getKeyFieldSourceIndicators(), index.getKeyFieldTypes(),
-                                            index.isOverridingKeyFieldTypes(), index.isEnforced(),
-                                            externalIndex.isPrimaryIndex(), MetadataUtil.PENDING_DROP_OP));
+                                            externalIndex.getIndexType(), externalIndex.getIndexDetails(),
+                                            externalIndex.isEnforced(), externalIndex.isPrimaryIndex(),
+                                            MetadataUtil.PENDING_DROP_OP));
                         }
                     }
                 }
@@ -2014,10 +2173,8 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                 // #. mark PendingDropOp on the existing index
                 MetadataManager.INSTANCE.dropIndex(mdTxnCtx, dataverseName, datasetName, indexName);
                 MetadataManager.INSTANCE.addIndex(mdTxnCtx,
-                        new Index(dataverseName, datasetName, indexName, index.getIndexType(), index.getKeyFieldNames(),
-                                index.getKeyFieldSourceIndicators(), index.getKeyFieldTypes(),
-                                index.isOverridingKeyFieldTypes(), index.isEnforced(), index.isPrimaryIndex(),
-                                MetadataUtil.PENDING_DROP_OP));
+                        new Index(dataverseName, datasetName, indexName, index.getIndexType(), index.getIndexDetails(),
+                                index.isEnforced(), index.isPrimaryIndex(), MetadataUtil.PENDING_DROP_OP));
 
                 // #. commit the existing transaction before calling runJob.
                 MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java
index 1040781..c668fb6 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java
@@ -201,7 +201,8 @@ public class TestNodeController {
                     storageComponentProvider.getStorageManager(), secondaryIndexInfo.fileSplitProvider);
             IIndexDataflowHelperFactory primaryIndexHelperFactory = new IndexDataflowHelperFactory(
                     storageComponentProvider.getStorageManager(), primaryIndexInfo.getFileSplitProvider());
-            int[] fieldPermutation = new int[secondaryIndex.getKeyFieldNames().size()];
+            Index.ValueIndexDetails secondaryIndexDetails = (Index.ValueIndexDetails) secondaryIndex.getIndexDetails();
+            int[] fieldPermutation = new int[secondaryIndexDetails.getKeyFieldNames().size()];
             for (int i = 0; i < fieldPermutation.length; i++) {
                 fieldPermutation[i] = i;
             }
@@ -257,8 +258,10 @@ public class TestNodeController {
             // for the index, we will have to create an assign operator that extract the sk
             // then the secondary LSMInsertDeleteOperatorNodePushable
             if (secondaryIndex != null) {
-                List<List<String>> skNames = secondaryIndex.getKeyFieldNames();
-                List<Integer> indicators = secondaryIndex.getKeyFieldSourceIndicators();
+                Index.ValueIndexDetails secondaryIndexDetails =
+                        (Index.ValueIndexDetails) secondaryIndex.getIndexDetails();
+                List<List<String>> skNames = secondaryIndexDetails.getKeyFieldNames();
+                List<Integer> indicators = secondaryIndexDetails.getKeyFieldSourceIndicators();
                 IScalarEvaluatorFactory[] secondaryFieldAccessEvalFactories =
                         new IScalarEvaluatorFactory[skNames.size()];
                 for (int i = 0; i < skNames.size(); i++) {
@@ -266,14 +269,15 @@ public class TestNodeController {
                             ? indicators.get(i).intValue() == Index.RECORD_INDICATOR ? recordType : metaType
                             : recordType;
                     int pos = skNames.get(i).size() > 1 ? -1 : sourceType.getFieldIndex(skNames.get(i).get(0));
-                    secondaryFieldAccessEvalFactories[i] =
-                            mdProvider.getDataFormat().getFieldAccessEvaluatorFactory(mdProvider.getFunctionManager(),
-                                    sourceType, secondaryIndex.getKeyFieldNames().get(i), pos, null);
+                    secondaryFieldAccessEvalFactories[i] = mdProvider.getDataFormat().getFieldAccessEvaluatorFactory(
+                            mdProvider.getFunctionManager(), sourceType, skNames.get(i), pos, null);
                 }
                 // outColumns are computed inside the assign runtime
                 int[] outColumns = new int[skNames.size()];
                 // projection list include old and new (primary and secondary keys)
-                int[] projectionList = new int[skNames.size() + primaryIndexInfo.index.getKeyFieldNames().size()];
+                Index.ValueIndexDetails primaryIndexDetails =
+                        (Index.ValueIndexDetails) primaryIndexInfo.index.getIndexDetails();
+                int[] projectionList = new int[skNames.size() + primaryIndexDetails.getKeyFieldNames().size()];
                 for (int i = 0; i < secondaryFieldAccessEvalFactories.length; i++) {
                     outColumns[i] = primaryIndexInfo.rDesc.getFieldCount() + i;
                 }
@@ -281,7 +285,7 @@ public class TestNodeController {
                 for (int i = 0; i < secondaryFieldAccessEvalFactories.length; i++) {
                     projectionList[projCount++] = primaryIndexInfo.rDesc.getFieldCount() + i;
                 }
-                for (int i = 0; i < primaryIndexInfo.index.getKeyFieldNames().size(); i++) {
+                for (int i = 0; i < primaryIndexDetails.getKeyFieldNames().size(); i++) {
                     projectionList[projCount++] = i;
                 }
                 IPushRuntime assignOp =
@@ -354,8 +358,10 @@ public class TestNodeController {
             // for the index, we will have to create an assign operator that extract the sk
             // then the secondary LSMInsertDeleteOperatorNodePushable
             if (secondaryIndex != null) {
-                List<List<String>> skNames = secondaryIndex.getKeyFieldNames();
-                List<Integer> indicators = secondaryIndex.getKeyFieldSourceIndicators();
+                Index.ValueIndexDetails secondaryIndexDetails =
+                        (Index.ValueIndexDetails) secondaryIndex.getIndexDetails();
+                List<List<String>> skNames = secondaryIndexDetails.getKeyFieldNames();
+                List<Integer> indicators = secondaryIndexDetails.getKeyFieldSourceIndicators();
                 IScalarEvaluatorFactory[] secondaryFieldAccessEvalFactories =
                         new IScalarEvaluatorFactory[skNames.size()];
                 for (int i = 0; i < skNames.size(); i++) {
@@ -363,14 +369,15 @@ public class TestNodeController {
                             ? indicators.get(i).intValue() == Index.RECORD_INDICATOR ? recordType : metaType
                             : recordType;
                     int pos = skNames.get(i).size() > 1 ? -1 : sourceType.getFieldIndex(skNames.get(i).get(0));
-                    secondaryFieldAccessEvalFactories[i] =
-                            mdProvider.getDataFormat().getFieldAccessEvaluatorFactory(mdProvider.getFunctionManager(),
-                                    sourceType, secondaryIndex.getKeyFieldNames().get(i), pos, null);
+                    secondaryFieldAccessEvalFactories[i] = mdProvider.getDataFormat().getFieldAccessEvaluatorFactory(
+                            mdProvider.getFunctionManager(), sourceType, skNames.get(i), pos, null);
                 }
                 // outColumns are computed inside the assign runtime
                 int[] outColumns = new int[skNames.size()];
                 // projection list include old and new (primary and secondary keys)
-                int[] projectionList = new int[skNames.size() + primaryIndexInfo.index.getKeyFieldNames().size()];
+                Index.ValueIndexDetails primaryIndexDetails =
+                        (Index.ValueIndexDetails) primaryIndexInfo.index.getIndexDetails();
+                int[] projectionList = new int[skNames.size() + primaryIndexDetails.getKeyFieldNames().size()];
                 for (int i = 0; i < secondaryFieldAccessEvalFactories.length; i++) {
                     outColumns[i] = primaryIndexInfo.rDesc.getFieldCount() + i;
                 }
@@ -378,7 +385,7 @@ public class TestNodeController {
                 for (int i = 0; i < secondaryFieldAccessEvalFactories.length; i++) {
                     projectionList[projCount++] = primaryIndexInfo.rDesc.getFieldCount() + i;
                 }
-                for (int i = 0; i < primaryIndexInfo.index.getKeyFieldNames().size(); i++) {
+                for (int i = 0; i < primaryIndexDetails.getKeyFieldNames().size(); i++) {
                     projectionList[projCount++] = i;
                 }
                 IPushRuntime assignOp =
@@ -632,6 +639,7 @@ public class TestNodeController {
         public SecondaryIndexInfo(PrimaryIndexInfo primaryIndexInfo, Index secondaryIndex) {
             this.primaryIndexInfo = primaryIndexInfo;
             this.secondaryIndex = secondaryIndex;
+            Index.ValueIndexDetails secondaryIndexDetails = (Index.ValueIndexDetails) secondaryIndex.getIndexDetails();
             List<String> nodes = Collections.singletonList(ExecutionTestUtil.integrationUtil.ncs[0].getId());
             CcApplicationContext appCtx =
                     (CcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext();
@@ -639,11 +647,11 @@ public class TestNodeController {
                     primaryIndexInfo.dataset, secondaryIndex.getIndexName(), nodes);
             fileSplitProvider = new ConstantFileSplitProvider(splits);
             secondaryIndexTypeTraits = createSecondaryIndexTypeTraits(primaryIndexInfo.recordType,
-                    primaryIndexInfo.metaType, primaryIndexInfo.primaryKeyTypes,
-                    secondaryIndex.getKeyFieldTypes().toArray(new IAType[secondaryIndex.getKeyFieldTypes().size()]));
+                    primaryIndexInfo.metaType, primaryIndexInfo.primaryKeyTypes, secondaryIndexDetails
+                            .getKeyFieldTypes().toArray(new IAType[secondaryIndexDetails.getKeyFieldTypes().size()]));
             secondaryIndexSerdes = createSecondaryIndexSerdes(primaryIndexInfo.recordType, primaryIndexInfo.metaType,
-                    primaryIndexInfo.primaryKeyTypes,
-                    secondaryIndex.getKeyFieldTypes().toArray(new IAType[secondaryIndex.getKeyFieldTypes().size()]));
+                    primaryIndexInfo.primaryKeyTypes, secondaryIndexDetails.getKeyFieldTypes()
+                            .toArray(new IAType[secondaryIndexDetails.getKeyFieldTypes().size()]));
             rDesc = new RecordDescriptor(secondaryIndexSerdes, secondaryIndexTypeTraits);
             insertFieldsPermutations = new int[secondaryIndexTypeTraits.length];
             for (int i = 0; i < insertFieldsPermutations.length; i++) {
@@ -651,7 +659,7 @@ public class TestNodeController {
             }
             primaryKeyIndexes = new int[primaryIndexInfo.primaryKeyIndexes.length];
             for (int i = 0; i < primaryKeyIndexes.length; i++) {
-                primaryKeyIndexes[i] = i + secondaryIndex.getKeyFieldNames().size();
+                primaryKeyIndexes[i] = i + secondaryIndexDetails.getKeyFieldNames().size();
             }
         }
 
@@ -711,7 +719,8 @@ public class TestNodeController {
                 keyFieldNames.add(Arrays.asList(fieldNames[primaryKeyIndexes[i]]));
             }
             index = new Index(dataset.getDataverseName(), dataset.getDatasetName(), dataset.getDatasetName(),
-                    IndexType.BTREE, keyFieldNames, primaryKeyIndicators, keyFieldTypes, false, false, true,
+                    IndexType.BTREE,
+                    new Index.ValueIndexDetails(keyFieldNames, primaryKeyIndicators, keyFieldTypes, false), false, true,
                     MetadataUtil.PENDING_NO_OP);
             List<String> nodes = Collections.singletonList(ExecutionTestUtil.integrationUtil.ncs[0].getId());
             CcApplicationContext appCtx =
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestDataset.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestDataset.java
index f28e9bb..f87757c 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestDataset.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestDataset.java
@@ -55,14 +55,14 @@ public class TestDataset extends Dataset {
     }
 
     @Override
-    public IPushRuntimeFactory getCommitRuntimeFactory(MetadataProvider metadataProvider,
-            int[] primaryKeyFieldPermutation, boolean isSink) throws AlgebricksException {
+    public IPushRuntimeFactory getCommitRuntimeFactory(MetadataProvider metadataProvider, int[] keyFieldPermutation,
+            boolean isSink) throws AlgebricksException {
         return new IPushRuntimeFactory() {
             @Override
             public IPushRuntime[] createPushRuntime(IHyracksTaskContext ctx) throws HyracksDataException {
-                return new IPushRuntime[] { new CommitRuntime(ctx, new TxnId(ctx.getJobletContext().getJobId().getId()),
-                        getDatasetId(), primaryKeyFieldPermutation, true,
-                        ctx.getTaskAttemptId().getTaskId().getPartition(), true) };
+                return new IPushRuntime[] {
+                        new CommitRuntime(ctx, new TxnId(ctx.getJobletContext().getJobId().getId()), getDatasetId(),
+                                keyFieldPermutation, true, ctx.getTaskAttemptId().getTaskId().getPartition(), true) };
             }
         };
     }
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestLsmBTreeResourceFactoryProvider.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestLsmBTreeResourceFactoryProvider.java
index 532337d..9818538 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestLsmBTreeResourceFactoryProvider.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/dataflow/TestLsmBTreeResourceFactoryProvider.java
@@ -98,20 +98,21 @@ public class TestLsmBTreeResourceFactoryProvider implements IResourceFactoryProv
                 && index.getIndexName().equals(IndexingConstants.getFilesIndexName(dataset.getDatasetName()))) {
             return FilesIndexDescription.EXTERNAL_FILE_INDEX_TYPE_TRAITS;
         }
+        Index.ValueIndexDetails indexDetails = (Index.ValueIndexDetails) index.getIndexDetails();
         int numPrimaryKeys = dataset.getPrimaryKeys().size();
-        int numSecondaryKeys = index.getKeyFieldNames().size();
+        int numSecondaryKeys = indexDetails.getKeyFieldNames().size();
         ITypeTraitProvider typeTraitProvider = metadataProvider.getStorageComponentProvider().getTypeTraitProvider();
         ITypeTraits[] secondaryTypeTraits = new ITypeTraits[numSecondaryKeys + numPrimaryKeys];
         for (int i = 0; i < numSecondaryKeys; i++) {
             ARecordType sourceType;
-            List<Integer> keySourceIndicators = index.getKeyFieldSourceIndicators();
+            List<Integer> keySourceIndicators = indexDetails.getKeyFieldSourceIndicators();
             if (keySourceIndicators == null || keySourceIndicators.get(i) == 0) {
                 sourceType = recordType;
             } else {
                 sourceType = metaType;
             }
-            Pair<IAType, Boolean> keyTypePair = Index.getNonNullableOpenFieldType(index.getKeyFieldTypes().get(i),
-                    index.getKeyFieldNames().get(i), sourceType);
+            Pair<IAType, Boolean> keyTypePair = Index.getNonNullableOpenFieldType(
+                    indexDetails.getKeyFieldTypes().get(i), indexDetails.getKeyFieldNames().get(i), sourceType);
             IAType keyType = keyTypePair.first;
             secondaryTypeTraits[i] = typeTraitProvider.getTypeTrait(keyType);
         }
@@ -132,22 +133,23 @@ public class TestLsmBTreeResourceFactoryProvider implements IResourceFactoryProv
                 && index.getIndexName().equals(IndexingConstants.getFilesIndexName(dataset.getDatasetName()))) {
             return FilesIndexDescription.FILES_INDEX_COMP_FACTORIES;
         }
+        Index.ValueIndexDetails indexDetails = (Index.ValueIndexDetails) index.getIndexDetails();
         int numPrimaryKeys = dataset.getPrimaryKeys().size();
-        int numSecondaryKeys = index.getKeyFieldNames().size();
+        int numSecondaryKeys = indexDetails.getKeyFieldNames().size();
         IBinaryComparatorFactoryProvider cmpFactoryProvider =
                 metadataProvider.getStorageComponentProvider().getComparatorFactoryProvider();
         IBinaryComparatorFactory[] secondaryCmpFactories =
                 new IBinaryComparatorFactory[numSecondaryKeys + numPrimaryKeys];
         for (int i = 0; i < numSecondaryKeys; i++) {
             ARecordType sourceType;
-            List<Integer> keySourceIndicators = index.getKeyFieldSourceIndicators();
+            List<Integer> keySourceIndicators = indexDetails.getKeyFieldSourceIndicators();
             if (keySourceIndicators == null || keySourceIndicators.get(i) == 0) {
                 sourceType = recordType;
             } else {
                 sourceType = metaType;
             }
-            Pair<IAType, Boolean> keyTypePair = Index.getNonNullableOpenFieldType(index.getKeyFieldTypes().get(i),
-                    index.getKeyFieldNames().get(i), sourceType);
+            Pair<IAType, Boolean> keyTypePair = Index.getNonNullableOpenFieldType(
+                    indexDetails.getKeyFieldTypes().get(i), indexDetails.getKeyFieldNames().get(i), sourceType);
             IAType keyType = keyTypePair.first;
             secondaryCmpFactories[i] = cmpFactoryProvider.getBinaryComparatorFactory(keyType, true);
         }
@@ -166,14 +168,14 @@ public class TestLsmBTreeResourceFactoryProvider implements IResourceFactoryProv
             if (index.getIndexName().equals(IndexingConstants.getFilesIndexName(dataset.getDatasetName()))) {
                 return FilesIndexDescription.BLOOM_FILTER_FIELDS;
             } else {
-                return new int[] { index.getKeyFieldNames().size() };
+                return new int[] { ((Index.ValueIndexDetails) index.getIndexDetails()).getKeyFieldNames().size() };
             }
         } else if (index.getIndexType() == IndexType.BTREE || index.getIndexType() == IndexType.RTREE) {
             // secondary btrees and rtrees do not have bloom filters
             return null;
         } else {
             // inverted indexes have bloom filters on deleted-key btrees
-            int numKeys = index.getKeyFieldNames().size();
+            int numKeys = ((Index.ValueIndexDetails) index.getIndexDetails()).getKeyFieldNames().size();
             int[] bloomFilterKeyFields = new int[numKeys];
             for (int i = 0; i < numKeys; i++) {
                 bloomFilterKeyFields[i] = i;
diff --git a/asterixdb/asterix-app/src/test/resources/metadata/testsuite.xml b/asterixdb/asterix-app/src/test/resources/metadata/testsuite.xml
index 8af4c67..a29dc48 100644
--- a/asterixdb/asterix-app/src/test/resources/metadata/testsuite.xml
+++ b/asterixdb/asterix-app/src/test/resources/metadata/testsuite.xml
@@ -524,37 +524,37 @@
     <test-case FilePath="exception">
       <compilation-unit name="issue_384_create_index_error_1">
         <output-dir compare="Text">none</output-dir>
-        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the BTree index. (in line 37, at column 1)</expected-error>
+        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the BTree index. (in line 37, at column 33)</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="exception">
       <compilation-unit name="issue_384_create_index_error_2">
         <output-dir compare="Text">none</output-dir>
-        <expected-error>ASX1079: Compilation error: The field "[age]" which is of type integer cannot be indexed using the RTree index. (in line 37, at column 1)</expected-error>
+        <expected-error>ASX1079: Compilation error: The field "[age]" which is of type integer cannot be indexed using the RTree index. (in line 37, at column 33)</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="exception">
       <compilation-unit name="issue_384_create_index_error_3">
         <output-dir compare="Text">none</output-dir>
-        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned Keyword index. (in line 37, at column 1)</expected-error>
+        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned Keyword index. (in line 37, at column 33)</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="exception">
       <compilation-unit name="issue_384_create_index_error_4">
         <output-dir compare="Text">none</output-dir>
-        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned Keyword index. (in line 37, at column 1)</expected-error>
+        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned Keyword index. (in line 37, at column 33)</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="exception">
       <compilation-unit name="issue_384_create_index_error_5">
         <output-dir compare="Text">none</output-dir>
-        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned N-Gram index. (in line 37, at column 1)</expected-error>
+        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned N-Gram index. (in line 37, at column 33)</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="exception">
       <compilation-unit name="issue_384_create_index_error_6">
         <output-dir compare="Text">none</output-dir>
-        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned N-Gram index. (in line 37, at column 1)</expected-error>
+        <expected-error>ASX1079: Compilation error: The field "[loc]" which is of type point cannot be indexed using the Length Partitioned N-Gram index. (in line 37, at column 33)</expected-error>
       </compilation-unit>
     </test-case>
   </test-group>
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query1.sqlpp
new file mode 100644
index 0000000..3960ee6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query1.sqlpp
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.dates D
+WHERE D /*+ indexnl */ = M.datetime
+      AND C.business_id = "--Ni3oJ4VOqfOEu7Sj2Vzg";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query2.sqlpp
new file mode 100644
index 0000000..a231ac7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query2.sqlpp
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.dates D
+WHERE D /*+ indexnl */ = M.datetime
+      AND M.time = "19:49:16";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query3.sqlpp
new file mode 100644
index 0000000..8fe3c15
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-1/query3.sqlpp
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckinDateMarkers M
+INNER JOIN (
+    SELECT VALUE D
+    FROM YelpCheckin C, C.dates D
+) AS CD ON CD /*+ indexnl */ = M.datetime;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query1.sqlpp
new file mode 100644
index 0000000..c10c2a9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query1.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times.dates D
+WHERE D /*+ indexnl */ = M.date
+      AND C.business_id = "--Ni3oJ4VOqfOEu7Sj2Vzg";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query2.sqlpp
new file mode 100644
index 0000000..8e5cf1e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query2.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times.dates D
+WHERE D /*+ indexnl */ = M.date
+      AND M.time = "19:49:16";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query3.sqlpp
new file mode 100644
index 0000000..b8e9b49
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-2/query3.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT COUNT(*)
+FROM YelpCheckinDateMarkers M
+INNER JOIN (
+    SELECT VALUE D
+    FROM YelpCheckin C, C.checkin_times.dates D
+) AS CD ON CD /*+ indexnl */ = M.date;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query1.sqlpp
new file mode 100644
index 0000000..91f6d8a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query1.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times D
+WHERE D.date /*+ indexnl */ = M.date
+      AND C.business_id = "--Ni3oJ4VOqfOEu7Sj2Vzg";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query2.sqlpp
new file mode 100644
index 0000000..d4742ce
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query2.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times D
+WHERE D.date /*+ indexnl */ = M.date
+      AND D.time = "19:49:16";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query3.sqlpp
new file mode 100644
index 0000000..e3afa1e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query3.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times D
+WHERE D.date /*+ indexnl */ = M.date
+      AND M.time = "19:49:16";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query4.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query4.sqlpp
new file mode 100644
index 0000000..f5d3c52
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-3/query4.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT COUNT(*)
+FROM YelpCheckinDateMarkers M
+INNER JOIN (
+    SELECT VALUE D.date
+    FROM YelpCheckin C, C.checkin_times D
+) AS CD ON CD /*+ indexnl */ = M.date;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query1.sqlpp
new file mode 100644
index 0000000..1afd508
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query1.sqlpp
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        checkin_times_id: int,
+        dates: [string],
+        times: [string]
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times CT, CT.dates D
+WHERE D /*+ indexnl */ = M.date
+      AND C.business_id = "--Ni3oJ4VOqfOEu7Sj2Vzg";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query2.sqlpp
new file mode 100644
index 0000000..b911a20
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query2.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        checkin_times_id: int,
+        dates: [string],
+        times: [string]
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times CT, CT.dates D
+WHERE D /*+ indexnl */ = M.date
+      AND CT.checkin_times_id = 1
+      AND C.business_id = "--Ni3oJ4VOqfOEu7Sj2Vzg";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query3.sqlpp
new file mode 100644
index 0000000..5a23cb4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query3.sqlpp
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        checkin_times_id: int,
+        dates: [string],
+        times: [string]
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT M.marker
+FROM YelpCheckinDateMarkers M, YelpCheckin C, C.checkin_times CT, CT.dates D
+WHERE D /*+ indexnl */ = M.date
+      AND M.time = "19:49:16";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query4.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query4.sqlpp
new file mode 100644
index 0000000..c142d1a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/join-unnest-queries/use-case-4/query4.sqlpp
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        checkin_times_id: int,
+        dates: [string],
+        times: [string]
+    }]
+};
+CREATE TYPE CheckinDatesToMarkersType AS {
+    marker: string,
+    datetime: string,
+    date: string,
+    time: string
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE DATASET YelpCheckinDateMarkers (CheckinDatesToMarkersType) PRIMARY KEY marker;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckinDateMarkers M
+INNER JOIN (
+    SELECT VALUE D
+    FROM YelpCheckin C, C.checkin_times CT, CT.dates D
+) AS CD ON CD /*+ indexnl */ = M.date;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query1.sqlpp
new file mode 100644
index 0000000..f267701
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query1.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT C.business_id
+FROM YelpCheckin C 
+WHERE "2016-04-26 19:49:16" IN C.dates;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query2.sqlpp
new file mode 100644
index 0000000..6cbfdff
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query2.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C
+WHERE SOME D IN C.dates
+SATISFIES D > "2016" AND D < "2017";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query3.sqlpp
new file mode 100644
index 0000000..d2dc752
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-1/query3.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C
+WHERE LEN(C.dates) > 0 AND
+      (EVERY D IN C.dates
+       SATISFIES D > "2016" AND D < "2017");
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query1.sqlpp
new file mode 100644
index 0000000..145f8f21
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query1.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT C.business_id
+FROM YelpCheckin C
+WHERE "2016-04-26" IN C.checkin_times.dates;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query2.sqlpp
new file mode 100644
index 0000000..65e15fc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query2.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C
+WHERE SOME D IN C.checkin_times.dates 
+SATISFIES D > "2016" AND D < "2017";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query3.sqlpp
new file mode 100644
index 0000000..24ae2e4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-2/query3.sqlpp
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C
+WHERE LEN(C.checkin_times.dates) > 0 AND
+      (EVERY D IN C.checkin_times.dates
+       SATISFIES D > "2016" AND D < "2017");
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query1.sqlpp
new file mode 100644
index 0000000..3c07b9d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query1.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT C.business_id
+FROM YelpCheckin C
+WHERE SOME D IN C.checkin_times 
+SATISFIES "2016-04-26" = D.date;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query2.sqlpp
new file mode 100644
index 0000000..c8d5c23
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query2.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT C.business_id
+FROM YelpCheckin C
+WHERE SOME D IN C.checkin_times 
+SATISFIES D.date = "2016-04-26" AND D.time = "19:49:16";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query3.sqlpp
new file mode 100644
index 0000000..52f533d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-3/query3.sqlpp
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT C.business_id
+FROM YelpCheckin C
+WHERE LEN(C.checkin_times) > 0 AND
+      (EVERY D IN C.checkin_times
+       SATISFIES D.date BETWEEN "2016" AND "2017");
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query1.sqlpp
new file mode 100644
index 0000000..1bd9564
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query1.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        dates: [string],
+        times: [string]
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT C.business_id
+FROM YelpCheckin C
+WHERE SOME D IN C.checkin_times
+SATISFIES "2016-04-26" IN D.dates;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query2.sqlpp
new file mode 100644
index 0000000..204e589
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query2.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        dates: [string],
+        times: [string]
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT C.business_id
+FROM YelpCheckin C
+WHERE SOME CT IN C.checkin_times
+SATISFIES (
+    SOME D IN CT.dates
+    SATISFIES D = "2016-04-26"
+);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query3.sqlpp
new file mode 100644
index 0000000..8b3fd61
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-quantified-queries/use-case-4/query3.sqlpp
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        dates: [string],
+        times: [string]
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+/* TODO (GLENN) Support checking for nested length clauses (i.e. EVERY, then EVERY). */
+SELECT C.business_id
+FROM YelpCheckin C
+WHERE LEN(C.checkin_times) > 0 AND
+      (EVERY CT IN C.checkin_times
+       SATISFIES (
+           SOME D IN CT.dates
+           SATISFIES "2019-06-07" = D
+      ));
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-1/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-1/query1.sqlpp
new file mode 100644
index 0000000..d4bb5ff
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-1/query1.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.dates D
+WHERE "2016-04-26 19:49:16" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-1/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-1/query2.sqlpp
new file mode 100644
index 0000000..ae997b3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-1/query2.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-2/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-2/query1.sqlpp
new file mode 100644
index 0000000..602e924
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-2/query1.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times.dates D
+WHERE "2016-04-26" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-2/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-2/query2.sqlpp
new file mode 100644
index 0000000..0d94cce
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-2/query2.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_times.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query1.sqlpp
new file mode 100644
index 0000000..52d826a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query1.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times D
+WHERE "2016-04-26" = D.date;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query2.sqlpp
new file mode 100644
index 0000000..0755f55
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query2.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_times D
+WHERE D.date > "2016" AND D.date < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query3.sqlpp
new file mode 100644
index 0000000..49949a0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-3/query3.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times D
+WHERE "2016-04-26" = D.date AND 
+      D.time = "19:49:16"; 
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-4/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-4/query1.sqlpp
new file mode 100644
index 0000000..2e3c0a8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-4/query1.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        dates: [string],
+        times: [string]
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times CT, CT.dates D
+WHERE "2016-04-26" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-4/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-4/query2.sqlpp
new file mode 100644
index 0000000..89dc790
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/use-case-4/query2.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        dates: [string],
+        times: [string]
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_times CT, CT.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-3-level-record-path/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-3-level-record-path/query1.sqlpp
new file mode 100644
index 0000000..660f62b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-3-level-record-path/query1.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_data: {
+        checkin_temporal: {
+            checkin_times: {
+                dates: [string],
+                times: [string]
+            }
+        }
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_data.checkin_temporal.checkin_times.dates);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_data.checkin_temporal.checkin_times.dates D
+WHERE "2016-04-26" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-3-level-record-path/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-3-level-record-path/query2.sqlpp
new file mode 100644
index 0000000..ce7fad8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-3-level-record-path/query2.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_data: {
+        checkin_temporal: {
+            checkin_times: {
+                dates: [string],
+                times: [string]
+            }
+        }
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_data.checkin_temporal.checkin_times.dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_data.checkin_temporal.checkin_times.dates D
+WHERE D > "2016" AND D < "2017";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-pk/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-pk/query1.sqlpp
new file mode 100644
index 0000000..80a6bcc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-pk/query1.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: smallint,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id, business_id;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.dates D
+WHERE "2016-04-26 19:49:16" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-pk/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-pk/query2.sqlpp
new file mode 100644
index 0000000..3ea8ac2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-pk/query2.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: smallint,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id, business_id;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query1.sqlpp
new file mode 100644
index 0000000..b9970c5
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query1.sqlpp
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDatesTimes ON YelpCheckin (UNNEST checkin_times SELECT date, time);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times D
+WHERE "2016-04-26" = D.date AND 
+      "19:49:16" = D.time AND
+      "--1UhMGODdWsrMastO9DZw" = C.business_id;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query2.sqlpp
new file mode 100644
index 0000000..022d5ea
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-composite-sk/query2.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDatesTimes ON YelpCheckin (UNNEST checkin_times SELECT date, time);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_times D
+WHERE D.date > "2016" AND D.date < "2017";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-filter-fields/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-filter-fields/query1.sqlpp
new file mode 100644
index 0000000..9b36d62
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-filter-fields/query1.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED WITH FILTER ON business_id;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.dates D
+WHERE "2016-04-26 19:49:16" = D AND 
+      C.business_id = "--1UhMGODdWsrMastO9DZw";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-filter-fields/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-filter-fields/query2.sqlpp
new file mode 100644
index 0000000..2cffd50
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/closed/with-filter-fields/query2.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED WITH FILTER ON business_id;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-1/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-1/query1.sqlpp
new file mode 100644
index 0000000..71c910f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-1/query1.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates : string ?) ;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.dates D
+WHERE "2016-04-26 19:49:16" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-1/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-1/query2.sqlpp
new file mode 100644
index 0000000..2d7e3a0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-1/query2.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates : string ?) ;
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-2/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-2/query1.sqlpp
new file mode 100644
index 0000000..c0f0377
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-2/query1.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates : string ?) ;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times.dates D
+WHERE "2016-04-26" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-2/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-2/query2.sqlpp
new file mode 100644
index 0000000..e624637
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-2/query2.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates : string ?) ;
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_times.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query1.sqlpp
new file mode 100644
index 0000000..7054aed
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query1.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date : string ? ) ;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times D
+WHERE "2016-04-26" = D.date;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query2.sqlpp
new file mode 100644
index 0000000..ecfd0c5
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query2.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date : string ?) ;
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_times D
+WHERE D.date > "2016" AND D.date < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query3.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query3.sqlpp
new file mode 100644
index 0000000..97506d7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-3/query3.sqlpp
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date : string ? ) ;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times D
+WHERE "2016-04-26" = D.date AND 
+      D.time = "19:49:16"; 
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-4/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-4/query1.sqlpp
new file mode 100644
index 0000000..464705e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-4/query1.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates : string ?) ;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times CT, CT.dates D
+WHERE "2016-04-26" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-4/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-4/query2.sqlpp
new file mode 100644
index 0000000..26fd6e8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/use-case-4/query2.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times UNNEST dates : string ?) ;
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_times CT, CT.dates D
+WHERE D > "2016" AND D < "2017";
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-3-level-record-path/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-3-level-record-path/query1.sqlpp
new file mode 100644
index 0000000..913d010
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-3-level-record-path/query1.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_data.checkin_temporal.checkin_times.dates : string ?) ;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_data.checkin_temporal.checkin_times.dates D
+WHERE "2016-04-26" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-3-level-record-path/query2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-3-level-record-path/query2.sqlpp
new file mode 100644
index 0000000..8d56541
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-3-level-record-path/query2.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_data.checkin_temporal.checkin_times.dates : string ?) ;
+
+SELECT COUNT(*)
+FROM YelpCheckin C, C.checkin_data.checkin_temporal.checkin_times.dates D
+WHERE D > "2016" AND D < "2017";
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-composite-sk/query1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-composite-sk/query1.sqlpp
new file mode 100644
index 0000000..3dcc3a2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/array-index/select-unnest-queries/open/with-composite-sk/query1.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDatesTimes ON YelpCheckin (UNNEST checkin_times SELECT date : string ?, time : string ?);
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times D
+WHERE "2016-04-26" = D.date AND 
+      "19:49:16" = D.time AND
+      "--1UhMGODdWsrMastO9DZw" = C.business_id;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query1.plan
new file mode 100644
index 0000000..e75ce49
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query1.plan
@@ -0,0 +1,29 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- STREAM_SELECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$49(ASC), $$42(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query2.plan
new file mode 100644
index 0000000..2c83436
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query2.plan
@@ -0,0 +1,29 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$49(ASC), $$42(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- STREAM_SELECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query3.plan
new file mode 100644
index 0000000..56db4c9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-1/query3.plan
@@ -0,0 +1,32 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STABLE_SORT [$$75(ASC), $$68(ASC)]  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query1.plan
new file mode 100644
index 0000000..99cd582
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query1.plan
@@ -0,0 +1,29 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$51(ASC), $$43(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query2.plan
new file mode 100644
index 0000000..6864a95
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query2.plan
@@ -0,0 +1,29 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$51(ASC), $$43(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- STREAM_SELECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query3.plan
new file mode 100644
index 0000000..b4ee1b0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-2/query3.plan
@@ -0,0 +1,32 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STABLE_SORT [$$77(ASC), $$69(ASC)]  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query1.plan
new file mode 100644
index 0000000..54e1f18
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query1.plan
@@ -0,0 +1,29 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- STREAM_SELECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$51(ASC), $$43(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query2.plan
new file mode 100644
index 0000000..0bc4272
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query2.plan
@@ -0,0 +1,29 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_SELECT  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- UNNEST  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$51(ASC), $$43(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                            -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query3.plan
new file mode 100644
index 0000000..6864a95
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query3.plan
@@ -0,0 +1,29 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$51(ASC), $$43(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- STREAM_SELECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query4.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query4.plan
new file mode 100644
index 0000000..f251b77
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-3/query4.plan
@@ -0,0 +1,32 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STABLE_SORT [$$77(ASC), $$70(ASC)]  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query1.plan
new file mode 100644
index 0000000..778f88d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query1.plan
@@ -0,0 +1,33 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- UNNEST  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- STREAM_SELECT  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STABLE_SORT [$$61(ASC), $$53(ASC)]  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                    -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                      -- STREAM_PROJECT  |PARTITIONED|
+                                                        -- ASSIGN  |PARTITIONED|
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query2.plan
new file mode 100644
index 0000000..8db17af
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query2.plan
@@ -0,0 +1,34 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- STREAM_SELECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- UNNEST  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- STREAM_SELECT  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STABLE_SORT [$$64(ASC), $$55(ASC)]  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                      -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          -- ASSIGN  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query3.plan
new file mode 100644
index 0000000..d28ee6d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query3.plan
@@ -0,0 +1,33 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- UNNEST  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ASSIGN  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STABLE_SORT [$$61(ASC), $$53(ASC)]  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                  -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- STREAM_SELECT  |PARTITIONED|
+                                                        -- ASSIGN  |PARTITIONED|
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query4.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query4.plan
new file mode 100644
index 0000000..22b9b1c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/join-unnest-queries/use-case-4/query4.plan
@@ -0,0 +1,36 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- UNNEST  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- STABLE_SORT [$$87(ASC), $$79(ASC)]  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                          -- BROADCAST_EXCHANGE  |PARTITIONED|
+                                                            -- STREAM_PROJECT  |PARTITIONED|
+                                                              -- ASSIGN  |PARTITIONED|
+                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                  -- DATASOURCE_SCAN (TestYelp.YelpCheckinDateMarkers)  |PARTITIONED|
+                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query1.plan
new file mode 100644
index 0000000..2c5b278
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query1.plan
@@ -0,0 +1,30 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- UNNEST  |LOCAL|
+                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$26(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query2.plan
new file mode 100644
index 0000000..761ed03
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query2.plan
@@ -0,0 +1,32 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- SUBPLAN  |PARTITIONED|
+                          {
+                            -- AGGREGATE  |LOCAL|
+                              -- STREAM_SELECT  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                          }
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$55(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query3.plan
new file mode 100644
index 0000000..f2819ea
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-1/query3.plan
@@ -0,0 +1,33 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- SUBPLAN  |PARTITIONED|
+                          {
+                            -- AGGREGATE  |LOCAL|
+                              -- STREAM_SELECT  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                          }
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ASSIGN  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$60(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- ASSIGN  |PARTITIONED|
+                                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query1.plan
new file mode 100644
index 0000000..e29b93c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query1.plan
@@ -0,0 +1,30 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- UNNEST  |LOCAL|
+                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$28(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query2.plan
new file mode 100644
index 0000000..9843256
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query2.plan
@@ -0,0 +1,32 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- SUBPLAN  |PARTITIONED|
+                          {
+                            -- AGGREGATE  |LOCAL|
+                              -- STREAM_SELECT  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                          }
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$57(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query3.plan
new file mode 100644
index 0000000..b9e4c18
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-2/query3.plan
@@ -0,0 +1,33 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- SUBPLAN  |PARTITIONED|
+                          {
+                            -- AGGREGATE  |LOCAL|
+                              -- STREAM_SELECT  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                          }
+                    -- STREAM_SELECT  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ASSIGN  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$63(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- ASSIGN  |PARTITIONED|
+                                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query1.plan
new file mode 100644
index 0000000..1d92106
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query1.plan
@@ -0,0 +1,31 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query2.plan
new file mode 100644
index 0000000..45549a5
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query2.plan
@@ -0,0 +1,31 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$40(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query3.plan
new file mode 100644
index 0000000..f8aad44
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-3/query3.plan
@@ -0,0 +1,32 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- ASSIGN  |LOCAL|
+                              -- UNNEST  |LOCAL|
+                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_SELECT  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$44(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query1.plan
new file mode 100644
index 0000000..84dc8c7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query1.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- SUBPLAN  |LOCAL|
+                                    {
+                                      -- AGGREGATE  |LOCAL|
+                                        -- STREAM_SELECT  |LOCAL|
+                                          -- UNNEST  |LOCAL|
+                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                    }
+                              -- ASSIGN  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$41(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query2.plan
new file mode 100644
index 0000000..a689c35
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query2.plan
@@ -0,0 +1,38 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- SUBPLAN  |LOCAL|
+                                    {
+                                      -- AGGREGATE  |LOCAL|
+                                        -- STREAM_SELECT  |LOCAL|
+                                          -- UNNEST  |LOCAL|
+                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                    }
+                              -- ASSIGN  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$49(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query3.plan
new file mode 100644
index 0000000..bfdc30a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-quantified-queries/use-case-4/query3.plan
@@ -0,0 +1,39 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- SUBPLAN  |PARTITIONED|
+                      {
+                        -- AGGREGATE  |LOCAL|
+                          -- STREAM_SELECT  |LOCAL|
+                            -- SUBPLAN  |LOCAL|
+                                    {
+                                      -- AGGREGATE  |LOCAL|
+                                        -- STREAM_SELECT  |LOCAL|
+                                          -- UNNEST  |LOCAL|
+                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                    }
+                              -- ASSIGN  |LOCAL|
+                                -- UNNEST  |LOCAL|
+                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                      }
+                -- STREAM_SELECT  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$54(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-1/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-1/query1.plan
new file mode 100644
index 0000000..85a80b7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-1/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$33(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-1/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-1/query2.plan
new file mode 100644
index 0000000..a65a2c1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-1/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$54(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-2/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-2/query1.plan
new file mode 100644
index 0000000..3f9647b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-2/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-2/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-2/query2.plan
new file mode 100644
index 0000000..9af45a6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-2/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$56(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query1.plan
new file mode 100644
index 0000000..3f9647b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query2.plan
new file mode 100644
index 0000000..bb72552
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query2.plan
@@ -0,0 +1,28 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- UNNEST  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ASSIGN  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STABLE_SORT [$$57(ASC)]  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query3.plan
new file mode 100644
index 0000000..0202127
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-3/query3.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$39(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-4/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-4/query1.plan
new file mode 100644
index 0000000..4cf8dfe
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-4/query1.plan
@@ -0,0 +1,27 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- UNNEST  |PARTITIONED|
+                        -- ASSIGN  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$45(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- ASSIGN  |PARTITIONED|
+                                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-4/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-4/query2.plan
new file mode 100644
index 0000000..4cb1efa
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/use-case-4/query2.plan
@@ -0,0 +1,30 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- UNNEST  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STABLE_SORT [$$66(ASC)]  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- ASSIGN  |PARTITIONED|
+                                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-3-level-record-path/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-3-level-record-path/query1.plan
new file mode 100644
index 0000000..0202127
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-3-level-record-path/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$39(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-3-level-record-path/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-3-level-record-path/query2.plan
new file mode 100644
index 0000000..9177af3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-3-level-record-path/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$60(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-pk/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-pk/query1.plan
new file mode 100644
index 0000000..883fa06
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-pk/query1.plan
@@ -0,0 +1,24 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- STABLE_SORT [$$34(ASC), $$35(ASC)]  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-pk/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-pk/query2.plan
new file mode 100644
index 0000000..e4d4667
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-pk/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$55(ASC), $$56(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query1.plan
new file mode 100644
index 0000000..2ad5dca
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query1.plan
@@ -0,0 +1,25 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_SELECT  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$42(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDatesTimes)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query2.plan
new file mode 100644
index 0000000..afcd3a0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-composite-sk/query2.plan
@@ -0,0 +1,28 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- UNNEST  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ASSIGN  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STABLE_SORT [$$58(ASC)]  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDatesTimes)  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-filter-fields/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-filter-fields/query1.plan
new file mode 100644
index 0000000..3a14007
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-filter-fields/query1.plan
@@ -0,0 +1,25 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_SELECT  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$36(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-filter-fields/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-filter-fields/query2.plan
new file mode 100644
index 0000000..a65a2c1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/closed/with-filter-fields/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$54(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-1/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-1/query1.plan
new file mode 100644
index 0000000..85a80b7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-1/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$33(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-1/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-1/query2.plan
new file mode 100644
index 0000000..a65a2c1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-1/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$54(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-2/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-2/query1.plan
new file mode 100644
index 0000000..3f9647b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-2/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-2/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-2/query2.plan
new file mode 100644
index 0000000..9af45a6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-2/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$56(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query1.plan
new file mode 100644
index 0000000..3f9647b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query2.plan
new file mode 100644
index 0000000..bb72552
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query2.plan
@@ -0,0 +1,28 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- UNNEST  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ASSIGN  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STABLE_SORT [$$57(ASC)]  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query3.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query3.plan
new file mode 100644
index 0000000..0202127
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-3/query3.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$39(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-4/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-4/query1.plan
new file mode 100644
index 0000000..4cf8dfe
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-4/query1.plan
@@ -0,0 +1,27 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- UNNEST  |PARTITIONED|
+                        -- ASSIGN  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STABLE_SORT [$$45(ASC)]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- ASSIGN  |PARTITIONED|
+                                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-4/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-4/query2.plan
new file mode 100644
index 0000000..4cb1efa
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/use-case-4/query2.plan
@@ -0,0 +1,30 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- UNNEST  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STABLE_SORT [$$66(ASC)]  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- ASSIGN  |PARTITIONED|
+                                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-3-level-record-path/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-3-level-record-path/query1.plan
new file mode 100644
index 0000000..0202127
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-3-level-record-path/query1.plan
@@ -0,0 +1,23 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- STABLE_SORT [$$39(ASC)]  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-3-level-record-path/query2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-3-level-record-path/query2.plan
new file mode 100644
index 0000000..9177af3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-3-level-record-path/query2.plan
@@ -0,0 +1,26 @@
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    -- STREAM_PROJECT  |UNPARTITIONED|
+      -- ASSIGN  |UNPARTITIONED|
+        -- AGGREGATE  |UNPARTITIONED|
+          -- RANDOM_MERGE_EXCHANGE  |PARTITIONED|
+            -- AGGREGATE  |PARTITIONED|
+              -- STREAM_SELECT  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- UNNEST  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STABLE_SORT [$$60(ASC)]  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDates)  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-composite-sk/query1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-composite-sk/query1.plan
new file mode 100644
index 0000000..8ae90bf
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/array-index/select-unnest-queries/open/with-composite-sk/query1.plan
@@ -0,0 +1,25 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- STREAM_PROJECT  |PARTITIONED|
+          -- STREAM_SELECT  |PARTITIONED|
+            -- STREAM_PROJECT  |PARTITIONED|
+              -- UNNEST  |PARTITIONED|
+                -- STREAM_SELECT  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- BTREE_SEARCH (TestYelp.YelpCheckin.YelpCheckin)  |PARTITIONED|
+                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                              -- PRE_SORTED_DISTINCT_BY  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STABLE_SORT [$$44(ASC)]  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- BTREE_SEARCH (TestYelp.YelpCheckin.IdxYelpCheckinDatesTimes)  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.1.ddl.sqlpp
new file mode 100644
index 0000000..3e2eeda
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.1.ddl.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+/*
+ * Description: Verify the bulk-loading operation for an array->atomic index.
+ */
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    dates: [string]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST dates);
+// CREATE INDEX IdxYelpCheckinDatesBusinessID ON YelpCheckin (UNNEST dates, business_id);
+// CREATE INDEX IdxYelpCheckinBusinessIDDates ON YelpCheckin (business_id, UNNEST dates);
+CREATE INDEX IdxYelpCheckinBusinessID ON YelpCheckin (business_id);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.2.update.sqlpp
new file mode 100644
index 0000000..f911125
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.2.update.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+USE TestYelp;
+
+LOAD DATASET YelpCheckin 
+USING localfs (("path"="asterix_nc1://data/yelp-checkin/use-case-1.json"),
+                ("format"="json"));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.3.query.sqlpp
new file mode 100644
index 0000000..ec79bc6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-1/use-case-1.3.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+USE TestYelp;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.dates D
+WHERE "2016-04-26 19:49:16" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.1.ddl.sqlpp
new file mode 100644
index 0000000..ecef50a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.1.ddl.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+/*
+ * Description: Verify the bulk-loading operation for a record->array->atomic index.
+ */
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: {
+        dates: [string],
+        times: [string]
+    }
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times.dates);
+// CREATE INDEX IdxYelpCheckinDatesBusiness ON YelpCheckin (UNNEST checkin_times.dates, (business_id));
+// CREATE INDEX IdxYelpCheckinBusinessDates ON YelpCheckin (business_id, UNNEST checkin_times.dates);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.2.update.sqlpp
new file mode 100644
index 0000000..3fd8b82
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.2.update.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+USE TestYelp;
+
+LOAD DATASET YelpCheckin 
+USING localfs (("path"="asterix_nc1://data/yelp-checkin/use-case-2.json"),
+                ("format"="json"));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.3.query.sqlpp
new file mode 100644
index 0000000..0f23cd4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-2/use-case-2.3.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+USE TestYelp;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times.dates D
+WHERE "2016-04-26" = D;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.1.ddl.sqlpp
new file mode 100644
index 0000000..abd1c6c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.1.ddl.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+/*
+ * Description: Verify the bulk-loading operation for an array->record->atomic index, as well as a composite index on both fields. 
+ */
+
+DROP DATAVERSE TestYelp IF EXISTS;
+CREATE DATAVERSE TestYelp;
+USE TestYelp;
+
+CREATE TYPE CheckinType AS {
+    checkin_id: uuid,
+    business_id: string,
+    checkin_times: [{
+        date: string,
+        time: string
+    }]
+};
+
+CREATE DATASET YelpCheckin(CheckinType) PRIMARY KEY checkin_id AUTOGENERATED;
+CREATE INDEX IdxYelpCheckinDates ON YelpCheckin (UNNEST checkin_times SELECT date);
+CREATE INDEX IdxYelpCheckinDatesTimes ON YelpCheckin (UNNEST checkin_times SELECT date, time);
+CREATE INDEX IdxYelpCheckinBusinessID ON YelpCheckin (business_id);
+// CREATE INDEX IdxYelpCheckinDatesTimesBusiness ON YelpCheckin ((UNNEST checkin_times SELECT date, time), (business_id));
+// CREATE INDEX IdxYelpCheckinBusinessDatesTimes ON YelpCheckin (business_id, UNNEST checkin_times SELECT date, time);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.2.update.sqlpp
new file mode 100644
index 0000000..371bc27
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.2.update.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+USE TestYelp;
+
+LOAD DATASET YelpCheckin 
+USING localfs (("path"="asterix_nc1://data/yelp-checkin/use-case-3.json"),
+                ("format"="json"));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.3.query.sqlpp
new file mode 100644
index 0000000..27f01aa
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-3/use-case-3.3.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 `compiler.arrayindex` "true";
+
+USE TestYelp;
+
+SELECT C.business_id
+FROM YelpCheckin C, C.checkin_times CT
+WHERE "2016-04-26" = CT.date;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-4/use-case-4.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-4/use-case-4.1.ddl.sqlpp
new file mode 100644
index 0000000..626e797
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/array-index/bulk-loading/after-index-creation/use-case-4/use-case-4.1.ddl.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
... 40430 lines suppressed ...