diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000000..e69de29bb2d diff --git a/404.html b/404.html new file mode 100644 index 00000000000..f61f5ac0075 --- /dev/null +++ b/404.html @@ -0,0 +1,21 @@ + + + + + + 404 | HPCC Platform + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/BUILD_ME.html b/BUILD_ME.html new file mode 100644 index 00000000000..a0c6e66d954 --- /dev/null +++ b/BUILD_ME.html @@ -0,0 +1,98 @@ + + + + + + Build Instructions | HPCC Platform + + + + + + + + + + + + + +
Skip to content
HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+https://hpccsystems.com

Build Instructions

Prerequisites

Ubuntu

Ubuntu 19.10/19.04/18.04

sudo apt-get install cmake bison flex build-essential binutils-dev libldap2-dev libcppunit-dev libicu-dev libxslt1-dev \
+zlib1g-dev libboost-regex-dev libarchive-dev python-dev libv8-dev default-jdk libapr1-dev libaprutil1-dev libiberty-dev \
+libhiredis-dev libtbb-dev libxalan-c-dev libnuma-dev nodejs libevent-dev libatlas-base-dev libblas-dev python3-dev \
+default-libmysqlclient-dev libsqlite3-dev r-base-dev r-cran-rcpp r-cran-rinside r-cran-inline libmemcached-dev \
+libcurl4-openssl-dev pkg-config libtool autotools-dev automake libssl-dev
+

Ubuntu 16.04

sudo apt-get install cmake bison flex build-essential binutils-dev libldap2-dev libcppunit-dev libicu-dev libxslt1-dev \
+zlib1g-dev libboost-regex-dev libssl-dev libarchive-dev python-dev libv8-dev default-jdk libapr1-dev libaprutil1-dev \
+libiberty-dev libhiredis-dev libtbb-dev libxalan-c-dev libnuma-dev libevent-dev libatlas-base-dev libblas-dev \
+libatlas-dev python3-dev libcurl4-openssl-dev libtool autotools-dev automake
+
Additional information for building versions prior to 7.0 on Ubuntu 18.04

Get openssl-1.0.2o.tar.gz from https://www.openssl.org/source/

unpack, build, and install:

./config -fPIC shared
+make
+make install
+

This installs to /usr/local/ssl by default.

Build the platform with the following additional CMake options:

-DOPENSSL_LIBRARIES=/usr/local/ssl/lib/libssl.so -DOPENSSL_SSL_LIBRARY=/usr/local/ssl/lib/libssl.so
+

CentOS

Regardless of which version of CentOS you will be building on, it is suggested that you enable the EPEL repository

bash
sudo yum install -y epel-release

CentOS 7:

sudo yum install gcc-c++ gcc make bison flex binutils-devel openldap-devel libicu-devel libxslt-devel libarchive-devel \
+boost-devel openssl-devel apr-devel apr-util-devel hiredis-devel numactl-devel mariadb-devel libevent-devel tbb-devel \
+atlas-devel python34 libmemcached-devel sqlite-devel v8-devel python-devel python34-devel java-1.8.0-openjdk-devel \
+R-core-devel R-Rcpp-devel R-inline R-RInside-devel nodejs cmake3 rpm-build libcurl-devel
+

CentOS 6.4:

sudo yum install gcc-c++ gcc make bison flex binutils-devel openldap-devel libicu-devel libxslt-devel libarchive-devel \
+boost-devel openssl-devel apr-devel apr-util-devel hiredis-devel numactl-devel libmysqlclient-dev libevent-devel \
+tbb-devel atlas-devel python34 R-core-devel R-Rcpp-devel R-inline R-RInside-devel nodejs libcurl-devel
+

Other Platforms

Fedora 19:

sudo yum install gcc-c++ gcc make fedora-packager cmake bison flex binutils-devel openldap-devel libicu-devel  \
+xerces-c-devel xalan-c-devel libarchive-devel boost-devel openssl-devel apr-devel apr-util-devel
+

Fedora 23:

sudo dnf install gcc-c++ gcc make fedora-packager cmake bison flex binutils-devel openldap-devel libicu-devel \
+xerces-c-devel xalan-c-devel libarchive-devel boost-devel openssl-devel apr-devel apr-util-devel numactl-devel \
+tbb-devel libxslt-devel nodejs
+

Mac (Snow Leopard):

sudo port install bison flex binutils openldap icu xalanc zlib boost openssl libarchive
+

You can disable some functionality in order to reduce the list of required components, if necessary. Optional components, such as the plugins for interfacing to external languages, will be disabled automatically if the required libraries and headers are not found at build time.

Optional dependencies:

To build with support for all the plugins for third party embedded code, additional dependencies may be required. If not found, the system will default to skipping those components.

Windows

From 7.4 the build system has been changed to make it easier to build on windows. It is dependent on two other projects chocolatey and vcpkg for installing the dependencies:

Install chocolatey:

https://chocolatey.org/install

Use that to install bison/flex:

choco install winflexbison3

Install vcpkg:

git clone https://github.com/Microsoft/vcpkg
+cd vcpkg
+bootstrap-vcpkg

Use vcpkg to install various packages:

vcpkg install zlib
+vcpkg install boost
+vcpkg install icu
+vcpkg install libxslt
+vcpkg install tbb
+vcpkg install cppunit
+vcpkg install libarchive
+vcpkg install apr
+vcpkg install apr-util

You may need to force vcpkg to build the correct version, e.g. for 64bit:

vcpkg install zlib zlib:x64-windows

Other required third-party packages

Nodejs

NodeJS (version 8.x.x LTS recommended) is used to package ECL Watch and related web pages.

To install nodeJs on Linux based systems, try:

   curl -sL https://deb.nodesource.com/setup_8.x | sudo bash -
+   sudo apt-get install -y nodejs

If these instructions do not work on your system, refer to the detailed instructions available here

Building with R Support:

First insure that the R language is installed on your system. For Ubuntu use sudo apt-get install r-base-dev. For centos distributions use sudo yum install -y R-core-devel.

To install the prerequisites for building R support, use the following for all distros:

wget https://cran.r-project.org/src/contrib/00Archive/Rcpp/Rcpp_0.12.1.tar.gz
+wget https://cran.r-project.org/src/contrib/00Archive/RInside/RInside_0.2.12.tar.gz
+wget http://cran.r-project.org/src/contrib/inline_0.3.14.tar.gz
+sudo R CMD INSTALL Rcpp_0.12.1.tar.gz RInside_0.2.12.tar.gz inline_0.3.14.tar.gz

Get Latest HPCC Systems Sources

Visit Git-step-by-step for full instructions.

To get started quickly, simply:

bash
git clone [-b <branch name>] --recurse-submodules https://github.com/hpcc-systems/HPCC-Platform.git

Where [ ] denotes an optional argument.

CMake

The minimum version of CMake required to build the HPCC Platform is 3.3.2 on Linux. You may need to download a recent version here at cmake.org.

Now you need to run CMake to populate your build directory with Makefiles and special configuration to build HPCC, packages, tests, etc.

A separate directory is required for the build files. In the examples below, the source directory is contained in ~/hpcc and the build directory is ~/hpcc/build. mkdir ~/hpcc/build

All cmake commands would normally need to be executed within the build directory: cd ~/hpcc/build

For release builds, do: cmake ../src

To enable a specific plugin in the build:

bash
    cmake –D<Plugin Name>=ON ../src 
+    make –j6 package

These are the current supported plugins:

  • CASSANDRAEMBED
  • REMBED
  • V8EMBED
  • MEMCACHED
  • PYEMBED
  • REDIS
  • JAVAEMBED
  • KAFKA
  • SQLITE3EMBED
  • MYSQLEMBED

If testing during development and you may want to include plugins (except R) in the package: cmake -DTEST_PLUGINS=ON ../src

To produce a debug build: cmake -DCMAKE_BUILD_TYPE:STRING=Debug ../src

To build the client tools only: cmake -DCLIENTTOOLS_ONLY=1 ../src

To enable signing of the ecl standard library, ensure you have a gpg private key loaded into your gpg keychain and do: # Add -DSIGN_MODULES_KEYID and -DSIGN_MODULES_PASSPHRASE if applicable cmake -DSIGN_MODULES=ON ../src

In some cases, users have found that when packaging for Ubuntu, the dpkg-shlibdeps portion of the packaging adds an exceptional amount of time to the build process. To turn this off (and to create a package without dynamic dependency generation) do: cmake -DUSE_SHLIBDEPS=OFF ../src

CMake will check for necessary dependencies like binutils, boost regex, cppunit, pthreads, etc. If everything is correct, it'll create the necessary Makefiles and you're ready to build HPCC.

NOTE:

We default to using libxslt in place of Xalan for xslt support. Should you prefer to use libxalan, you can specify -DUSE_LIBXALAN on the cmake command line.

Building

You may build by either using make:

# Using -j option here to specify 6 compilation threads (suitable for quad core cpu)
+make -j6
+

Or, alternatively you can call a build system agnostic variant (works with make, ninja, XCode, Visual Studio etc.):

cmake --build .
+

This will make all binaries, libraries and scripts necessary to create the package.

  • Executables will be created in ./<releasemode>/bin and ./<releasemode>/libs

Creating a package

The recommended method to install HPCC Systems on your machine (even for testing) is to use distro packages. CMake has already detected your system, so it know whether to generate TGZ files, DEB or RPM packages.

Just type:

make package
+

Alternatively you can use the build system agnostic variant:

cmake --build . --target package
+

and it will create the appropriate package for you to install. The package file will be created inside the build directory.

Installing the package

Install the package:

sudo dpkg -i hpccsystems-platform-community_6.0.0-trunk0trusty_amd64.deb
+

(note that the name of the package you have just built will depend on the branch you checked out, the distro, and other options).

Hint: missing dependencies may be fixed with:

sudo apt-get -f install
+

(see here for Ubuntu based installation).

To build client tools for MacOS:

Note: These instructions may not be up to date

  • Check out sources (for example, to directory ~/hpcc)
  • Fetch all sub-modules with:
   git submodule update --init --recursive
  • You many need to install some 3rd-party dev packages using macports or brew. (brew installs shown below)
   brew install icu4c
+   brew install boost
+   brew install libarchive
+   brew install bison
+   brew install openldap

** Also make sure that bison is ahead of the system bison on your path. bison --version (The result should be > 2.4.1 )

** OS X has LDAP installed, but when compiling against it (/System/Library/Frameworks/LDAP.framework/Headers/ldap.h) you will get a #include nested too deeply, which is why you should install openldap.

  • Create a build directory - either as a child of hpcc or elsewhere
  • cd to the build directory
  • Use clang to build the clienttools (gcc4.2 cores when compiling some of the sources):
   export CC=/usr/bin/clang
+   export CXX=/usr/bin/clang++
+   cmake ../ -DICU_LIBRARIES=/usr/local/opt/icu4c/lib/libicuuc.dylib -DICU_INCLUDE_DIR=/usr/local/opt/icu4c/include \
+   -DLIBARCHIVE_INCLUDE_DIR=/usr/local/opt/libarchive/include \
+   -DLIBARCHIVE_LIBRARIES=/usr/local/opt/libarchive/lib/libarchive.dylib \
+   -DBOOST_REGEX_LIBRARIES=/usr/local/opt/boost/lib -DBOOST_REGEX_INCLUDE_DIR=/usr/local/opt/boost/include \
+   -DCLIENTTOOLS_ONLY=true \
+   -DUSE_OPENLDAP=true -DOPENLDAP_INCLUDE_DIR=/usr/local/opt/openldap/include \
+   -DOPENLDAP_LIBRARIES=/usr/local/opt/openldap/lib/libldap_r.dylib
  • To build the makefiles just created above, run make
  • Executables will be created in ./<releasemode>/bin and ./<releasemode>/libs
  • To create a .dmg to install, run make package

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/README.html b/README.html new file mode 100644 index 00000000000..f75b4163b10 --- /dev/null +++ b/README.html @@ -0,0 +1,60 @@ + + + + + + Description / Rationale | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Description / Rationale

HPCC Systems offers an enterprise ready, open source supercomputing platform to solve big data problems. As compared to Hadoop, the platform offers analysis of big data using less code and less nodes for greater efficiencies and offers a single programming language, a single platform and a single architecture for efficient processing. HPCC Systems is a technology division of LexisNexis Risk Solutions.

Getting Started

Release + Support Policy

In general, a new version of the HPCC Platform is released every 3 months. These releases can be either Major (with breaking changes) or Minor (with new features). Maintenance and security releases (point releases) are typically made weekly, and may occasionally include technical previews.

Maintenance releases are supported for the current and previous release, while security releases are supported for the current and previous two releases:

mermaid
---
+displayMode: compact
+---
+gantt
+    title Release Schedule
+    axisFormat %Y-Q%q
+    tickInterval 3month
+    dateFormat YYYY-MM-DD
+    section v8.12.x
+        Active:          active, 2023-02-07, 5M
+        Critical:        3M
+        Security:        6M
+    section v9.0.x
+        Active:          active, 2023-04-03, 6M
+        Critical:        3M
+        Security:        6M
+    section v9.2.x
+        Active:          active, 2023-07-04, 9M
+        Critical:        3M
+        Security:        3M
+    section v9.4.x
+        Active:          active, 2023-10-04, 9M
+        Critical:        3M
+        Security:        3M
+    section v9.6.x
+        Active:          active, 2024-04-04, 6M
+        Critical:        3M
+        Security:        3M
+    section v9.8.x
+        Active:          active, 2024-07-02, 6M
+        Critical:        3M
+        Security:        3M
+    section v9.10.x
+        Active:          active, 2024-10-01, 6M
+        Critical:        3M
+        Security:        3M

Architecture

The HPCC Systems architecture incorporates the Thor and Roxie clusters as well as common middleware components, an external communications layer, client interfaces which provide both end-user services and system management tools, and auxiliary components to support monitoring and to facilitate loading and storing of filesystem data from external sources. An HPCC environment can include only Thor clusters, or both Thor and Roxie clusters. Each of these cluster types is described in more detail in the following sections below the architecture diagram.

Thor

Thor (the Data Refinery Cluster) is responsible for consuming vast amounts of data, transforming, linking and indexing that data. It functions as a distributed file system with parallel processing power spread across the nodes. A cluster can scale from a single node to thousands of nodes.

  • Single-threaded
  • Distributed parallel processing
  • Distributed file system
  • Powerful parallel processing programming language (ECL)
  • Optimized for Extraction, Transformation, Loading, Sorting, Indexing and Linking
  • Scales from 1-1000s of nodes

Roxie

Roxie (the Query Cluster) provides separate high-performance online query processing and data warehouse capabilities. Roxie (Rapid Online XML Inquiry Engine) is the data delivery engine used in HPCC to serve data quickly and can support many thousands of requests per node per second.

  • Multi-threaded
  • Distributed parallel processing
  • Distributed file system
  • Powerful parallel processing programming language (ECL)
  • Optimized for concurrent query processing
  • Scales from 1-1000s of nodes

ECL

ECL (Enterprise Control Language) is the powerful programming language that is ideally suited for the manipulation of Big Data.

  • Transparent and implicitly parallel programming language
  • Non-procedural and dataflow oriented
  • Modular, reusable, extensible syntax
  • Combines data representation and algorithm implementation
  • Easily extend using C++ libraries
  • ECL is compiled into optimized C++

ECL IDE

ECL IDE is a modern IDE used to code, debug and monitor ECL programs.

  • Access to shared source code repositories
  • Complete development, debugging and testing environment for developing ECL dataflow programs
  • Access to the ECLWatch tool is built-in, allowing developers to watch job graphs as they are executing
  • Access to current and historical job workunits

ESP

ESP (Enterprise Services Platform) provides an easy to use interface to access ECL queries using XML, HTTP, SOAP and REST.

  • Standards-based interface to access ECL functions

Developer documentation

The following links describe the structure of the system and detail some of the key components:

Regression test

sh
cd /opt/HPCCSystems/testing/regress
+./ecl-test query --target thor nlppp.ecl

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/assets/BUILD_ME.md.Cs6Xx-2H.js b/assets/BUILD_ME.md.Cs6Xx-2H.js new file mode 100644 index 00000000000..80b68823156 --- /dev/null +++ b/assets/BUILD_ME.md.Cs6Xx-2H.js @@ -0,0 +1,75 @@ +import{_ as a,c as s,a3 as i,o as n}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Build Instructions","description":"","frontmatter":{},"headers":[],"relativePath":"BUILD_ME.md","filePath":"BUILD_ME.md","lastUpdated":1731340314000}'),l={name:"BUILD_ME.md"};function t(o,e,p,r,d,c){return n(),s("div",null,e[0]||(e[0]=[i(`
HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+https://hpccsystems.com

Build Instructions

Prerequisites

Ubuntu

Ubuntu 19.10/19.04/18.04

sudo apt-get install cmake bison flex build-essential binutils-dev libldap2-dev libcppunit-dev libicu-dev libxslt1-dev \\
+zlib1g-dev libboost-regex-dev libarchive-dev python-dev libv8-dev default-jdk libapr1-dev libaprutil1-dev libiberty-dev \\
+libhiredis-dev libtbb-dev libxalan-c-dev libnuma-dev nodejs libevent-dev libatlas-base-dev libblas-dev python3-dev \\
+default-libmysqlclient-dev libsqlite3-dev r-base-dev r-cran-rcpp r-cran-rinside r-cran-inline libmemcached-dev \\
+libcurl4-openssl-dev pkg-config libtool autotools-dev automake libssl-dev
+

Ubuntu 16.04

sudo apt-get install cmake bison flex build-essential binutils-dev libldap2-dev libcppunit-dev libicu-dev libxslt1-dev \\
+zlib1g-dev libboost-regex-dev libssl-dev libarchive-dev python-dev libv8-dev default-jdk libapr1-dev libaprutil1-dev \\
+libiberty-dev libhiredis-dev libtbb-dev libxalan-c-dev libnuma-dev libevent-dev libatlas-base-dev libblas-dev \\
+libatlas-dev python3-dev libcurl4-openssl-dev libtool autotools-dev automake
+
Additional information for building versions prior to 7.0 on Ubuntu 18.04

Get openssl-1.0.2o.tar.gz from https://www.openssl.org/source/

unpack, build, and install:

./config -fPIC shared
+make
+make install
+

This installs to /usr/local/ssl by default.

Build the platform with the following additional CMake options:

-DOPENSSL_LIBRARIES=/usr/local/ssl/lib/libssl.so -DOPENSSL_SSL_LIBRARY=/usr/local/ssl/lib/libssl.so
+

CentOS

Regardless of which version of CentOS you will be building on, it is suggested that you enable the EPEL repository

bash
sudo yum install -y epel-release

CentOS 7:

sudo yum install gcc-c++ gcc make bison flex binutils-devel openldap-devel libicu-devel libxslt-devel libarchive-devel \\
+boost-devel openssl-devel apr-devel apr-util-devel hiredis-devel numactl-devel mariadb-devel libevent-devel tbb-devel \\
+atlas-devel python34 libmemcached-devel sqlite-devel v8-devel python-devel python34-devel java-1.8.0-openjdk-devel \\
+R-core-devel R-Rcpp-devel R-inline R-RInside-devel nodejs cmake3 rpm-build libcurl-devel
+

CentOS 6.4:

sudo yum install gcc-c++ gcc make bison flex binutils-devel openldap-devel libicu-devel libxslt-devel libarchive-devel \\
+boost-devel openssl-devel apr-devel apr-util-devel hiredis-devel numactl-devel libmysqlclient-dev libevent-devel \\
+tbb-devel atlas-devel python34 R-core-devel R-Rcpp-devel R-inline R-RInside-devel nodejs libcurl-devel
+

Other Platforms

Fedora 19:

sudo yum install gcc-c++ gcc make fedora-packager cmake bison flex binutils-devel openldap-devel libicu-devel  \\
+xerces-c-devel xalan-c-devel libarchive-devel boost-devel openssl-devel apr-devel apr-util-devel
+

Fedora 23:

sudo dnf install gcc-c++ gcc make fedora-packager cmake bison flex binutils-devel openldap-devel libicu-devel \\
+xerces-c-devel xalan-c-devel libarchive-devel boost-devel openssl-devel apr-devel apr-util-devel numactl-devel \\
+tbb-devel libxslt-devel nodejs
+

Mac (Snow Leopard):

sudo port install bison flex binutils openldap icu xalanc zlib boost openssl libarchive
+

You can disable some functionality in order to reduce the list of required components, if necessary. Optional components, such as the plugins for interfacing to external languages, will be disabled automatically if the required libraries and headers are not found at build time.

Optional dependencies:

To build with support for all the plugins for third party embedded code, additional dependencies may be required. If not found, the system will default to skipping those components.

Windows

From 7.4 the build system has been changed to make it easier to build on windows. It is dependent on two other projects chocolatey and vcpkg for installing the dependencies:

Install chocolatey:

https://chocolatey.org/install

Use that to install bison/flex:

choco install winflexbison3

Install vcpkg:

git clone https://github.com/Microsoft/vcpkg
+cd vcpkg
+bootstrap-vcpkg

Use vcpkg to install various packages:

vcpkg install zlib
+vcpkg install boost
+vcpkg install icu
+vcpkg install libxslt
+vcpkg install tbb
+vcpkg install cppunit
+vcpkg install libarchive
+vcpkg install apr
+vcpkg install apr-util

You may need to force vcpkg to build the correct version, e.g. for 64bit:

vcpkg install zlib zlib:x64-windows

Other required third-party packages

Nodejs

NodeJS (version 8.x.x LTS recommended) is used to package ECL Watch and related web pages.

To install nodeJs on Linux based systems, try:

   curl -sL https://deb.nodesource.com/setup_8.x | sudo bash -
+   sudo apt-get install -y nodejs

If these instructions do not work on your system, refer to the detailed instructions available here

Building with R Support:

First insure that the R language is installed on your system. For Ubuntu use sudo apt-get install r-base-dev. For centos distributions use sudo yum install -y R-core-devel.

To install the prerequisites for building R support, use the following for all distros:

wget https://cran.r-project.org/src/contrib/00Archive/Rcpp/Rcpp_0.12.1.tar.gz
+wget https://cran.r-project.org/src/contrib/00Archive/RInside/RInside_0.2.12.tar.gz
+wget http://cran.r-project.org/src/contrib/inline_0.3.14.tar.gz
+sudo R CMD INSTALL Rcpp_0.12.1.tar.gz RInside_0.2.12.tar.gz inline_0.3.14.tar.gz

Get Latest HPCC Systems Sources

Visit Git-step-by-step for full instructions.

To get started quickly, simply:

bash
git clone [-b <branch name>] --recurse-submodules https://github.com/hpcc-systems/HPCC-Platform.git

Where [ ] denotes an optional argument.

CMake

The minimum version of CMake required to build the HPCC Platform is 3.3.2 on Linux. You may need to download a recent version here at cmake.org.

Now you need to run CMake to populate your build directory with Makefiles and special configuration to build HPCC, packages, tests, etc.

A separate directory is required for the build files. In the examples below, the source directory is contained in ~/hpcc and the build directory is ~/hpcc/build. mkdir ~/hpcc/build

All cmake commands would normally need to be executed within the build directory: cd ~/hpcc/build

For release builds, do: cmake ../src

To enable a specific plugin in the build:

bash
    cmake –D<Plugin Name>=ON ../src 
+    make –j6 package

These are the current supported plugins:

If testing during development and you may want to include plugins (except R) in the package: cmake -DTEST_PLUGINS=ON ../src

To produce a debug build: cmake -DCMAKE_BUILD_TYPE:STRING=Debug ../src

To build the client tools only: cmake -DCLIENTTOOLS_ONLY=1 ../src

To enable signing of the ecl standard library, ensure you have a gpg private key loaded into your gpg keychain and do: # Add -DSIGN_MODULES_KEYID and -DSIGN_MODULES_PASSPHRASE if applicable cmake -DSIGN_MODULES=ON ../src

In some cases, users have found that when packaging for Ubuntu, the dpkg-shlibdeps portion of the packaging adds an exceptional amount of time to the build process. To turn this off (and to create a package without dynamic dependency generation) do: cmake -DUSE_SHLIBDEPS=OFF ../src

CMake will check for necessary dependencies like binutils, boost regex, cppunit, pthreads, etc. If everything is correct, it'll create the necessary Makefiles and you're ready to build HPCC.

NOTE:

We default to using libxslt in place of Xalan for xslt support. Should you prefer to use libxalan, you can specify -DUSE_LIBXALAN on the cmake command line.

Building

You may build by either using make:

# Using -j option here to specify 6 compilation threads (suitable for quad core cpu)
+make -j6
+

Or, alternatively you can call a build system agnostic variant (works with make, ninja, XCode, Visual Studio etc.):

cmake --build .
+

This will make all binaries, libraries and scripts necessary to create the package.

Creating a package

The recommended method to install HPCC Systems on your machine (even for testing) is to use distro packages. CMake has already detected your system, so it know whether to generate TGZ files, DEB or RPM packages.

Just type:

make package
+

Alternatively you can use the build system agnostic variant:

cmake --build . --target package
+

and it will create the appropriate package for you to install. The package file will be created inside the build directory.

Installing the package

Install the package:

sudo dpkg -i hpccsystems-platform-community_6.0.0-trunk0trusty_amd64.deb
+

(note that the name of the package you have just built will depend on the branch you checked out, the distro, and other options).

Hint: missing dependencies may be fixed with:

sudo apt-get -f install
+

(see here for Ubuntu based installation).

To build client tools for MacOS:

Note: These instructions may not be up to date

   git submodule update --init --recursive
   brew install icu4c
+   brew install boost
+   brew install libarchive
+   brew install bison
+   brew install openldap

** Also make sure that bison is ahead of the system bison on your path. bison --version (The result should be > 2.4.1 )

** OS X has LDAP installed, but when compiling against it (/System/Library/Frameworks/LDAP.framework/Headers/ldap.h) you will get a #include nested too deeply, which is why you should install openldap.

   export CC=/usr/bin/clang
+   export CXX=/usr/bin/clang++
+   cmake ../ -DICU_LIBRARIES=/usr/local/opt/icu4c/lib/libicuuc.dylib -DICU_INCLUDE_DIR=/usr/local/opt/icu4c/include \\
+   -DLIBARCHIVE_INCLUDE_DIR=/usr/local/opt/libarchive/include \\
+   -DLIBARCHIVE_LIBRARIES=/usr/local/opt/libarchive/lib/libarchive.dylib \\
+   -DBOOST_REGEX_LIBRARIES=/usr/local/opt/boost/lib -DBOOST_REGEX_INCLUDE_DIR=/usr/local/opt/boost/include \\
+   -DCLIENTTOOLS_ONLY=true \\
+   -DUSE_OPENLDAP=true -DOPENLDAP_INCLUDE_DIR=/usr/local/opt/openldap/include \\
+   -DOPENLDAP_LIBRARIES=/usr/local/opt/openldap/lib/libldap_r.dylib
`,110)]))}const b=a(l,[["render",t]]);export{u as __pageData,b as default}; diff --git a/assets/BUILD_ME.md.Cs6Xx-2H.lean.js b/assets/BUILD_ME.md.Cs6Xx-2H.lean.js new file mode 100644 index 00000000000..80b68823156 --- /dev/null +++ b/assets/BUILD_ME.md.Cs6Xx-2H.lean.js @@ -0,0 +1,75 @@ +import{_ as a,c as s,a3 as i,o as n}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Build Instructions","description":"","frontmatter":{},"headers":[],"relativePath":"BUILD_ME.md","filePath":"BUILD_ME.md","lastUpdated":1731340314000}'),l={name:"BUILD_ME.md"};function t(o,e,p,r,d,c){return n(),s("div",null,e[0]||(e[0]=[i(`
HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+https://hpccsystems.com

Build Instructions

Prerequisites

Ubuntu

Ubuntu 19.10/19.04/18.04

sudo apt-get install cmake bison flex build-essential binutils-dev libldap2-dev libcppunit-dev libicu-dev libxslt1-dev \\
+zlib1g-dev libboost-regex-dev libarchive-dev python-dev libv8-dev default-jdk libapr1-dev libaprutil1-dev libiberty-dev \\
+libhiredis-dev libtbb-dev libxalan-c-dev libnuma-dev nodejs libevent-dev libatlas-base-dev libblas-dev python3-dev \\
+default-libmysqlclient-dev libsqlite3-dev r-base-dev r-cran-rcpp r-cran-rinside r-cran-inline libmemcached-dev \\
+libcurl4-openssl-dev pkg-config libtool autotools-dev automake libssl-dev
+

Ubuntu 16.04

sudo apt-get install cmake bison flex build-essential binutils-dev libldap2-dev libcppunit-dev libicu-dev libxslt1-dev \\
+zlib1g-dev libboost-regex-dev libssl-dev libarchive-dev python-dev libv8-dev default-jdk libapr1-dev libaprutil1-dev \\
+libiberty-dev libhiredis-dev libtbb-dev libxalan-c-dev libnuma-dev libevent-dev libatlas-base-dev libblas-dev \\
+libatlas-dev python3-dev libcurl4-openssl-dev libtool autotools-dev automake
+
Additional information for building versions prior to 7.0 on Ubuntu 18.04

Get openssl-1.0.2o.tar.gz from https://www.openssl.org/source/

unpack, build, and install:

./config -fPIC shared
+make
+make install
+

This installs to /usr/local/ssl by default.

Build the platform with the following additional CMake options:

-DOPENSSL_LIBRARIES=/usr/local/ssl/lib/libssl.so -DOPENSSL_SSL_LIBRARY=/usr/local/ssl/lib/libssl.so
+

CentOS

Regardless of which version of CentOS you will be building on, it is suggested that you enable the EPEL repository

bash
sudo yum install -y epel-release

CentOS 7:

sudo yum install gcc-c++ gcc make bison flex binutils-devel openldap-devel libicu-devel libxslt-devel libarchive-devel \\
+boost-devel openssl-devel apr-devel apr-util-devel hiredis-devel numactl-devel mariadb-devel libevent-devel tbb-devel \\
+atlas-devel python34 libmemcached-devel sqlite-devel v8-devel python-devel python34-devel java-1.8.0-openjdk-devel \\
+R-core-devel R-Rcpp-devel R-inline R-RInside-devel nodejs cmake3 rpm-build libcurl-devel
+

CentOS 6.4:

sudo yum install gcc-c++ gcc make bison flex binutils-devel openldap-devel libicu-devel libxslt-devel libarchive-devel \\
+boost-devel openssl-devel apr-devel apr-util-devel hiredis-devel numactl-devel libmysqlclient-dev libevent-devel \\
+tbb-devel atlas-devel python34 R-core-devel R-Rcpp-devel R-inline R-RInside-devel nodejs libcurl-devel
+

Other Platforms

Fedora 19:

sudo yum install gcc-c++ gcc make fedora-packager cmake bison flex binutils-devel openldap-devel libicu-devel  \\
+xerces-c-devel xalan-c-devel libarchive-devel boost-devel openssl-devel apr-devel apr-util-devel
+

Fedora 23:

sudo dnf install gcc-c++ gcc make fedora-packager cmake bison flex binutils-devel openldap-devel libicu-devel \\
+xerces-c-devel xalan-c-devel libarchive-devel boost-devel openssl-devel apr-devel apr-util-devel numactl-devel \\
+tbb-devel libxslt-devel nodejs
+

Mac (Snow Leopard):

sudo port install bison flex binutils openldap icu xalanc zlib boost openssl libarchive
+

You can disable some functionality in order to reduce the list of required components, if necessary. Optional components, such as the plugins for interfacing to external languages, will be disabled automatically if the required libraries and headers are not found at build time.

Optional dependencies:

To build with support for all the plugins for third party embedded code, additional dependencies may be required. If not found, the system will default to skipping those components.

Windows

From 7.4 the build system has been changed to make it easier to build on windows. It is dependent on two other projects chocolatey and vcpkg for installing the dependencies:

Install chocolatey:

https://chocolatey.org/install

Use that to install bison/flex:

choco install winflexbison3

Install vcpkg:

git clone https://github.com/Microsoft/vcpkg
+cd vcpkg
+bootstrap-vcpkg

Use vcpkg to install various packages:

vcpkg install zlib
+vcpkg install boost
+vcpkg install icu
+vcpkg install libxslt
+vcpkg install tbb
+vcpkg install cppunit
+vcpkg install libarchive
+vcpkg install apr
+vcpkg install apr-util

You may need to force vcpkg to build the correct version, e.g. for 64bit:

vcpkg install zlib zlib:x64-windows

Other required third-party packages

Nodejs

NodeJS (version 8.x.x LTS recommended) is used to package ECL Watch and related web pages.

To install nodeJs on Linux based systems, try:

   curl -sL https://deb.nodesource.com/setup_8.x | sudo bash -
+   sudo apt-get install -y nodejs

If these instructions do not work on your system, refer to the detailed instructions available here

Building with R Support:

First insure that the R language is installed on your system. For Ubuntu use sudo apt-get install r-base-dev. For centos distributions use sudo yum install -y R-core-devel.

To install the prerequisites for building R support, use the following for all distros:

wget https://cran.r-project.org/src/contrib/00Archive/Rcpp/Rcpp_0.12.1.tar.gz
+wget https://cran.r-project.org/src/contrib/00Archive/RInside/RInside_0.2.12.tar.gz
+wget http://cran.r-project.org/src/contrib/inline_0.3.14.tar.gz
+sudo R CMD INSTALL Rcpp_0.12.1.tar.gz RInside_0.2.12.tar.gz inline_0.3.14.tar.gz

Get Latest HPCC Systems Sources

Visit Git-step-by-step for full instructions.

To get started quickly, simply:

bash
git clone [-b <branch name>] --recurse-submodules https://github.com/hpcc-systems/HPCC-Platform.git

Where [ ] denotes an optional argument.

CMake

The minimum version of CMake required to build the HPCC Platform is 3.3.2 on Linux. You may need to download a recent version here at cmake.org.

Now you need to run CMake to populate your build directory with Makefiles and special configuration to build HPCC, packages, tests, etc.

A separate directory is required for the build files. In the examples below, the source directory is contained in ~/hpcc and the build directory is ~/hpcc/build. mkdir ~/hpcc/build

All cmake commands would normally need to be executed within the build directory: cd ~/hpcc/build

For release builds, do: cmake ../src

To enable a specific plugin in the build:

bash
    cmake –D<Plugin Name>=ON ../src 
+    make –j6 package

These are the current supported plugins:

If testing during development and you may want to include plugins (except R) in the package: cmake -DTEST_PLUGINS=ON ../src

To produce a debug build: cmake -DCMAKE_BUILD_TYPE:STRING=Debug ../src

To build the client tools only: cmake -DCLIENTTOOLS_ONLY=1 ../src

To enable signing of the ecl standard library, ensure you have a gpg private key loaded into your gpg keychain and do: # Add -DSIGN_MODULES_KEYID and -DSIGN_MODULES_PASSPHRASE if applicable cmake -DSIGN_MODULES=ON ../src

In some cases, users have found that when packaging for Ubuntu, the dpkg-shlibdeps portion of the packaging adds an exceptional amount of time to the build process. To turn this off (and to create a package without dynamic dependency generation) do: cmake -DUSE_SHLIBDEPS=OFF ../src

CMake will check for necessary dependencies like binutils, boost regex, cppunit, pthreads, etc. If everything is correct, it'll create the necessary Makefiles and you're ready to build HPCC.

NOTE:

We default to using libxslt in place of Xalan for xslt support. Should you prefer to use libxalan, you can specify -DUSE_LIBXALAN on the cmake command line.

Building

You may build by either using make:

# Using -j option here to specify 6 compilation threads (suitable for quad core cpu)
+make -j6
+

Or, alternatively you can call a build system agnostic variant (works with make, ninja, XCode, Visual Studio etc.):

cmake --build .
+

This will make all binaries, libraries and scripts necessary to create the package.

Creating a package

The recommended method to install HPCC Systems on your machine (even for testing) is to use distro packages. CMake has already detected your system, so it know whether to generate TGZ files, DEB or RPM packages.

Just type:

make package
+

Alternatively you can use the build system agnostic variant:

cmake --build . --target package
+

and it will create the appropriate package for you to install. The package file will be created inside the build directory.

Installing the package

Install the package:

sudo dpkg -i hpccsystems-platform-community_6.0.0-trunk0trusty_amd64.deb
+

(note that the name of the package you have just built will depend on the branch you checked out, the distro, and other options).

Hint: missing dependencies may be fixed with:

sudo apt-get -f install
+

(see here for Ubuntu based installation).

To build client tools for MacOS:

Note: These instructions may not be up to date

   git submodule update --init --recursive
   brew install icu4c
+   brew install boost
+   brew install libarchive
+   brew install bison
+   brew install openldap

** Also make sure that bison is ahead of the system bison on your path. bison --version (The result should be > 2.4.1 )

** OS X has LDAP installed, but when compiling against it (/System/Library/Frameworks/LDAP.framework/Headers/ldap.h) you will get a #include nested too deeply, which is why you should install openldap.

   export CC=/usr/bin/clang
+   export CXX=/usr/bin/clang++
+   cmake ../ -DICU_LIBRARIES=/usr/local/opt/icu4c/lib/libicuuc.dylib -DICU_INCLUDE_DIR=/usr/local/opt/icu4c/include \\
+   -DLIBARCHIVE_INCLUDE_DIR=/usr/local/opt/libarchive/include \\
+   -DLIBARCHIVE_LIBRARIES=/usr/local/opt/libarchive/lib/libarchive.dylib \\
+   -DBOOST_REGEX_LIBRARIES=/usr/local/opt/boost/lib -DBOOST_REGEX_INCLUDE_DIR=/usr/local/opt/boost/include \\
+   -DCLIENTTOOLS_ONLY=true \\
+   -DUSE_OPENLDAP=true -DOPENLDAP_INCLUDE_DIR=/usr/local/opt/openldap/include \\
+   -DOPENLDAP_LIBRARIES=/usr/local/opt/openldap/lib/libldap_r.dylib
`,110)]))}const b=a(l,[["render",t]]);export{u as __pageData,b as default}; diff --git a/assets/ECLIDE-AppProperties.CzKpIibe.png b/assets/ECLIDE-AppProperties.CzKpIibe.png new file mode 100644 index 00000000000..77eb9635760 Binary files /dev/null and b/assets/ECLIDE-AppProperties.CzKpIibe.png differ diff --git a/assets/ECLIDE-WindowsProtectionError.ZJeAxFtk.png b/assets/ECLIDE-WindowsProtectionError.ZJeAxFtk.png new file mode 100644 index 00000000000..b0e08831224 Binary files /dev/null and b/assets/ECLIDE-WindowsProtectionError.ZJeAxFtk.png differ diff --git a/assets/HPCC-12345-build-in-progress.DU0-PikH.png b/assets/HPCC-12345-build-in-progress.DU0-PikH.png new file mode 100644 index 00000000000..70185a9e6ac Binary files /dev/null and b/assets/HPCC-12345-build-in-progress.DU0-PikH.png differ diff --git a/assets/README.md.aSZY6Tfx.js b/assets/README.md.aSZY6Tfx.js new file mode 100644 index 00000000000..8317365e832 --- /dev/null +++ b/assets/README.md.aSZY6Tfx.js @@ -0,0 +1,37 @@ +import{_ as s,c as a,a3 as i,o as t}from"./chunks/framework.DkhCEVKm.js";const E=JSON.parse('{"title":"Description / Rationale","description":"","frontmatter":{},"headers":[],"relativePath":"README.md","filePath":"README.md","lastUpdated":1731340314000}'),n={name:"README.md"};function l(r,e,o,p,h,c){return t(),a("div",null,e[0]||(e[0]=[i(`

Description / Rationale

HPCC Systems offers an enterprise ready, open source supercomputing platform to solve big data problems. As compared to Hadoop, the platform offers analysis of big data using less code and less nodes for greater efficiencies and offers a single programming language, a single platform and a single architecture for efficient processing. HPCC Systems is a technology division of LexisNexis Risk Solutions.

Getting Started

Release + Support Policy

In general, a new version of the HPCC Platform is released every 3 months. These releases can be either Major (with breaking changes) or Minor (with new features). Maintenance and security releases (point releases) are typically made weekly, and may occasionally include technical previews.

Maintenance releases are supported for the current and previous release, while security releases are supported for the current and previous two releases:

mermaid
---
+displayMode: compact
+---
+gantt
+    title Release Schedule
+    axisFormat %Y-Q%q
+    tickInterval 3month
+    dateFormat YYYY-MM-DD
+    section v8.12.x
+        Active:          active, 2023-02-07, 5M
+        Critical:        3M
+        Security:        6M
+    section v9.0.x
+        Active:          active, 2023-04-03, 6M
+        Critical:        3M
+        Security:        6M
+    section v9.2.x
+        Active:          active, 2023-07-04, 9M
+        Critical:        3M
+        Security:        3M
+    section v9.4.x
+        Active:          active, 2023-10-04, 9M
+        Critical:        3M
+        Security:        3M
+    section v9.6.x
+        Active:          active, 2024-04-04, 6M
+        Critical:        3M
+        Security:        3M
+    section v9.8.x
+        Active:          active, 2024-07-02, 6M
+        Critical:        3M
+        Security:        3M
+    section v9.10.x
+        Active:          active, 2024-10-01, 6M
+        Critical:        3M
+        Security:        3M

Architecture

The HPCC Systems architecture incorporates the Thor and Roxie clusters as well as common middleware components, an external communications layer, client interfaces which provide both end-user services and system management tools, and auxiliary components to support monitoring and to facilitate loading and storing of filesystem data from external sources. An HPCC environment can include only Thor clusters, or both Thor and Roxie clusters. Each of these cluster types is described in more detail in the following sections below the architecture diagram.

Thor

Thor (the Data Refinery Cluster) is responsible for consuming vast amounts of data, transforming, linking and indexing that data. It functions as a distributed file system with parallel processing power spread across the nodes. A cluster can scale from a single node to thousands of nodes.

Roxie

Roxie (the Query Cluster) provides separate high-performance online query processing and data warehouse capabilities. Roxie (Rapid Online XML Inquiry Engine) is the data delivery engine used in HPCC to serve data quickly and can support many thousands of requests per node per second.

ECL

ECL (Enterprise Control Language) is the powerful programming language that is ideally suited for the manipulation of Big Data.

ECL IDE

ECL IDE is a modern IDE used to code, debug and monitor ECL programs.

ESP

ESP (Enterprise Services Platform) provides an easy to use interface to access ECL queries using XML, HTTP, SOAP and REST.

Developer documentation

The following links describe the structure of the system and detail some of the key components:

Regression test

sh
cd /opt/HPCCSystems/testing/regress
+./ecl-test query --target thor nlppp.ecl
`,30)]))}const u=s(n,[["render",l]]);export{E as __pageData,u as default}; diff --git a/assets/README.md.aSZY6Tfx.lean.js b/assets/README.md.aSZY6Tfx.lean.js new file mode 100644 index 00000000000..8317365e832 --- /dev/null +++ b/assets/README.md.aSZY6Tfx.lean.js @@ -0,0 +1,37 @@ +import{_ as s,c as a,a3 as i,o as t}from"./chunks/framework.DkhCEVKm.js";const E=JSON.parse('{"title":"Description / Rationale","description":"","frontmatter":{},"headers":[],"relativePath":"README.md","filePath":"README.md","lastUpdated":1731340314000}'),n={name:"README.md"};function l(r,e,o,p,h,c){return t(),a("div",null,e[0]||(e[0]=[i(`

Description / Rationale

HPCC Systems offers an enterprise ready, open source supercomputing platform to solve big data problems. As compared to Hadoop, the platform offers analysis of big data using less code and less nodes for greater efficiencies and offers a single programming language, a single platform and a single architecture for efficient processing. HPCC Systems is a technology division of LexisNexis Risk Solutions.

Getting Started

Release + Support Policy

In general, a new version of the HPCC Platform is released every 3 months. These releases can be either Major (with breaking changes) or Minor (with new features). Maintenance and security releases (point releases) are typically made weekly, and may occasionally include technical previews.

Maintenance releases are supported for the current and previous release, while security releases are supported for the current and previous two releases:

mermaid
---
+displayMode: compact
+---
+gantt
+    title Release Schedule
+    axisFormat %Y-Q%q
+    tickInterval 3month
+    dateFormat YYYY-MM-DD
+    section v8.12.x
+        Active:          active, 2023-02-07, 5M
+        Critical:        3M
+        Security:        6M
+    section v9.0.x
+        Active:          active, 2023-04-03, 6M
+        Critical:        3M
+        Security:        6M
+    section v9.2.x
+        Active:          active, 2023-07-04, 9M
+        Critical:        3M
+        Security:        3M
+    section v9.4.x
+        Active:          active, 2023-10-04, 9M
+        Critical:        3M
+        Security:        3M
+    section v9.6.x
+        Active:          active, 2024-04-04, 6M
+        Critical:        3M
+        Security:        3M
+    section v9.8.x
+        Active:          active, 2024-07-02, 6M
+        Critical:        3M
+        Security:        3M
+    section v9.10.x
+        Active:          active, 2024-10-01, 6M
+        Critical:        3M
+        Security:        3M

Architecture

The HPCC Systems architecture incorporates the Thor and Roxie clusters as well as common middleware components, an external communications layer, client interfaces which provide both end-user services and system management tools, and auxiliary components to support monitoring and to facilitate loading and storing of filesystem data from external sources. An HPCC environment can include only Thor clusters, or both Thor and Roxie clusters. Each of these cluster types is described in more detail in the following sections below the architecture diagram.

Thor

Thor (the Data Refinery Cluster) is responsible for consuming vast amounts of data, transforming, linking and indexing that data. It functions as a distributed file system with parallel processing power spread across the nodes. A cluster can scale from a single node to thousands of nodes.

Roxie

Roxie (the Query Cluster) provides separate high-performance online query processing and data warehouse capabilities. Roxie (Rapid Online XML Inquiry Engine) is the data delivery engine used in HPCC to serve data quickly and can support many thousands of requests per node per second.

ECL

ECL (Enterprise Control Language) is the powerful programming language that is ideally suited for the manipulation of Big Data.

ECL IDE

ECL IDE is a modern IDE used to code, debug and monitor ECL programs.

ESP

ESP (Enterprise Services Platform) provides an easy to use interface to access ECL queries using XML, HTTP, SOAP and REST.

Developer documentation

The following links describe the structure of the system and detail some of the key components:

Regression test

sh
cd /opt/HPCCSystems/testing/regress
+./ecl-test query --target thor nlppp.ecl
`,30)]))}const u=s(n,[["render",l]]);export{E as __pageData,u as default}; diff --git a/assets/actions-secrets-and-variables.BfAcH5xn.png b/assets/actions-secrets-and-variables.BfAcH5xn.png new file mode 100644 index 00000000000..1c267910fc5 Binary files /dev/null and b/assets/actions-secrets-and-variables.BfAcH5xn.png differ diff --git a/assets/app.OLGF0mjw.js b/assets/app.OLGF0mjw.js new file mode 100644 index 00000000000..1fbb6cd2317 --- /dev/null +++ b/assets/app.OLGF0mjw.js @@ -0,0 +1 @@ +import{R as i}from"./chunks/theme.B_cCIxVx.js";import{R as o,a4 as u,a5 as c,a6 as l,a7 as f,a8 as d,a9 as m,aa as h,ab as g,ac as A,ad as v,d as P,u as R,v as w,s as y,ae as C,af as b,ag as E,a2 as S}from"./chunks/framework.DkhCEVKm.js";function p(e){if(e.extends){const a=p(e.extends);return{...a,...e,async enhanceApp(t){a.enhanceApp&&await a.enhanceApp(t),e.enhanceApp&&await e.enhanceApp(t)}}}return e}const s=p(i),T=P({name:"VitePressApp",setup(){const{site:e,lang:a,dir:t}=R();return w(()=>{y(()=>{document.documentElement.lang=a.value,document.documentElement.dir=t.value})}),e.value.router.prefetchLinks&&C(),b(),E(),s.setup&&s.setup(),()=>S(s.Layout)}});async function D(){globalThis.__VITEPRESS__=!0;const e=j(),a=_();a.provide(c,e);const t=l(e.route);return a.provide(f,t),a.component("Content",d),a.component("ClientOnly",m),Object.defineProperties(a.config.globalProperties,{$frontmatter:{get(){return t.frontmatter.value}},$params:{get(){return t.page.value.params}}}),s.enhanceApp&&await s.enhanceApp({app:a,router:e,siteData:h}),{app:a,router:e,data:t}}function _(){return g(T)}function j(){let e=o,a;return A(t=>{let n=v(t),r=null;return n&&(e&&(a=n),(e||a===n)&&(n=n.replace(/\.js$/,".lean.js")),r=import(n)),o&&(e=!1),r},s.NotFound)}o&&D().then(({app:e,router:a,data:t})=>{a.go().then(()=>{u(a.route,t.site),e.mount("#app")})});export{D as createApp}; diff --git a/assets/chunks/@localSearchIndexroot.h6lM-Azh.js b/assets/chunks/@localSearchIndexroot.h6lM-Azh.js new file mode 100644 index 00000000000..f409b4c756c --- /dev/null +++ b/assets/chunks/@localSearchIndexroot.h6lM-Azh.js @@ -0,0 +1 @@ +const e='{"documentCount":500,"nextId":500,"documentIds":{"0":"/HPCC-Platform/BUILD_ME.html#build-instructions","1":"/HPCC-Platform/BUILD_ME.html#prerequisites","2":"/HPCC-Platform/BUILD_ME.html#ubuntu","3":"/HPCC-Platform/BUILD_ME.html#ubuntu-19-10-19-04-18-04","4":"/HPCC-Platform/BUILD_ME.html#ubuntu-16-04","5":"/HPCC-Platform/BUILD_ME.html#additional-information-for-building-versions-prior-to-7-0-on-ubuntu-18-04","6":"/HPCC-Platform/BUILD_ME.html#centos","7":"/HPCC-Platform/BUILD_ME.html#centos-7","8":"/HPCC-Platform/BUILD_ME.html#centos-6-4","9":"/HPCC-Platform/BUILD_ME.html#other-platforms","10":"/HPCC-Platform/BUILD_ME.html#fedora-19","11":"/HPCC-Platform/BUILD_ME.html#fedora-23","12":"/HPCC-Platform/BUILD_ME.html#mac-snow-leopard","13":"/HPCC-Platform/BUILD_ME.html#windows","14":"/HPCC-Platform/BUILD_ME.html#other-required-third-party-packages","15":"/HPCC-Platform/BUILD_ME.html#nodejs","16":"/HPCC-Platform/BUILD_ME.html#building-with-r-support","17":"/HPCC-Platform/BUILD_ME.html#get-latest-hpcc-systems-sources","18":"/HPCC-Platform/BUILD_ME.html#cmake","19":"/HPCC-Platform/BUILD_ME.html#note","20":"/HPCC-Platform/BUILD_ME.html#building","21":"/HPCC-Platform/BUILD_ME.html#creating-a-package","22":"/HPCC-Platform/BUILD_ME.html#installing-the-package","23":"/HPCC-Platform/BUILD_ME.html#to-build-client-tools-for-macos","24":"/HPCC-Platform/README.html#description-rationale","25":"/HPCC-Platform/README.html#getting-started","26":"/HPCC-Platform/README.html#release-support-policy","27":"/HPCC-Platform/README.html#architecture","28":"/HPCC-Platform/README.html#thor","29":"/HPCC-Platform/README.html#roxie","30":"/HPCC-Platform/README.html#ecl","31":"/HPCC-Platform/README.html#ecl-ide","32":"/HPCC-Platform/README.html#esp","33":"/HPCC-Platform/README.html#developer-documentation","34":"/HPCC-Platform/README.html#regression-test","35":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#cmake-files-structure-and-usage","36":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#directory-structure-of-cmake-files","37":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#common-macros","38":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#documentation-macros","39":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#initfiles-macro","40":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#some-standard-techniques-used-in-cmake-project-files","41":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#common-looping","42":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#common-installs-over-just-install","43":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#common-settings-for-generated-source-files","44":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#using-custom-commands-between-multiple-cmake-files","45":"/HPCC-Platform/cmake_modules/DOCUMENTATION.html#findxxxxx-cmake-format","46":"/HPCC-Platform/devdoc/CodeGenerator.html#introduction","47":"/HPCC-Platform/devdoc/CodeGenerator.html#purpose","48":"/HPCC-Platform/devdoc/CodeGenerator.html#aims","49":"/HPCC-Platform/devdoc/CodeGenerator.html#key-ideas","50":"/HPCC-Platform/devdoc/CodeGenerator.html#from-declarative-to-imperative","51":"/HPCC-Platform/devdoc/CodeGenerator.html#flow-of-processing","52":"/HPCC-Platform/devdoc/CodeGenerator.html#working-on-the-code-generator","53":"/HPCC-Platform/devdoc/CodeGenerator.html#the-regression-suite","54":"/HPCC-Platform/devdoc/CodeGenerator.html#running-directly-from-the-build-directory","55":"/HPCC-Platform/devdoc/CodeGenerator.html#eclcc-eclbundle-path-eclbundlespath","56":"/HPCC-Platform/devdoc/CodeGenerator.html#hints-and-tips","57":"/HPCC-Platform/devdoc/CodeGenerator.html#expressions","58":"/HPCC-Platform/devdoc/CodeGenerator.html#expression-graph-representation","59":"/HPCC-Platform/devdoc/CodeGenerator.html#ihqlexpression","60":"/HPCC-Platform/devdoc/CodeGenerator.html#ihqlsimplescope","61":"/HPCC-Platform/devdoc/CodeGenerator.html#ihqlscope","62":"/HPCC-Platform/devdoc/CodeGenerator.html#ihqldataset","63":"/HPCC-Platform/devdoc/CodeGenerator.html#properties-and-attributes","64":"/HPCC-Platform/devdoc/CodeGenerator.html#field-references","65":"/HPCC-Platform/devdoc/CodeGenerator.html#attribute-new","66":"/HPCC-Platform/devdoc/CodeGenerator.html#transforming-selects","67":"/HPCC-Platform/devdoc/CodeGenerator.html#annotations","68":"/HPCC-Platform/devdoc/CodeGenerator.html#associated-side-effects","69":"/HPCC-Platform/devdoc/CodeGenerator.html#derived-properties","70":"/HPCC-Platform/devdoc/CodeGenerator.html#transformations","71":"/HPCC-Platform/devdoc/CodeGenerator.html#key-stages","72":"/HPCC-Platform/devdoc/CodeGenerator.html#parsing","73":"/HPCC-Platform/devdoc/CodeGenerator.html#normalizing","74":"/HPCC-Platform/devdoc/CodeGenerator.html#scope-checking","75":"/HPCC-Platform/devdoc/CodeGenerator.html#constant-folding-foldhqlexpression","76":"/HPCC-Platform/devdoc/CodeGenerator.html#expression-optimizer-optimizehqlexpression","77":"/HPCC-Platform/devdoc/CodeGenerator.html#implicit-project-insertimplicitprojects","78":"/HPCC-Platform/devdoc/CodeGenerator.html#workunits","79":"/HPCC-Platform/devdoc/CodeGenerator.html#is-this-the-correct-term-should-it-be-a-query-this-should-really-be-independent-of-this-document","80":"/HPCC-Platform/devdoc/CodeGenerator.html#workflow","81":"/HPCC-Platform/devdoc/CodeGenerator.html#graph","82":"/HPCC-Platform/devdoc/CodeGenerator.html#inputs-and-results","83":"/HPCC-Platform/devdoc/CodeGenerator.html#generated-code","84":"/HPCC-Platform/devdoc/CodeGenerator.html#implementation-details","85":"/HPCC-Platform/devdoc/CodeGenerator.html#parser","86":"/HPCC-Platform/devdoc/CodeGenerator.html#generated-code-1","87":"/HPCC-Platform/devdoc/CodeGenerator.html#c-output-structures","88":"/HPCC-Platform/devdoc/CodeGenerator.html#activity-helper","89":"/HPCC-Platform/devdoc/CodeGenerator.html#meta-helper","90":"/HPCC-Platform/devdoc/CodeGenerator.html#building-expressions","91":"/HPCC-Platform/devdoc/CodeGenerator.html#scalar-expressions","92":"/HPCC-Platform/devdoc/CodeGenerator.html#datasets","93":"/HPCC-Platform/devdoc/CodeGenerator.html#dataset-cursors","94":"/HPCC-Platform/devdoc/CodeGenerator.html#field-access-classes","95":"/HPCC-Platform/devdoc/CodeGenerator.html#key-filepos-weirdness","96":"/HPCC-Platform/devdoc/CodeGenerator.html#source-code","97":"/HPCC-Platform/devdoc/CodeGenerator.html#ecl-eclcc-the-executable-which-ties-everything-together","98":"/HPCC-Platform/devdoc/CodeGenerator.html#challenges","99":"/HPCC-Platform/devdoc/CodeGenerator.html#from-declarative-to-imperative-1","100":"/HPCC-Platform/devdoc/CodeGenerator.html#the-parser","101":"/HPCC-Platform/devdoc/CodeReviews.html#code-review-guidelines","102":"/HPCC-Platform/devdoc/CodeReviews.html#review-goals","103":"/HPCC-Platform/devdoc/CodeReviews.html#general-comments","104":"/HPCC-Platform/devdoc/CodeReviews.html#strictness","105":"/HPCC-Platform/devdoc/CodeReviews.html#checklist","106":"/HPCC-Platform/devdoc/CodeReviews.html#comment-tags","107":"/HPCC-Platform/devdoc/CodeSubmissions.html#code-submission-guidelines","108":"/HPCC-Platform/devdoc/CodeSubmissions.html#pull-requests","109":"/HPCC-Platform/devdoc/CodeSubmissions.html#reviewers","110":"/HPCC-Platform/devdoc/CodeSubmissions.html#target-branch","111":"/HPCC-Platform/devdoc/DevDocs.html#working-with-developer-documentation","112":"/HPCC-Platform/devdoc/DevDocs.html#documentation-location","113":"/HPCC-Platform/devdoc/DevDocs.html#documentation-format","114":"/HPCC-Platform/devdoc/DevDocs.html#rendering-documentation-locally-with-vitepress","115":"/HPCC-Platform/devdoc/DevDocs.html#adding-a-new-document","116":"/HPCC-Platform/devdoc/DevDocs.html#adding-a-new-document-to-the-sidebar","117":"/HPCC-Platform/devdoc/DevDocs.html#editing-the-main-landing-page","118":"/HPCC-Platform/devdoc/Development.html#development-guide","119":"/HPCC-Platform/devdoc/Development.html#hpcc-source","120":"/HPCC-Platform/devdoc/Development.html#getting-the-sources","121":"/HPCC-Platform/devdoc/Development.html#building-the-system-from-sources","122":"/HPCC-Platform/devdoc/Development.html#requirements","123":"/HPCC-Platform/devdoc/Development.html#building-the-system","124":"/HPCC-Platform/devdoc/Development.html#packaging","125":"/HPCC-Platform/devdoc/Development.html#testing-the-system","126":"/HPCC-Platform/devdoc/Development.html#unit-tests","127":"/HPCC-Platform/devdoc/Development.html#regression-tests","128":"/HPCC-Platform/devdoc/Development.html#compiler-tests","129":"/HPCC-Platform/devdoc/Development.html#debugging-the-system","130":"/HPCC-Platform/devdoc/GitAuthenticate.html#hpcc-git-support","131":"/HPCC-Platform/devdoc/GitAuthenticate.html#credentials-for-local-development","132":"/HPCC-Platform/devdoc/GitAuthenticate.html#configuring-eclccserver","133":"/HPCC-Platform/devdoc/GitAuthenticate.html#kubernetes","134":"/HPCC-Platform/devdoc/GitAuthenticate.html#bare-metal","135":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#ldap-security-manager-init","136":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#ldap-instances","137":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#initialization-steps","138":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#load-configuration","139":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#ad-hosts","140":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#ad-credentials","141":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#retrieve-server-information-from-the-ad","142":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#connections","143":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#connection-pool","144":"/HPCC-Platform/devdoc/LDAPSecurityManager.html#handling-ad-hosts","145":"/HPCC-Platform/devdoc/MemoryManager.html#introduction","146":"/HPCC-Platform/devdoc/MemoryManager.html#main-structure","147":"/HPCC-Platform/devdoc/MemoryManager.html#the-page-bitmap","148":"/HPCC-Platform/devdoc/MemoryManager.html#irowmanager","149":"/HPCC-Platform/devdoc/MemoryManager.html#heaps","150":"/HPCC-Platform/devdoc/MemoryManager.html#huge-heap","151":"/HPCC-Platform/devdoc/MemoryManager.html#specialised-heaps","152":"/HPCC-Platform/devdoc/MemoryManager.html#packed","153":"/HPCC-Platform/devdoc/MemoryManager.html#unique","154":"/HPCC-Platform/devdoc/MemoryManager.html#blocked","155":"/HPCC-Platform/devdoc/MemoryManager.html#scanning","156":"/HPCC-Platform/devdoc/MemoryManager.html#delay-release","157":"/HPCC-Platform/devdoc/MemoryManager.html#dynamic-spilling","158":"/HPCC-Platform/devdoc/MemoryManager.html#complications","159":"/HPCC-Platform/devdoc/MemoryManager.html#callback-rules","160":"/HPCC-Platform/devdoc/MemoryManager.html#resizing-large-memory-blocks","161":"/HPCC-Platform/devdoc/MemoryManager.html#compacting-heaps","162":"/HPCC-Platform/devdoc/MemoryManager.html#shared-memory","163":"/HPCC-Platform/devdoc/MemoryManager.html#huge-pages","164":"/HPCC-Platform/devdoc/MemoryManager.html#global-memory-and-channels","165":"/HPCC-Platform/devdoc/Metrics.html#metrics-framework-design","166":"/HPCC-Platform/devdoc/Metrics.html#introduction","167":"/HPCC-Platform/devdoc/Metrics.html#definitions","168":"/HPCC-Platform/devdoc/Metrics.html#use-scenarios","169":"/HPCC-Platform/devdoc/Metrics.html#roxie","170":"/HPCC-Platform/devdoc/Metrics.html#esp","171":"/HPCC-Platform/devdoc/Metrics.html#dali-use-cases","172":"/HPCC-Platform/devdoc/Metrics.html#framework-design","173":"/HPCC-Platform/devdoc/Metrics.html#framework-implementation","174":"/HPCC-Platform/devdoc/Metrics.html#metrics","175":"/HPCC-Platform/devdoc/Metrics.html#sinks","176":"/HPCC-Platform/devdoc/Metrics.html#metrics-reporter","177":"/HPCC-Platform/devdoc/Metrics.html#metrics-implementations","178":"/HPCC-Platform/devdoc/Metrics.html#counter-metric","179":"/HPCC-Platform/devdoc/Metrics.html#gauge-metric","180":"/HPCC-Platform/devdoc/Metrics.html#custom-metric","181":"/HPCC-Platform/devdoc/Metrics.html#histogram-metric","182":"/HPCC-Platform/devdoc/Metrics.html#scaled-histogram-metric","183":"/HPCC-Platform/devdoc/Metrics.html#configuration","184":"/HPCC-Platform/devdoc/Metrics.html#metric-naming","185":"/HPCC-Platform/devdoc/Metrics.html#base-name","186":"/HPCC-Platform/devdoc/Metrics.html#meta-data","187":"/HPCC-Platform/devdoc/Metrics.html#component-instrumentation","188":"/HPCC-Platform/devdoc/Metrics.html#adding-metric-meta-data","189":"/HPCC-Platform/devdoc/Metrics.html#metric-units","190":"/HPCC-Platform/devdoc/NewFileProcessing.html#storage-planes","191":"/HPCC-Platform/devdoc/NewFileProcessing.html#files","192":"/HPCC-Platform/devdoc/NewFileProcessing.html#functions","193":"/HPCC-Platform/devdoc/NewFileProcessing.html#examples","194":"/HPCC-Platform/devdoc/NewFileProcessing.html#milestones","195":"/HPCC-Platform/devdoc/NewFileProcessing.html#file-reading-refactoring","196":"/HPCC-Platform/devdoc/NewFileProcessing.html#dfu-server","197":"/HPCC-Platform/devdoc/README.html#developer-documentation","198":"/HPCC-Platform/devdoc/README.html#general-documentation","199":"/HPCC-Platform/devdoc/README.html#implementation-details-for-different-parts-of-the-system","200":"/HPCC-Platform/devdoc/README.html#other-documentation","201":"/HPCC-Platform/devdoc/SecurityConfig.html#security-configuration","202":"/HPCC-Platform/devdoc/SecurityConfig.html#supported-configurations","203":"/HPCC-Platform/devdoc/SecurityConfig.html#security-managers","204":"/HPCC-Platform/devdoc/SecurityConfig.html#ldap","205":"/HPCC-Platform/devdoc/SecurityConfig.html#plugin-security-managers","206":"/HPCC-Platform/devdoc/SecurityConfig.html#httpasswd-security-manager","207":"/HPCC-Platform/devdoc/SecurityConfig.html#single-user-security-manager","208":"/HPCC-Platform/devdoc/SecurityConfig.html#jwt-security-manager","209":"/HPCC-Platform/devdoc/SecurityUserAuthentication.html#user-authentication","210":"/HPCC-Platform/devdoc/SecurityUserAuthentication.html#security-manager-user-authentication","211":"/HPCC-Platform/devdoc/SecurityUserAuthentication.html#ldap","212":"/HPCC-Platform/devdoc/SecurityUserAuthentication.html#htpasswd","213":"/HPCC-Platform/devdoc/SecurityUserAuthentication.html#single-user","214":"/HPCC-Platform/devdoc/SecurityUserAuthentication.html#user-authentication-during-authorization","215":"/HPCC-Platform/devdoc/StyleGuide.html#coding-conventions","216":"/HPCC-Platform/devdoc/StyleGuide.html#why-coding-conventions","217":"/HPCC-Platform/devdoc/StyleGuide.html#c-coding-conventions","218":"/HPCC-Platform/devdoc/StyleGuide.html#source-files","219":"/HPCC-Platform/devdoc/StyleGuide.html#java-style","220":"/HPCC-Platform/devdoc/StyleGuide.html#identifiers","221":"/HPCC-Platform/devdoc/StyleGuide.html#pointers","222":"/HPCC-Platform/devdoc/StyleGuide.html#indentation","223":"/HPCC-Platform/devdoc/StyleGuide.html#comments","224":"/HPCC-Platform/devdoc/StyleGuide.html#classes","225":"/HPCC-Platform/devdoc/StyleGuide.html#namespaces","226":"/HPCC-Platform/devdoc/StyleGuide.html#other","227":"/HPCC-Platform/devdoc/StyleGuide.html#c-11","228":"/HPCC-Platform/devdoc/StyleGuide.html#other-coding-conventions","229":"/HPCC-Platform/devdoc/StyleGuide.html#ecl-code","230":"/HPCC-Platform/devdoc/StyleGuide.html#javascript-xml-xsl-etc","231":"/HPCC-Platform/devdoc/StyleGuide.html#design-patterns","232":"/HPCC-Platform/devdoc/StyleGuide.html#why-design-patterns","233":"/HPCC-Platform/devdoc/StyleGuide.html#interfaces","234":"/HPCC-Platform/devdoc/StyleGuide.html#reference-counted-objects","235":"/HPCC-Platform/devdoc/StyleGuide.html#stl","236":"/HPCC-Platform/devdoc/StyleGuide.html#structure-of-the-hpcc-source-tree","237":"/HPCC-Platform/devdoc/UserBuildAssets.html#build-assets-for-individual-developer","238":"/HPCC-Platform/devdoc/UserBuildAssets.html#build-assets","239":"/HPCC-Platform/devdoc/UserBuildAssets.html#dependent-variables","240":"/HPCC-Platform/devdoc/UserBuildAssets.html#generating-the-windows-signing-certificate","241":"/HPCC-Platform/devdoc/UserBuildAssets.html#generating-a-signing-key-for-linux-builds","242":"/HPCC-Platform/devdoc/UserBuildAssets.html#starting-a-build","243":"/HPCC-Platform/devdoc/UserBuildAssets.html#asset-output","244":"/HPCC-Platform/devdoc/VersionSupport.html#current-versions","245":"/HPCC-Platform/devdoc/VersionSupport.html#supported-versions","246":"/HPCC-Platform/devdoc/VersionSupport.html#patches-and-images","247":"/HPCC-Platform/devdoc/VersionSupport.html#package-versions","248":"/HPCC-Platform/devdoc/Workunits.html#understanding-workunits","249":"/HPCC-Platform/devdoc/Workunits.html#introduction","250":"/HPCC-Platform/devdoc/Workunits.html#contents-of-a-workunit","251":"/HPCC-Platform/devdoc/Workunits.html#how-is-the-workunit-used","252":"/HPCC-Platform/devdoc/Workunits.html#example","253":"/HPCC-Platform/devdoc/Workunits.html#workunit-main-elements","254":"/HPCC-Platform/devdoc/Workunits.html#workflow","255":"/HPCC-Platform/devdoc/Workunits.html#myprocess","256":"/HPCC-Platform/devdoc/Workunits.html#graph","257":"/HPCC-Platform/devdoc/Workunits.html#generated-activity-helpers","258":"/HPCC-Platform/devdoc/Workunits.html#other","259":"/HPCC-Platform/devdoc/Workunits.html#options","260":"/HPCC-Platform/devdoc/Workunits.html#input-parameters","261":"/HPCC-Platform/devdoc/Workunits.html#results","262":"/HPCC-Platform/devdoc/Workunits.html#timings-and-statistics","263":"/HPCC-Platform/devdoc/Workunits.html#manifests","264":"/HPCC-Platform/devdoc/Workunits.html#stages-of-execution","265":"/HPCC-Platform/devdoc/Workunits.html#queues","266":"/HPCC-Platform/devdoc/Workunits.html#workflow-1","267":"/HPCC-Platform/devdoc/Workunits.html#specialised-workflow-items","268":"/HPCC-Platform/devdoc/Workunits.html#graph-execution","269":"/HPCC-Platform/devdoc/Workunits.html#details-of-the-graph-structure","270":"/HPCC-Platform/devdoc/Workunits.html#executing-the-graph","271":"/HPCC-Platform/devdoc/Workunits.html#appendix","272":"/HPCC-Platform/devdoc/Workunits.html#key-types-and-interfaces-from-eclhelper-hpp","273":"/HPCC-Platform/devdoc/Workunits.html#glossary","274":"/HPCC-Platform/devdoc/Workunits.html#full-text-of-the-workunit-xml","275":"/HPCC-Platform/devdoc/Workunits.html#full-contents-of-the-generated-c-as-a-single-file","276":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#contributing-documentation-to-the-hpcc-systems-platform-project","277":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#documenting-a-new-software-feature-required-and-optional-components","278":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#required-components","279":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#optional-components","280":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#general-tips","281":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#who-should-write-it","282":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#changing-the-default-value-of-a-configuration-setting","283":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#adding-or-modifying-a-language-keyword-standard-library-function-or-command-line-tool-action","284":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#adding-a-new-feature-that-requires-an-overview","285":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#a-feature-function-that-is-only-used-internally-to-the-system","286":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#extending-the-tests-in-the-regression-suite","287":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#placement","288":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#other-folders","289":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#pull-requests","290":"/HPCC-Platform/devdoc/docs/ContributeDocs.html#documentation-jira-issues","291":"/HPCC-Platform/devdoc/docs/DocTemplate.html#feature-documentation-template","292":"/HPCC-Platform/devdoc/docs/DocTemplate.html#overview","293":"/HPCC-Platform/devdoc/docs/DocTemplate.html#setup-configuration","294":"/HPCC-Platform/devdoc/docs/DocTemplate.html#user-guide","295":"/HPCC-Platform/devdoc/docs/DocTemplate.html#api-cli-parameter-reference","296":"/HPCC-Platform/devdoc/docs/DocTemplate.html#tutorial","297":"/HPCC-Platform/devdoc/docs/DocTemplate.html#troubleshooting","298":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#hpcc-writing-style-guide","299":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#general","300":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#terms","301":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#hpcc-systems®","302":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#other-terms","303":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#common-documentation-terms","304":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#usage-instructions","305":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#word-choices","306":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#write-up-vs-write-up","307":"/HPCC-Platform/devdoc/docs/HPCCStyleGuide.html#assure-vs-ensure-vs-insure","308":"/HPCC-Platform/devdoc/newActivity.html#quantile-1-what-is-it","309":"/HPCC-Platform/devdoc/newActivity.html#quantile-2-test-cases","310":"/HPCC-Platform/devdoc/newActivity.html#quantile-3-the-parser","311":"/HPCC-Platform/devdoc/newActivity.html#quantile-4-the-engine-interface","312":"/HPCC-Platform/devdoc/newActivity.html#quantile-5-the-code-generator","313":"/HPCC-Platform/devdoc/newActivity.html#quantile-6-roxie","314":"/HPCC-Platform/devdoc/newActivity.html#quantile-7-possible-roxie-improvements","315":"/HPCC-Platform/devdoc/newActivity.html#quantile-8-thor","316":"/HPCC-Platform/devdoc/roxie.html#everything-you-ever-wanted-to-know-about-roxie","317":"/HPCC-Platform/devdoc/roxie.html#why-did-i-create-it","318":"/HPCC-Platform/devdoc/roxie.html#how-do-activities-link-together","319":"/HPCC-Platform/devdoc/roxie.html#where-are-the-dragons","320":"/HPCC-Platform/devdoc/roxie.html#how-does-i-beat-you-to-it-work","321":"/HPCC-Platform/devdoc/roxie.html#all-about-index-compression","322":"/HPCC-Platform/devdoc/roxie.html#what-is-the-topology-server-for","323":"/HPCC-Platform/devdoc/roxie.html#lazy-file-io","324":"/HPCC-Platform/devdoc/roxie.html#new-ibyti-mode","325":"/HPCC-Platform/devdoc/roxie.html#testing-roxie-code","326":"/HPCC-Platform/devdoc/roxie.html#cache-prewarm","327":"/HPCC-Platform/devdoc/roxie.html#blacklisting-sockets","328":"/HPCC-Platform/devdoc/roxie.html#perftrace-options","329":"/HPCC-Platform/devdoc/roxie.html#some-notes-on-localagent-mode","330":"/HPCC-Platform/devdoc/roxie.html#some-notes-on-udp-packet-sending-mechanism","331":"/HPCC-Platform/devdoc/userdoc/README.html#user-documentation","332":"/HPCC-Platform/devdoc/userdoc/README.html#directory-structure-under-devdoc","333":"/HPCC-Platform/devdoc/userdoc/README.html#general-documentation","334":"/HPCC-Platform/devdoc/userdoc/README.html#hpcc-website-documentation","335":"/HPCC-Platform/devdoc/userdoc/azure/TipsAndTricks.html#azure-portal-faqs","336":"/HPCC-Platform/devdoc/userdoc/copilot/CopilotPromptTips.html#copilot-prompt-tips","337":"/HPCC-Platform/devdoc/userdoc/copilot/CopilotPromptTips.html#generic-prompts","338":"/HPCC-Platform/devdoc/userdoc/copilot/CopilotPromptTips.html#specific-prompts","339":"/HPCC-Platform/devdoc/userdoc/copilot/CopilotPromptTips.html#how-to-avoid-ai-hallucinations-with-good-prompts","340":"/HPCC-Platform/devdoc/userdoc/roxie/FAQ.html#roxie-faqs","341":"/HPCC-Platform/devdoc/userdoc/troubleshoot/ClientsToolIssues.html#how-to-resolve-issues-installing-the-ecl-ide-client-tools","342":"/HPCC-Platform/devdoc/userdoc/troubleshoot/ClientsToolIssues.html#problem","343":"/HPCC-Platform/ecl/ecl-bundle/DOCUMENTATION.html#ecl-bundle-source-documentation","344":"/HPCC-Platform/ecl/ecl-bundle/DOCUMENTATION.html#introduction","345":"/HPCC-Platform/ecl/ecl-bundle/DOCUMENTATION.html#purpose","346":"/HPCC-Platform/ecl/ecl-bundle/DOCUMENTATION.html#design","347":"/HPCC-Platform/ecl/ecl-bundle/DOCUMENTATION.html#directory-structure","348":"/HPCC-Platform/ecl/ecl-bundle/DOCUMENTATION.html#key-classes","349":"/HPCC-Platform/ecllibrary/StyleGuide.html#ecl-standard-library-style-guide","350":"/HPCC-Platform/system/httplib/README.html#cpp-httplib","351":"/HPCC-Platform/system/httplib/README.html#server-example","352":"/HPCC-Platform/system/httplib/README.html#bind-a-socket-to-multiple-interfaces-and-any-available-port","353":"/HPCC-Platform/system/httplib/README.html#static-file-server","354":"/HPCC-Platform/system/httplib/README.html#logging","355":"/HPCC-Platform/system/httplib/README.html#error-handler","356":"/HPCC-Platform/system/httplib/README.html#multipart-form-data-post-data","357":"/HPCC-Platform/system/httplib/README.html#receive-content-with-content-receiver","358":"/HPCC-Platform/system/httplib/README.html#send-content-with-content-provider","359":"/HPCC-Platform/system/httplib/README.html#chunked-transfer-encoding","360":"/HPCC-Platform/system/httplib/README.html#expect-100-continue-handler","361":"/HPCC-Platform/system/httplib/README.html#keep-alive-connection","362":"/HPCC-Platform/system/httplib/README.html#timeout","363":"/HPCC-Platform/system/httplib/README.html#set-maximum-payload-length-for-reading-request-body","364":"/HPCC-Platform/system/httplib/README.html#server-sent-events","365":"/HPCC-Platform/system/httplib/README.html#default-thread-pool-support","366":"/HPCC-Platform/system/httplib/README.html#override-the-default-thread-pool-with-yours","367":"/HPCC-Platform/system/httplib/README.html#client-example","368":"/HPCC-Platform/system/httplib/README.html#get-with-http-headers","369":"/HPCC-Platform/system/httplib/README.html#post","370":"/HPCC-Platform/system/httplib/README.html#post-with-parameters","371":"/HPCC-Platform/system/httplib/README.html#post-with-multipart-form-data","372":"/HPCC-Platform/system/httplib/README.html#put","373":"/HPCC-Platform/system/httplib/README.html#delete","374":"/HPCC-Platform/system/httplib/README.html#options","375":"/HPCC-Platform/system/httplib/README.html#timeout-1","376":"/HPCC-Platform/system/httplib/README.html#receive-content-with-content-receiver-1","377":"/HPCC-Platform/system/httplib/README.html#send-content-with-content-provider-1","378":"/HPCC-Platform/system/httplib/README.html#with-progress-callback","379":"/HPCC-Platform/system/httplib/README.html#authentication","380":"/HPCC-Platform/system/httplib/README.html#proxy-server-support","381":"/HPCC-Platform/system/httplib/README.html#range","382":"/HPCC-Platform/system/httplib/README.html#keep-alive-connection-1","383":"/HPCC-Platform/system/httplib/README.html#redirect","384":"/HPCC-Platform/system/httplib/README.html#use-a-specitic-network-interface","385":"/HPCC-Platform/system/httplib/README.html#openssl-support","386":"/HPCC-Platform/system/httplib/README.html#compression","387":"/HPCC-Platform/system/httplib/README.html#zlib-support","388":"/HPCC-Platform/system/httplib/README.html#brotli-support","389":"/HPCC-Platform/system/httplib/README.html#compress-request-body-on-client","390":"/HPCC-Platform/system/httplib/README.html#compress-response-body-on-client","391":"/HPCC-Platform/system/httplib/README.html#split-httplib-h-into-h-and-cc","392":"/HPCC-Platform/system/httplib/README.html#note","393":"/HPCC-Platform/system/httplib/README.html#g","394":"/HPCC-Platform/system/httplib/README.html#windows","395":"/HPCC-Platform/system/httplib/README.html#license","396":"/HPCC-Platform/system/httplib/README.html#special-thanks-to","397":"/HPCC-Platform/system/masking/include/readme.html#data-masking-framework","398":"/HPCC-Platform/system/masking/include/readme.html#glossary","399":"/HPCC-Platform/system/masking/include/readme.html#domain","400":"/HPCC-Platform/system/masking/include/readme.html#masker","401":"/HPCC-Platform/system/masking/include/readme.html#profile","402":"/HPCC-Platform/system/masking/include/readme.html#context","403":"/HPCC-Platform/system/masking/include/readme.html#value-type","404":"/HPCC-Platform/system/masking/include/readme.html#mask-style","405":"/HPCC-Platform/system/masking/include/readme.html#rule","406":"/HPCC-Platform/system/masking/include/readme.html#plugin","407":"/HPCC-Platform/system/masking/include/readme.html#engine","408":"/HPCC-Platform/system/masking/include/readme.html#environment","409":"/HPCC-Platform/system/masking/include/readme.html#value-type-sets","410":"/HPCC-Platform/system/masking/include/readme.html#overview","411":"/HPCC-Platform/system/masking/include/readme.html#runtime-compatibility","412":"/HPCC-Platform/system/masking/include/readme.html#implementation","413":"/HPCC-Platform/system/masking/include/readme.html#rule-sets","414":"/HPCC-Platform/system/masking/include/readme.html#overview-1","415":"/HPCC-Platform/system/masking/include/readme.html#runtime-compatibility-1","416":"/HPCC-Platform/system/masking/include/readme.html#implementation-1","417":"/HPCC-Platform/system/masking/include/readme.html#operations","418":"/HPCC-Platform/system/masking/include/readme.html#maskvalue","419":"/HPCC-Platform/system/masking/include/readme.html#overview-2","420":"/HPCC-Platform/system/masking/include/readme.html#runtime-compatibility-2","421":"/HPCC-Platform/system/masking/include/readme.html#implementations","422":"/HPCC-Platform/system/masking/include/readme.html#maskcontent","423":"/HPCC-Platform/system/masking/include/readme.html#overview-3","424":"/HPCC-Platform/system/masking/include/readme.html#runtime-compatibility-3","425":"/HPCC-Platform/system/masking/include/readme.html#implementations-1","426":"/HPCC-Platform/system/masking/include/readme.html#maskmarkupvalue","427":"/HPCC-Platform/system/masking/include/readme.html#overview-4","428":"/HPCC-Platform/system/masking/include/readme.html#runtime-compatibility-4","429":"/HPCC-Platform/system/masking/include/readme.html#implementations-2","430":"/HPCC-Platform/system/masking/include/readme.html#load-time-compatibility","431":"/HPCC-Platform/system/masking/include/readme.html#overview-5","432":"/HPCC-Platform/system/masking/include/readme.html#implementation-2","433":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#libdatamasker-so","434":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#namespace-datamasking","435":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#ccontext","436":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#cmaskstyle","437":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#cpartialmaskstyle","438":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#crule","439":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#cserialtokenrule","440":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#tplugin","441":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#tprofile","442":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#tserialprofile","443":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#tvaluetype","444":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#entry-point-functions","445":"/HPCC-Platform/system/masking/plugins/datamasker/readme.html#newpartialmaskserialtoken","446":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#jwt-authorization-security-manager-plugin","447":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#code-documentation","448":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#theory-of-operations","449":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#deviations-from-oic-specification","450":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#implications-of-deviations","451":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#hpcc-systems-configuration-notes","452":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#hpcc-systems-authorization-and-jwt-claims","453":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#workunit-scope-permissions","454":"/HPCC-Platform/system/security/plugins/jwtSecurity/README.html#file-scope-permissions","455":"/HPCC-Platform/testing/regress/cleanupReadme.html#cleanup-parameter-of-regression-suite-run-and-query-sub-command","456":"/HPCC-Platform/testing/regress/cleanupReadme.html#command","457":"/HPCC-Platform/testing/regress/cleanupReadme.html#result","458":"/HPCC-Platform/testing/regress/cleanupReadme.html#the-sample-terminal-output-for-hthor-target","459":"/HPCC-Platform/testing/regress/ecl/README.html#test-suite-for-the-parquet-plugin","460":"/HPCC-Platform/testing/regress/ecl/README.html#running-the-test-suite","461":"/HPCC-Platform/testing/regress/ecl/README.html#project-description","462":"/HPCC-Platform/testing/regress/ecl/README.html#test-files","463":"/HPCC-Platform/testing/regress/ecl/README.html#test-suite-overview","464":"/HPCC-Platform/testing/regress/ecl/README.html#type-testing","465":"/HPCC-Platform/testing/regress/ecl/README.html#data-type-tests","466":"/HPCC-Platform/testing/regress/ecl/README.html#arrow-types-supported-by-the-parquet-plugin","467":"/HPCC-Platform/testing/regress/ecl/README.html#compression-testing","468":"/HPCC-Platform/testing/regress/ecl/README.html#parquet-read-and-write-operations","469":"/HPCC-Platform/testing/regress/ecl/README.html#additional-tests","470":"/HPCC-Platform/testing/regress/ecl/README.html#test-evaluation","471":"/HPCC-Platform/tools/esdlcmd/README.html#esdl-utility","472":"/HPCC-Platform/tools/esdlcmd/README.html#manifest","473":"/HPCC-Platform/tools/esdlcmd/README.html#manifest-file","474":"/HPCC-Platform/tools/esdlcmd/README.html#example","475":"/HPCC-Platform/tools/esdlcmd/README.html#syntax","476":"/HPCC-Platform/tools/esdlcmd/README.html#manifest-1","477":"/HPCC-Platform/tools/esdlcmd/README.html#servicebinding","478":"/HPCC-Platform/tools/esdlcmd/README.html#standard-attributes","479":"/HPCC-Platform/tools/esdlcmd/README.html#service-specific-attributes","480":"/HPCC-Platform/tools/esdlcmd/README.html#auxillary-attributes","481":"/HPCC-Platform/tools/esdlcmd/README.html#esdldefinition","482":"/HPCC-Platform/tools/esdlcmd/README.html#include","483":"/HPCC-Platform/tools/esdlcmd/README.html#scripts","484":"/HPCC-Platform/tools/esdlcmd/README.html#transform","485":"/HPCC-Platform/tools/esdlcmd/README.html#usage","486":"/HPCC-Platform/tools/esdlcmd/README.html#output","487":"/HPCC-Platform/tools/esdlcmd/README.html#bundle","488":"/HPCC-Platform/tools/esdlcmd/README.html#binding","489":"/HPCC-Platform/tools/esp-api/README.html#developer-readme-for-esp-api-command-line-tool","490":"/HPCC-Platform/tools/esp-api/README.html#overview","491":"/HPCC-Platform/tools/esp-api/README.html#usage-notes","492":"/HPCC-Platform/tools/esp-api/README.html#ideas-for-expansion","493":"/HPCC-Platform/tools/tagging/README.html#tagging-new-versions","494":"/HPCC-Platform/tools/tagging/README.html#general","495":"/HPCC-Platform/tools/tagging/README.html#pre-requisites","496":"/HPCC-Platform/tools/tagging/README.html#tagging-new-versions-1","497":"/HPCC-Platform/tools/tagging/README.html#taking-a-build-gold","498":"/HPCC-Platform/tools/tagging/README.html#creating-a-new-rc-for-an-existing-point-release","499":"/HPCC-Platform/tools/tagging/README.html#create-a-new-minor-major-branch"},"fieldIds":{"title":0,"titles":1,"text":2},"fieldLength":{"0":[2,1,17],"1":[1,2,1],"2":[1,3,1],"3":[5,4,54],"4":[3,4,43],"5":[13,6,44],"6":[1,3,25],"7":[3,4,45],"8":[4,4,34],"9":[2,3,1],"10":[3,5,24],"11":[3,5,28],"12":[4,5,67],"13":[1,5,66],"14":[5,3,1],"15":[1,8,52],"16":[5,3,58],"17":[5,2,34],"18":[1,2,178],"19":[2,8,27],"20":[1,2,60],"21":[3,2,55],"22":[3,2,50],"23":[7,2,149],"24":[2,1,43],"25":[2,1,10],"26":[4,1,90],"27":[1,1,62],"28":[1,1,59],"29":[1,1,57],"30":[1,1,43],"31":[2,1,42],"32":[1,1,23],"33":[2,1,44],"34":[2,2,12],"35":[5,1,1],"36":[5,5,92],"37":[2,7,74],"38":[2,7,39],"39":[2,7,19],"40":[8,5,1],"41":[2,12,15],"42":[5,12,17],"43":[6,12,23],"44":[7,12,85],"45":[3,5,81],"46":[1,1,1],"47":[1,1,28],"48":[1,1,88],"49":[2,1,90],"50":[4,1,93],"51":[3,1,83],"52":[5,1,1],"53":[3,5,174],"54":[6,5,78],"55":[4,5,33],"56":[3,5,210],"57":[1,1,1],"58":[3,1,244],"59":[1,4,133],"60":[1,4,29],"61":[1,4,88],"62":[1,4,73],"63":[3,4,88],"64":[2,1,164],"65":[4,3,131],"66":[2,3,96],"67":[1,1,71],"68":[3,1,75],"69":[2,1,179],"70":[1,1,199],"71":[2,1,1],"72":[1,2,120],"73":[1,2,144],"74":[2,2,73],"75":[3,2,105],"76":[3,2,95],"77":[3,2,83],"78":[1,1,1],"79":[17,1,69],"80":[1,1,64],"81":[1,1,118],"82":[3,1,49],"83":[2,1,116],"84":[2,1,126],"85":[1,2,122],"86":[2,1,31],"87":[3,2,134],"88":[2,2,87],"89":[2,2,47],"90":[2,2,109],"91":[2,4,111],"92":[1,4,109],"93":[2,4,71],"94":[3,4,37],"95":[3,4,73],"96":[2,1,55],"97":[9,2,1],"98":[1,1,1],"99":[4,1,76],"100":[2,1,74],"101":[3,1,24],"102":[2,3,85],"103":[2,3,255],"104":[1,3,93],"105":[1,3,143],"106":[2,3,303],"107":[3,1,26],"108":[2,3,257],"109":[1,3,106],"110":[2,3,53],"111":[4,1,12],"112":[2,4,68],"113":[2,4,52],"114":[5,4,91],"115":[4,4,45],"116":[7,4,47],"117":[5,4,22],"118":[2,1,1],"119":[2,2,15],"120":[3,2,49],"121":[5,2,1],"122":[1,7,58],"123":[3,7,109],"124":[1,2,75],"125":[3,2,19],"126":[2,5,41],"127":[2,5,8],"128":[2,5,176],"129":[3,2,72],"130":[3,1,60],"131":[4,3,138],"132":[2,3,66],"133":[1,3,91],"134":[2,3,35],"135":[4,1,41],"136":[2,4,30],"137":[2,4,11],"138":[2,6,9],"139":[2,8,49],"140":[2,8,75],"141":[6,6,59],"142":[1,4,28],"143":[2,5,118],"144":[3,5,36],"145":[1,1,117],"146":[2,1,36],"147":[3,2,42],"148":[1,2,73],"149":[1,2,174],"150":[2,2,49],"151":[3,2,1],"152":[1,5,43],"153":[1,5,68],"154":[1,5,49],"155":[1,5,99],"156":[2,5,63],"157":[2,1,82],"158":[1,2,60],"159":[2,2,72],"160":[4,2,108],"161":[2,2,89],"162":[2,1,76],"163":[2,1,148],"164":[4,1,64],"165":[3,1,1],"166":[1,3,118],"167":[1,4,69],"168":[2,3,23],"169":[1,5,151],"170":[1,5,203],"171":[3,5,18],"172":[2,3,186],"173":[2,3,14],"174":[1,4,106],"175":[1,4,89],"176":[2,4,39],"177":[2,4,8],"178":[2,5,37],"179":[2,5,66],"180":[2,5,52],"181":[2,5,75],"182":[3,5,60],"183":[1,3,107],"184":[2,3,75],"185":[2,5,71],"186":[2,5,101],"187":[2,3,197],"188":[4,5,84],"189":[2,5,67],"190":[2,1,152],"191":[1,1,140],"192":[1,1,269],"193":[1,1,97],"194":[2,1,136],"195":[3,1,124],"196":[2,1,402],"197":[2,1,26],"198":[2,2,45],"199":[8,2,38],"200":[2,2,18],"201":[2,1,41],"202":[2,2,53],"203":[2,2,47],"204":[1,3,240],"205":[3,3,40],"206":[3,4,10],"207":[4,4,4],"208":[3,4,4],"209":[2,1,70],"210":[4,2,49],"211":[1,4,104],"212":[1,4,46],"213":[2,4,38],"214":[4,2,72],"215":[2,1,1],"216":[4,2,62],"217":[3,2,193],"218":[2,4,62],"219":[2,4,99],"220":[1,4,76],"221":[1,4,169],"222":[1,4,42],"223":[1,4,40],"224":[1,4,21],"225":[1,4,28],"226":[1,4,16],"227":[1,4,1],"228":[3,2,1],"229":[2,4,8],"230":[4,4,11],"231":[2,1,1],"232":[4,2,13],"233":[1,4,174],"234":[3,4,153],"235":[1,4,5],"236":[6,1,15],"237":[5,1,1],"238":[2,5,110],"239":[2,5,100],"240":[5,7,149],"241":[7,7,100],"242":[3,5,127],"243":[2,5,56],"244":[2,1,13],"245":[2,2,186],"246":[3,2,93],"247":[3,2,63],"248":[2,1,1],"249":[1,2,217],"250":[4,3,113],"251":[6,3,84],"252":[1,3,63],"253":[3,2,28],"254":[1,5,145],"255":[1,5,74],"256":[1,5,108],"257":[3,5,58],"258":[1,5,50],"259":[1,5,46],"260":[2,5,64],"261":[1,5,83],"262":[3,5,60],"263":[1,5,34],"264":[3,2,132],"265":[1,5,62],"266":[1,5,250],"267":[3,2,158],"268":[2,5,62],"269":[5,7,380],"270":[3,7,353],"271":[1,5,83],"272":[7,6,126],"273":[1,6,66],"274":[6,6,212],"275":[11,6,178],"276":[8,1,71],"277":[9,8,108],"278":[3,17,120],"279":[3,17,108],"280":[2,17,101],"281":[5,17,32],"282":[8,22,57],"283":[13,22,50],"284":[9,22,33],"285":[11,22,49],"286":[6,22,29],"287":[1,17,49],"288":[2,18,72],"289":[2,18,38],"290":[3,18,51],"291":[3,1,28],"292":[1,3,17],"293":[3,3,19],"294":[2,3,19],"295":[4,3,15],"296":[1,3,21],"297":[1,3,20],"298":[4,1,17],"299":[1,4,154],"300":[1,4,35],"301":[2,5,98],"302":[2,5,69],"303":[3,5,30],"304":[2,5,11],"305":[2,5,1],"306":[3,7,22],"307":[4,7,55],"308":[6,1,457],"309":[4,1,299],"310":[4,1,480],"311":[6,1,280],"312":[5,1,216],"313":[3,1,306],"314":[5,1,32],"315":[3,1,19],"316":[8,1,1],"317":[6,8,95],"318":[6,8,194],"319":[5,8,147],"320":[9,8,163],"321":[4,8,210],"322":[7,8,70],"323":[3,8,67],"324":[3,8,141],"325":[3,8,78],"326":[2,8,312],"327":[2,8,329],"328":[2,1,183],"329":[5,1,176],"330":[7,1,240],"331":[2,1,37],"332":[4,2,30],"333":[2,2,42],"334":[3,2,15],"335":[3,1,120],"336":[3,1,54],"337":[2,3,117],"338":[2,3,81],"339":[8,3,189],"340":[2,1,217],"341":[10,1,1],"342":[1,10,54],"343":[4,1,1],"344":[1,4,1],"345":[1,5,44],"346":[1,5,62],"347":[2,5,88],"348":[2,5,64],"349":[5,1,139],"350":[2,1,25],"351":[2,2,58],"352":[10,4,11],"353":[3,4,84],"354":[1,4,10],"355":[2,4,32],"356":[5,4,21],"357":[5,4,38],"358":[5,4,63],"359":[3,4,37],"360":[5,4,38],"361":[3,4,11],"362":[1,4,15],"363":[8,4,9],"364":[3,4,7],"365":[4,4,29],"366":[7,4,33],"367":[2,2,44],"368":[4,4,18],"369":[1,4,18],"370":[3,4,16],"371":[5,4,32],"372":[1,4,9],"373":[1,4,7],"374":[1,4,8],"375":[1,4,14],"376":[5,4,37],"377":[5,4,32],"378":[3,4,38],"379":[1,4,21],"380":[3,4,24],"381":[1,4,33],"382":[3,4,19],"383":[1,4,17],"384":[5,4,20],"385":[2,2,44],"386":[1,2,25],"387":[2,3,13],"388":[2,3,24],"389":[5,3,13],"390":[5,3,19],"391":[6,2,11],"392":[1,2,1],"393":[1,3,19],"394":[1,3,25],"395":[1,2,7],"396":[3,2,17],"397":[3,1,112],"398":[1,3,1],"399":[1,4,99],"400":[1,4,146],"401":[1,4,81],"402":[1,4,113],"403":[2,4,101],"404":[2,4,52],"405":[1,4,79],"406":[1,4,48],"407":[1,4,104],"408":[1,3,89],"409":[3,4,7],"410":[1,7,95],"411":[2,7,74],"412":[1,7,125],"413":[2,4,7],"414":[1,6,79],"415":[2,6,69],"416":[1,6,95],"417":[1,3,1],"418":[1,4,15],"419":[1,5,123],"420":[2,5,108],"421":[1,5,108],"422":[1,4,11],"423":[1,5,80],"424":[2,5,79],"425":[1,5,105],"426":[1,4,15],"427":[1,5,106],"428":[2,5,153],"429":[1,5,2],"430":[3,3,7],"431":[1,6,122],"432":[1,6,164],"433":[2,1,31],"434":[2,2,1],"435":[1,4,51],"436":[1,4,64],"437":[1,4,92],"438":[1,4,97],"439":[1,4,133],"440":[1,4,59],"441":[1,4,240],"442":[1,4,42],"443":[1,4,126],"444":[3,2,51],"445":[1,5,37],"446":[5,1,128],"447":[2,5,53],"448":[3,5,178],"449":[4,5,130],"450":[3,5,59],"451":[4,5,158],"452":[6,5,101],"453":[3,9,23],"454":[3,9,23],"455":[10,1,50],"456":[2,10,29],"457":[2,10,1],"458":[8,12,71],"459":[6,1,1],"460":[4,6,149],"461":[2,6,85],"462":[2,1,52],"463":[3,2,1],"464":[2,2,18],"465":[3,4,13],"466":[7,6,29],"467":[2,1,21],"468":[5,2,14],"469":[2,1,19],"470":[2,1,46],"471":[2,1,92],"472":[1,2,23],"473":[2,3,88],"474":[1,3,211],"475":[1,3,26],"476":[1,4,77],"477":[1,4,90],"478":[2,5,61],"479":[3,5,115],"480":[2,5,108],"481":[1,4,58],"482":[1,4,130],"483":[1,4,73],"484":[1,4,55],"485":[1,3,101],"486":[1,3,186],"487":[1,4,185],"488":[1,4,149],"489":[8,1,1],"490":[1,8,55],"491":[2,8,51],"492":[3,8,78],"493":[3,1,1],"494":[1,3,51],"495":[2,3,63],"496":[3,3,76],"497":[5,3,51],"498":[10,3,28],"499":[6,3,13]},"averageFieldLength":[2.744,3.964000000000001,74.734],"storedFields":{"0":{"title":"Build Instructions","titles":[]},"1":{"title":"Prerequisites","titles":["Build Instructions"]},"2":{"title":"Ubuntu","titles":["Build Instructions","Prerequisites"]},"3":{"title":"Ubuntu 19.10/19.04/18.04","titles":["Build Instructions","Prerequisites","Ubuntu"]},"4":{"title":"Ubuntu 16.04","titles":["Build Instructions","Prerequisites","Ubuntu"]},"5":{"title":"Additional information for building versions prior to 7.0 on Ubuntu 18.04","titles":["Build Instructions","Prerequisites","Ubuntu","Ubuntu 16.04"]},"6":{"title":"CentOS","titles":["Build Instructions","Prerequisites"]},"7":{"title":"CentOS 7:","titles":["Build Instructions","Prerequisites","CentOS"]},"8":{"title":"CentOS 6.4:","titles":["Build Instructions","Prerequisites","CentOS"]},"9":{"title":"Other Platforms","titles":["Build Instructions","Prerequisites"]},"10":{"title":"Fedora 19:","titles":["Build Instructions","Prerequisites","Other Platforms"]},"11":{"title":"Fedora 23:","titles":["Build Instructions","Prerequisites","Other Platforms"]},"12":{"title":"Mac (Snow Leopard):","titles":["Build Instructions","Prerequisites","Other Platforms"]},"13":{"title":"Windows","titles":["Build Instructions","Prerequisites","Other Platforms"]},"14":{"title":"Other required third-party packages","titles":["Build Instructions","Prerequisites"]},"15":{"title":"Nodejs","titles":["Build Instructions","Prerequisites","Other required third-party packages"]},"16":{"title":"Building with R Support:","titles":["Build Instructions","Prerequisites"]},"17":{"title":"Get Latest HPCC Systems Sources","titles":["Build Instructions"]},"18":{"title":"CMake","titles":["Build Instructions"]},"19":{"title":"NOTE:","titles":["Build Instructions","CMake","Building with R Support:"]},"20":{"title":"Building","titles":["Build Instructions"]},"21":{"title":"Creating a package","titles":["Build Instructions"]},"22":{"title":"Installing the package","titles":["Build Instructions"]},"23":{"title":"To build client tools for MacOS:","titles":["Build Instructions"]},"24":{"title":"Description / Rationale","titles":[]},"25":{"title":"Getting Started","titles":[]},"26":{"title":"Release + Support Policy","titles":[]},"27":{"title":"Architecture","titles":[]},"28":{"title":"Thor","titles":["Architecture"]},"29":{"title":"Roxie","titles":["Architecture"]},"30":{"title":"ECL","titles":["Architecture"]},"31":{"title":"ECL IDE","titles":["Architecture"]},"32":{"title":"ESP","titles":["Architecture"]},"33":{"title":"Developer documentation","titles":[]},"34":{"title":"Regression test","titles":["Developer documentation"]},"35":{"title":"CMake files structure and usage","titles":[]},"36":{"title":"Directory structure of CMake files","titles":["CMake files structure and usage"]},"37":{"title":"Common Macros","titles":["CMake files structure and usage","Directory structure of CMake files"]},"38":{"title":"Documentation Macros","titles":["CMake files structure and usage","Directory structure of CMake files"]},"39":{"title":"Initfiles macro","titles":["CMake files structure and usage","Directory structure of CMake files"]},"40":{"title":"Some standard techniques used in Cmake project files","titles":["CMake files structure and usage"]},"41":{"title":"Common looping","titles":["CMake files structure and usage","Some standard techniques used in Cmake project files"]},"42":{"title":"Common installs over just install","titles":["CMake files structure and usage","Some standard techniques used in Cmake project files"]},"43":{"title":"Common settings for generated source files","titles":["CMake files structure and usage","Some standard techniques used in Cmake project files"]},"44":{"title":"Using custom commands between multiple cmake files","titles":["CMake files structure and usage","Some standard techniques used in Cmake project files"]},"45":{"title":"FindXXXXX.cmake format","titles":["CMake files structure and usage"]},"46":{"title":"Introduction","titles":[]},"47":{"title":"Purpose","titles":["Introduction"]},"48":{"title":"Aims","titles":["Introduction"]},"49":{"title":"Key ideas","titles":["Introduction"]},"50":{"title":"From declarative to imperative","titles":["Introduction"]},"51":{"title":"Flow of processing","titles":["Introduction"]},"52":{"title":"Working on the code generator","titles":[]},"53":{"title":"The regression suite","titles":["Working on the code generator"]},"54":{"title":"Running directly from the build directory","titles":["Working on the code generator"]},"55":{"title":"ECLCC_ECLBUNDLE_PATH eclBundlesPath","titles":["Working on the code generator"]},"56":{"title":"Hints and tips","titles":["Working on the code generator"]},"57":{"title":"Expressions","titles":[]},"58":{"title":"Expression Graph representation","titles":["Expressions"]},"59":{"title":"IHqlExpression","titles":["Expressions","Expression Graph representation"]},"60":{"title":"IHqlSimpleScope","titles":["Expressions","Expression Graph representation"]},"61":{"title":"IHqlScope","titles":["Expressions","Expression Graph representation"]},"62":{"title":"IHqlDataset","titles":["Expressions","Expression Graph representation"]},"63":{"title":"Properties and attributes","titles":["Expressions","Expression Graph representation"]},"64":{"title":"Field references","titles":["Expressions"]},"65":{"title":"Attribute "new"","titles":["Expressions","Field references"]},"66":{"title":"Transforming selects","titles":["Expressions","Field references"]},"67":{"title":"Annotations","titles":["Expressions"]},"68":{"title":"Associated side-effects","titles":["Expressions"]},"69":{"title":"Derived properties","titles":["Expressions"]},"70":{"title":"Transformations","titles":["Expressions"]},"71":{"title":"Key Stages","titles":[]},"72":{"title":"Parsing","titles":["Key Stages"]},"73":{"title":"Normalizing","titles":["Key Stages"]},"74":{"title":"Scope checking","titles":["Key Stages"]},"75":{"title":"Constant folding: foldHqlExpression","titles":["Key Stages"]},"76":{"title":"Expression optimizer: optimizeHqlExpression","titles":["Key Stages"]},"77":{"title":"Implicit project: insertImplicitProjects","titles":["Key Stages"]},"78":{"title":"Workunits","titles":[]},"79":{"title":"is this the correct term? Should it be a query? This should really be independent of this document...)","titles":["Workunits"]},"80":{"title":"Workflow","titles":["Workunits"]},"81":{"title":"Graph","titles":["Workunits"]},"82":{"title":"Inputs and Results","titles":["Workunits"]},"83":{"title":"Generated code","titles":["Workunits"]},"84":{"title":"Implementation details","titles":[]},"85":{"title":"Parser","titles":["Implementation details"]},"86":{"title":"Generated code","titles":[]},"87":{"title":"C++ Output structures","titles":["Generated code"]},"88":{"title":"Activity Helper","titles":["Generated code"]},"89":{"title":"Meta helper","titles":["Generated code"]},"90":{"title":"Building expressions","titles":["Generated code"]},"91":{"title":"Scalar expressions","titles":["Generated code","Building expressions"]},"92":{"title":"Datasets","titles":["Generated code","Building expressions"]},"93":{"title":"Dataset cursors","titles":["Generated code","Building expressions"]},"94":{"title":"Field access classes","titles":["Generated code","Building expressions"]},"95":{"title":"Key filepos weirdness","titles":["Generated code","Building expressions"]},"96":{"title":"Source code","titles":[]},"97":{"title":"ecl/eclcc The executable which ties everything together.","titles":["Source code"]},"98":{"title":"Challenges","titles":[]},"99":{"title":"From declarative to imperative","titles":["Challenges"]},"100":{"title":"The parser","titles":["Challenges"]},"101":{"title":"Code Review Guidelines","titles":[]},"102":{"title":"Review Goals","titles":["Code Review Guidelines"]},"103":{"title":"General comments","titles":["Code Review Guidelines"]},"104":{"title":"Strictness","titles":["Code Review Guidelines"]},"105":{"title":"Checklist","titles":["Code Review Guidelines"]},"106":{"title":"Comment tags","titles":["Code Review Guidelines"]},"107":{"title":"Code Submission Guidelines","titles":[]},"108":{"title":"Pull requests","titles":["Code Submission Guidelines"]},"109":{"title":"Reviewers","titles":["Code Submission Guidelines"]},"110":{"title":"Target branch","titles":["Code Submission Guidelines"]},"111":{"title":"Working with developer documentation","titles":[]},"112":{"title":"Documentation location","titles":["Working with developer documentation"]},"113":{"title":"Documentation format","titles":["Working with developer documentation"]},"114":{"title":"Rendering documentation locally with VitePress","titles":["Working with developer documentation"]},"115":{"title":"Adding a new document","titles":["Working with developer documentation"]},"116":{"title":"Adding a new document to the sidebar","titles":["Working with developer documentation"]},"117":{"title":"Editing the main landing page","titles":["Working with developer documentation"]},"118":{"title":"Development Guide","titles":[]},"119":{"title":"HPCC Source","titles":["Development Guide"]},"120":{"title":"Getting the sources","titles":["Development Guide"]},"121":{"title":"Building the system from sources","titles":["Development Guide"]},"122":{"title":"Requirements","titles":["Development Guide","Building the system from sources"]},"123":{"title":"Building the system","titles":["Development Guide","Building the system from sources"]},"124":{"title":"Packaging","titles":["Development Guide"]},"125":{"title":"Testing the system","titles":["Development Guide"]},"126":{"title":"Unit Tests","titles":["Development Guide","Testing the system"]},"127":{"title":"Regression Tests","titles":["Development Guide","Testing the system"]},"128":{"title":"Compiler Tests","titles":["Development Guide","Testing the system"]},"129":{"title":"Debugging the system","titles":["Development Guide"]},"130":{"title":"HPCC git support","titles":[]},"131":{"title":"Credentials for local development","titles":["HPCC git support"]},"132":{"title":"Configuring eclccserver","titles":["HPCC git support"]},"133":{"title":"Kubernetes","titles":["HPCC git support"]},"134":{"title":"Bare-metal","titles":["HPCC git support"]},"135":{"title":"LDAP Security Manager Init","titles":[]},"136":{"title":"LDAP Instances","titles":["LDAP Security Manager Init"]},"137":{"title":"Initialization Steps","titles":["LDAP Security Manager Init"]},"138":{"title":"Load Configuration","titles":["LDAP Security Manager Init","Initialization Steps"]},"139":{"title":"AD Hosts","titles":["LDAP Security Manager Init","Initialization Steps","Load Configuration"]},"140":{"title":"AD Credentials","titles":["LDAP Security Manager Init","Initialization Steps","Load Configuration"]},"141":{"title":"Retrieve Server Information from the AD","titles":["LDAP Security Manager Init","Initialization Steps"]},"142":{"title":"Connections","titles":["LDAP Security Manager Init"]},"143":{"title":"Connection Pool","titles":["LDAP Security Manager Init","Connections"]},"144":{"title":"Handling AD Hosts","titles":["LDAP Security Manager Init","Connections"]},"145":{"title":"Introduction","titles":[]},"146":{"title":"Main Structure","titles":[]},"147":{"title":"The page bitmap","titles":["Main Structure"]},"148":{"title":"IRowManager","titles":["Main Structure"]},"149":{"title":"Heaps","titles":["Main Structure"]},"150":{"title":"Huge Heap","titles":["Main Structure"]},"151":{"title":"Specialised Heaps:","titles":["Main Structure"]},"152":{"title":"Packed","titles":["Main Structure","Specialised Heaps:"]},"153":{"title":"Unique","titles":["Main Structure","Specialised Heaps:"]},"154":{"title":"Blocked","titles":["Main Structure","Specialised Heaps:"]},"155":{"title":"Scanning","titles":["Main Structure","Specialised Heaps:"]},"156":{"title":"Delay Release","titles":["Main Structure","Specialised Heaps:"]},"157":{"title":"Dynamic Spilling","titles":[]},"158":{"title":"Complications","titles":["Dynamic Spilling"]},"159":{"title":"Callback Rules","titles":["Dynamic Spilling"]},"160":{"title":"Resizing Large memory blocks","titles":["Dynamic Spilling"]},"161":{"title":"Compacting heaps","titles":["Dynamic Spilling"]},"162":{"title":"Shared Memory","titles":[]},"163":{"title":"Huge pages","titles":[]},"164":{"title":"Global memory and channels","titles":[]},"165":{"title":"Metrics Framework Design","titles":[]},"166":{"title":"Introduction","titles":["Metrics Framework Design"]},"167":{"title":"Definitions","titles":["Metrics Framework Design","Introduction"]},"168":{"title":"Use Scenarios","titles":["Metrics Framework Design"]},"169":{"title":"Roxie","titles":["Metrics Framework Design","Use Scenarios"]},"170":{"title":"ESP","titles":["Metrics Framework Design","Use Scenarios"]},"171":{"title":"Dali Use Cases","titles":["Metrics Framework Design","Use Scenarios"]},"172":{"title":"Framework Design","titles":["Metrics Framework Design"]},"173":{"title":"Framework Implementation","titles":["Metrics Framework Design"]},"174":{"title":"Metrics","titles":["Metrics Framework Design","Framework Implementation"]},"175":{"title":"Sinks","titles":["Metrics Framework Design","Framework Implementation"]},"176":{"title":"Metrics Reporter","titles":["Metrics Framework Design","Framework Implementation"]},"177":{"title":"Metrics Implementations","titles":["Metrics Framework Design","Framework Implementation"]},"178":{"title":"Counter Metric","titles":["Metrics Framework Design","Framework Implementation","Metrics Implementations"]},"179":{"title":"Gauge Metric","titles":["Metrics Framework Design","Framework Implementation","Metrics Implementations"]},"180":{"title":"Custom Metric","titles":["Metrics Framework Design","Framework Implementation","Metrics Implementations"]},"181":{"title":"Histogram Metric","titles":["Metrics Framework Design","Framework Implementation","Metrics Implementations"]},"182":{"title":"Scaled Histogram Metric","titles":["Metrics Framework Design","Framework Implementation","Metrics Implementations"]},"183":{"title":"Configuration","titles":["Metrics Framework Design"]},"184":{"title":"Metric Naming","titles":["Metrics Framework Design"]},"185":{"title":"Base Name","titles":["Metrics Framework Design","Metric Naming"]},"186":{"title":"Meta Data","titles":["Metrics Framework Design","Metric Naming"]},"187":{"title":"Component Instrumentation","titles":["Metrics Framework Design"]},"188":{"title":"Adding Metric Meta Data","titles":["Metrics Framework Design","Component Instrumentation"]},"189":{"title":"Metric Units","titles":["Metrics Framework Design","Component Instrumentation"]},"190":{"title":"Storage planes","titles":[]},"191":{"title":"Files","titles":[]},"192":{"title":"Functions","titles":[]},"193":{"title":"Examples","titles":[]},"194":{"title":"Milestones:","titles":["Examples"]},"195":{"title":"File reading refactoring","titles":[]},"196":{"title":"DFU server","titles":[]},"197":{"title":"Developer Documentation","titles":[]},"198":{"title":"General documentation","titles":["Developer Documentation"]},"199":{"title":"Implementation details for different parts of the system","titles":["Developer Documentation"]},"200":{"title":"Other documentation","titles":["Developer Documentation"]},"201":{"title":"Security Configuration","titles":[]},"202":{"title":"Supported Configurations","titles":["Security Configuration"]},"203":{"title":"Security Managers","titles":["Security Configuration"]},"204":{"title":"LDAP","titles":["Security Configuration","Security Managers"]},"205":{"title":"Plugin Security Managers","titles":["Security Configuration","Security Managers"]},"206":{"title":"httpasswd Security Manager","titles":["Security Configuration","Security Managers","Plugin Security Managers"]},"207":{"title":"Single User Security Manager","titles":["Security Configuration","Security Managers","Plugin Security Managers"]},"208":{"title":"JWT Security Manager","titles":["Security Configuration","Security Managers","Plugin Security Managers"]},"209":{"title":"User Authentication","titles":[]},"210":{"title":"Security Manager User Authentication","titles":["User Authentication"]},"211":{"title":"LDAP","titles":["User Authentication","Security Manager User Authentication"]},"212":{"title":"HTPasswd","titles":["User Authentication","Security Manager User Authentication"]},"213":{"title":"Single User","titles":["User Authentication","Security Manager User Authentication"]},"214":{"title":"User Authentication During Authorization","titles":["User Authentication"]},"215":{"title":"Coding conventions","titles":[]},"216":{"title":"Why coding conventions?","titles":["Coding conventions"]},"217":{"title":"C++ coding conventions","titles":["Coding conventions"]},"218":{"title":"Source files","titles":["Coding conventions","C++ coding conventions"]},"219":{"title":"Java-style","titles":["Coding conventions","C++ coding conventions"]},"220":{"title":"Identifiers","titles":["Coding conventions","C++ coding conventions"]},"221":{"title":"Pointers","titles":["Coding conventions","C++ coding conventions"]},"222":{"title":"Indentation","titles":["Coding conventions","C++ coding conventions"]},"223":{"title":"Comments","titles":["Coding conventions","C++ coding conventions"]},"224":{"title":"Classes","titles":["Coding conventions","C++ coding conventions"]},"225":{"title":"Namespaces","titles":["Coding conventions","C++ coding conventions"]},"226":{"title":"Other","titles":["Coding conventions","C++ coding conventions"]},"227":{"title":"C++11","titles":["Coding conventions","C++ coding conventions"]},"228":{"title":"Other coding conventions","titles":["Coding conventions"]},"229":{"title":"ECL code","titles":["Coding conventions","Other coding conventions"]},"230":{"title":"Javascript, XML, XSL etc","titles":["Coding conventions","Other coding conventions"]},"231":{"title":"Design Patterns","titles":[]},"232":{"title":"Why Design Patterns?","titles":["Design Patterns"]},"233":{"title":"Interfaces","titles":["Design Patterns","Why Design Patterns?"]},"234":{"title":"Reference counted objects","titles":["Design Patterns","Why Design Patterns?"]},"235":{"title":"STL","titles":["Design Patterns","Why Design Patterns?"]},"236":{"title":"Structure of the HPCC source tree","titles":[]},"237":{"title":"Build Assets for individual developer","titles":[]},"238":{"title":"Build Assets","titles":["Build Assets for individual developer"]},"239":{"title":"Dependent variables","titles":["Build Assets for individual developer"]},"240":{"title":"Generating the windows signing certificate","titles":["Build Assets for individual developer","Dependent variables"]},"241":{"title":"Generating a signing key for linux builds","titles":["Build Assets for individual developer","Dependent variables"]},"242":{"title":"Starting a build","titles":["Build Assets for individual developer"]},"243":{"title":"Asset output","titles":["Build Assets for individual developer"]},"244":{"title":"Current versions","titles":[]},"245":{"title":"Supported versions","titles":["Current versions"]},"246":{"title":"Patches and images","titles":["Current versions"]},"247":{"title":"Package versions.","titles":["Current versions"]},"248":{"title":"Understanding workunits","titles":[]},"249":{"title":"Introduction","titles":["Understanding workunits"]},"250":{"title":"Contents of a workunit","titles":["Understanding workunits","Introduction"]},"251":{"title":"How is the workunit used?","titles":["Understanding workunits","Introduction"]},"252":{"title":"Example","titles":["Understanding workunits","Introduction"]},"253":{"title":"Workunit Main Elements","titles":["Understanding workunits"]},"254":{"title":"Workflow","titles":["Understanding workunits","Workunit Main Elements"]},"255":{"title":"MyProcess","titles":["Understanding workunits","Workunit Main Elements"]},"256":{"title":"Graph","titles":["Understanding workunits","Workunit Main Elements"]},"257":{"title":"Generated Activity Helpers","titles":["Understanding workunits","Workunit Main Elements"]},"258":{"title":"Other","titles":["Understanding workunits","Workunit Main Elements"]},"259":{"title":"Options","titles":["Understanding workunits","Workunit Main Elements"]},"260":{"title":"Input Parameters","titles":["Understanding workunits","Workunit Main Elements"]},"261":{"title":"Results","titles":["Understanding workunits","Workunit Main Elements"]},"262":{"title":"Timings and Statistics","titles":["Understanding workunits","Workunit Main Elements"]},"263":{"title":"Manifests","titles":["Understanding workunits","Workunit Main Elements"]},"264":{"title":"Stages of Execution","titles":["Understanding workunits"]},"265":{"title":"Queues","titles":["Understanding workunits","Stages of Execution"]},"266":{"title":"Workflow","titles":["Understanding workunits","Stages of Execution"]},"267":{"title":"Specialised Workflow Items","titles":["Understanding workunits"]},"268":{"title":"Graph Execution","titles":["Understanding workunits","Specialised Workflow Items"]},"269":{"title":"Details of the graph structure","titles":["Understanding workunits","Specialised Workflow Items","Graph Execution"]},"270":{"title":"Executing the graph","titles":["Understanding workunits","Specialised Workflow Items","Graph Execution"]},"271":{"title":"Appendix","titles":["Understanding workunits","Specialised Workflow Items"]},"272":{"title":"Key types and interfaces from eclhelper.hpp","titles":["Understanding workunits","Specialised Workflow Items","Appendix"]},"273":{"title":"Glossary","titles":["Understanding workunits","Specialised Workflow Items","Appendix"]},"274":{"title":"Full text of the workunit XML","titles":["Understanding workunits","Specialised Workflow Items","Appendix"]},"275":{"title":"Full contents of the generated C++ (as a single file)","titles":["Understanding workunits","Specialised Workflow Items","Appendix"]},"276":{"title":"Contributing Documentation to the HPCC Systems Platform Project","titles":[]},"277":{"title":"Documenting a New Software Feature--Required and Optional Components","titles":["Contributing Documentation to the HPCC Systems Platform Project"]},"278":{"title":"Required Components:","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components"]},"279":{"title":"Optional Components:","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components"]},"280":{"title":"General Tips","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components"]},"281":{"title":"Who should write it?","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components"]},"282":{"title":"Changing the default value of a configuration setting","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Who should write it?"]},"283":{"title":"Adding or modifying a Language keyword, Standard Library function, or command line tool action","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Who should write it?"]},"284":{"title":"Adding a new feature that requires an overview.","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Who should write it?"]},"285":{"title":"A feature/function that is only used internally to the system","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Who should write it?"]},"286":{"title":"Extending the tests in the regression suite","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Who should write it?"]},"287":{"title":"Placement","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components"]},"288":{"title":"Other Folders","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Placement"]},"289":{"title":"Pull Requests","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Placement"]},"290":{"title":"Documentation Jira Issues","titles":["Contributing Documentation to the HPCC Systems Platform Project","Documenting a New Software Feature--Required and Optional Components","Placement"]},"291":{"title":"Feature Documentation Template","titles":[]},"292":{"title":"Overview","titles":["Feature Documentation Template"]},"293":{"title":"Setup & Configuration","titles":["Feature Documentation Template"]},"294":{"title":"User Guide","titles":["Feature Documentation Template"]},"295":{"title":"API/CLI/Parameter Reference","titles":["Feature Documentation Template"]},"296":{"title":"Tutorial","titles":["Feature Documentation Template"]},"297":{"title":"Troubleshooting","titles":["Feature Documentation Template"]},"298":{"title":"HPCC Writing Style Guide","titles":[]},"299":{"title":"General","titles":["HPCC Writing Style Guide"]},"300":{"title":"Terms","titles":["HPCC Writing Style Guide"]},"301":{"title":"HPCC Systems®","titles":["HPCC Writing Style Guide","Terms"]},"302":{"title":"Other Terms","titles":["HPCC Writing Style Guide","Terms"]},"303":{"title":"Common Documentation terms","titles":["HPCC Writing Style Guide","Terms"]},"304":{"title":"Usage Instructions","titles":["HPCC Writing Style Guide","Terms"]},"305":{"title":"Word Choices","titles":["HPCC Writing Style Guide","Terms"]},"306":{"title":"Write up vs Write-up","titles":["HPCC Writing Style Guide","Terms","Word Choices"]},"307":{"title":"Assure vs ensure vs insure","titles":["HPCC Writing Style Guide","Terms","Word Choices"]},"308":{"title":"Quantile 1 - What is it?","titles":[]},"309":{"title":"Quantile 2 - Test cases","titles":[]},"310":{"title":"Quantile 3 - The parser","titles":[]},"311":{"title":"Quantile 4 - The engine interface.","titles":[]},"312":{"title":"Quantile 5 - The code generator","titles":[]},"313":{"title":"Quantile 6 - Roxie","titles":[]},"314":{"title":"Quantile 7 - Possible roxie improvements","titles":[]},"315":{"title":"Quantile 8 - Thor","titles":[]},"316":{"title":"Everything you ever wanted to know about Roxie","titles":[]},"317":{"title":"Why did I create it?","titles":["Everything you ever wanted to know about Roxie"]},"318":{"title":"How do activities link together?","titles":["Everything you ever wanted to know about Roxie"]},"319":{"title":"Where are the Dragons?","titles":["Everything you ever wanted to know about Roxie"]},"320":{"title":"How does “I beat you to it” work?","titles":["Everything you ever wanted to know about Roxie"]},"321":{"title":"All about index compression","titles":["Everything you ever wanted to know about Roxie"]},"322":{"title":"What is the topology server for?","titles":["Everything you ever wanted to know about Roxie"]},"323":{"title":"Lazy File IO","titles":["Everything you ever wanted to know about Roxie"]},"324":{"title":"New IBYTI mode","titles":["Everything you ever wanted to know about Roxie"]},"325":{"title":"Testing Roxie code","titles":["Everything you ever wanted to know about Roxie"]},"326":{"title":"Cache prewarm","titles":["Everything you ever wanted to know about Roxie"]},"327":{"title":"Blacklisting sockets","titles":["Everything you ever wanted to know about Roxie"]},"328":{"title":"perftrace options","titles":[]},"329":{"title":"Some notes on LocalAgent mode","titles":[]},"330":{"title":"Some notes on UDP packet sending mechanism","titles":[]},"331":{"title":"User Documentation","titles":[]},"332":{"title":"Directory structure under devdoc","titles":["User Documentation"]},"333":{"title":"General documentation","titles":["User Documentation"]},"334":{"title":"HPCC Website documentation","titles":["User Documentation"]},"335":{"title":"Azure Portal FAQs","titles":[]},"336":{"title":"Copilot Prompt Tips","titles":[]},"337":{"title":"Generic Prompts","titles":["Copilot Prompt Tips"]},"338":{"title":"Specific Prompts","titles":["Copilot Prompt Tips"]},"339":{"title":"How to Avoid AI Hallucinations with Good Prompts","titles":["Copilot Prompt Tips"]},"340":{"title":"ROXIE FAQs","titles":[]},"341":{"title":"How to resolve issues installing the ECL IDE / Client tools","titles":[]},"342":{"title":"Problem","titles":["How to resolve issues installing the ECL IDE / Client tools"]},"343":{"title":"Ecl-bundle source documentation","titles":[]},"344":{"title":"Introduction","titles":["Ecl-bundle source documentation"]},"345":{"title":"Purpose","titles":["Ecl-bundle source documentation","Introduction"]},"346":{"title":"Design","titles":["Ecl-bundle source documentation","Introduction"]},"347":{"title":"Directory structure","titles":["Ecl-bundle source documentation","Introduction"]},"348":{"title":"Key classes","titles":["Ecl-bundle source documentation","Introduction"]},"349":{"title":"ECL standard library style guide","titles":[]},"350":{"title":"cpp-httplib","titles":[]},"351":{"title":"Server Example","titles":["cpp-httplib"]},"352":{"title":"Bind a socket to multiple interfaces and any available port","titles":["cpp-httplib","Server Example"]},"353":{"title":"Static File Server","titles":["cpp-httplib","Server Example"]},"354":{"title":"Logging","titles":["cpp-httplib","Server Example"]},"355":{"title":"Error handler","titles":["cpp-httplib","Server Example"]},"356":{"title":"\'multipart/form-data\' POST data","titles":["cpp-httplib","Server Example"]},"357":{"title":"Receive content with Content receiver","titles":["cpp-httplib","Server Example"]},"358":{"title":"Send content with Content provider","titles":["cpp-httplib","Server Example"]},"359":{"title":"Chunked transfer encoding","titles":["cpp-httplib","Server Example"]},"360":{"title":"\'Expect: 100-continue\' handler","titles":["cpp-httplib","Server Example"]},"361":{"title":"Keep-Alive connection","titles":["cpp-httplib","Server Example"]},"362":{"title":"Timeout","titles":["cpp-httplib","Server Example"]},"363":{"title":"Set maximum payload length for reading request body","titles":["cpp-httplib","Server Example"]},"364":{"title":"Server-Sent Events","titles":["cpp-httplib","Server Example"]},"365":{"title":"Default thread pool support","titles":["cpp-httplib","Server Example"]},"366":{"title":"Override the default thread pool with yours","titles":["cpp-httplib","Server Example"]},"367":{"title":"Client Example","titles":["cpp-httplib"]},"368":{"title":"GET with HTTP headers","titles":["cpp-httplib","Client Example"]},"369":{"title":"POST","titles":["cpp-httplib","Client Example"]},"370":{"title":"POST with parameters","titles":["cpp-httplib","Client Example"]},"371":{"title":"POST with Multipart Form Data","titles":["cpp-httplib","Client Example"]},"372":{"title":"PUT","titles":["cpp-httplib","Client Example"]},"373":{"title":"DELETE","titles":["cpp-httplib","Client Example"]},"374":{"title":"OPTIONS","titles":["cpp-httplib","Client Example"]},"375":{"title":"Timeout","titles":["cpp-httplib","Client Example"]},"376":{"title":"Receive content with Content receiver","titles":["cpp-httplib","Client Example"]},"377":{"title":"Send content with Content provider","titles":["cpp-httplib","Client Example"]},"378":{"title":"With Progress Callback","titles":["cpp-httplib","Client Example"]},"379":{"title":"Authentication","titles":["cpp-httplib","Client Example"]},"380":{"title":"Proxy server support","titles":["cpp-httplib","Client Example"]},"381":{"title":"Range","titles":["cpp-httplib","Client Example"]},"382":{"title":"Keep-Alive connection","titles":["cpp-httplib","Client Example"]},"383":{"title":"Redirect","titles":["cpp-httplib","Client Example"]},"384":{"title":"Use a specitic network interface","titles":["cpp-httplib","Client Example"]},"385":{"title":"OpenSSL Support","titles":["cpp-httplib"]},"386":{"title":"Compression","titles":["cpp-httplib"]},"387":{"title":"Zlib Support","titles":["cpp-httplib","Compression"]},"388":{"title":"Brotli Support","titles":["cpp-httplib","Compression"]},"389":{"title":"Compress request body on client","titles":["cpp-httplib","Compression"]},"390":{"title":"Compress response body on client","titles":["cpp-httplib","Compression"]},"391":{"title":"Split httplib.h into .h and .cc","titles":["cpp-httplib"]},"392":{"title":"NOTE","titles":["cpp-httplib"]},"393":{"title":"g++","titles":["cpp-httplib","NOTE"]},"394":{"title":"Windows","titles":["cpp-httplib","NOTE"]},"395":{"title":"License","titles":["cpp-httplib"]},"396":{"title":"Special Thanks To","titles":["cpp-httplib"]},"397":{"title":"Data Masking Framework","titles":[]},"398":{"title":"Glossary","titles":["Data Masking Framework"]},"399":{"title":"Domain","titles":["Data Masking Framework","Glossary"]},"400":{"title":"Masker","titles":["Data Masking Framework","Glossary"]},"401":{"title":"Profile","titles":["Data Masking Framework","Glossary"]},"402":{"title":"Context","titles":["Data Masking Framework","Glossary"]},"403":{"title":"Value Type","titles":["Data Masking Framework","Glossary"]},"404":{"title":"Mask Style","titles":["Data Masking Framework","Glossary"]},"405":{"title":"Rule","titles":["Data Masking Framework","Glossary"]},"406":{"title":"Plugin","titles":["Data Masking Framework","Glossary"]},"407":{"title":"Engine","titles":["Data Masking Framework","Glossary"]},"408":{"title":"Environment","titles":["Data Masking Framework"]},"409":{"title":"Value Type Sets","titles":["Data Masking Framework","Environment"]},"410":{"title":"Overview","titles":["Data Masking Framework","Environment","Value Type Sets"]},"411":{"title":"Runtime Compatibility","titles":["Data Masking Framework","Environment","Value Type Sets"]},"412":{"title":"Implementation","titles":["Data Masking Framework","Environment","Value Type Sets"]},"413":{"title":"Rule Sets","titles":["Data Masking Framework","Environment"]},"414":{"title":"Overview","titles":["Data Masking Framework","Environment","Rule Sets"]},"415":{"title":"Runtime Compatibility","titles":["Data Masking Framework","Environment","Rule Sets"]},"416":{"title":"Implementation","titles":["Data Masking Framework","Environment","Rule Sets"]},"417":{"title":"Operations","titles":["Data Masking Framework"]},"418":{"title":"maskValue","titles":["Data Masking Framework","Operations"]},"419":{"title":"Overview","titles":["Data Masking Framework","Operations","maskValue"]},"420":{"title":"Runtime Compatibility","titles":["Data Masking Framework","Operations","maskValue"]},"421":{"title":"Implementations","titles":["Data Masking Framework","Operations","maskValue"]},"422":{"title":"maskContent","titles":["Data Masking Framework","Operations"]},"423":{"title":"Overview","titles":["Data Masking Framework","Operations","maskContent"]},"424":{"title":"Runtime Compatibility","titles":["Data Masking Framework","Operations","maskContent"]},"425":{"title":"Implementations","titles":["Data Masking Framework","Operations","maskContent"]},"426":{"title":"maskMarkupValue","titles":["Data Masking Framework","Operations"]},"427":{"title":"Overview","titles":["Data Masking Framework","Operations","maskMarkupValue"]},"428":{"title":"Runtime Compatibility","titles":["Data Masking Framework","Operations","maskMarkupValue"]},"429":{"title":"Implementations","titles":["Data Masking Framework","Operations","maskMarkupValue"]},"430":{"title":"Load-time Compatibility","titles":["Data Masking Framework"]},"431":{"title":"Overview","titles":["Data Masking Framework","Load-time Compatibility"]},"432":{"title":"Implementation","titles":["Data Masking Framework","Load-time Compatibility"]},"433":{"title":"libdatamasker.so","titles":[]},"434":{"title":"namespace DataMasking","titles":["libdatamasker.so"]},"435":{"title":"CContext","titles":["libdatamasker.so","namespace DataMasking"]},"436":{"title":"CMaskStyle","titles":["libdatamasker.so","namespace DataMasking"]},"437":{"title":"CPartialMaskStyle","titles":["libdatamasker.so","namespace DataMasking"]},"438":{"title":"CRule","titles":["libdatamasker.so","namespace DataMasking"]},"439":{"title":"CSerialTokenRule","titles":["libdatamasker.so","namespace DataMasking"]},"440":{"title":"TPlugin","titles":["libdatamasker.so","namespace DataMasking"]},"441":{"title":"TProfile","titles":["libdatamasker.so","namespace DataMasking"]},"442":{"title":"TSerialProfile","titles":["libdatamasker.so","namespace DataMasking"]},"443":{"title":"TValueType","titles":["libdatamasker.so","namespace DataMasking"]},"444":{"title":"Entry Point Functions","titles":["libdatamasker.so"]},"445":{"title":"newPartialMaskSerialToken","titles":["libdatamasker.so","Entry Point Functions"]},"446":{"title":"JWT Authorization Security Manager Plugin","titles":[]},"447":{"title":"Code Documentation","titles":["JWT Authorization Security Manager Plugin"]},"448":{"title":"Theory of Operations","titles":["JWT Authorization Security Manager Plugin"]},"449":{"title":"Deviations From OIC Specification","titles":["JWT Authorization Security Manager Plugin"]},"450":{"title":"Implications of Deviations","titles":["JWT Authorization Security Manager Plugin"]},"451":{"title":"HPCC Systems Configuration Notes","titles":["JWT Authorization Security Manager Plugin"]},"452":{"title":"HPCC Systems Authorization and JWT Claims","titles":["JWT Authorization Security Manager Plugin"]},"453":{"title":"Workunit Scope Permissions","titles":["JWT Authorization Security Manager Plugin","HPCC Systems Authorization and JWT Claims"]},"454":{"title":"File Scope Permissions","titles":["JWT Authorization Security Manager Plugin","HPCC Systems Authorization and JWT Claims"]},"455":{"title":"Cleanup Parameter of Regression Suite run and query sub-command","titles":[]},"456":{"title":"Command:","titles":["Cleanup Parameter of Regression Suite run and query sub-command"]},"457":{"title":"Result:","titles":["Cleanup Parameter of Regression Suite run and query sub-command"]},"458":{"title":"The sample terminal output for hthor target:","titles":["Cleanup Parameter of Regression Suite run and query sub-command","Result:"]},"459":{"title":"Test Suite for the Parquet Plugin","titles":[]},"460":{"title":"Running the Test Suite","titles":["Test Suite for the Parquet Plugin"]},"461":{"title":"Project Description","titles":["Test Suite for the Parquet Plugin"]},"462":{"title":"Test Files","titles":[]},"463":{"title":"Test Suite Overview","titles":["Test Files"]},"464":{"title":"Type Testing","titles":["Test Files"]},"465":{"title":"Data Type Tests","titles":["Test Files","Type Testing"]},"466":{"title":"Arrow Types Supported by the Parquet Plugin","titles":["Test Files","Type Testing","Data Type Tests"]},"467":{"title":"Compression Testing","titles":[]},"468":{"title":"Parquet Read and Write Operations","titles":["Compression Testing"]},"469":{"title":"Additional Tests","titles":[]},"470":{"title":"Test Evaluation","titles":[]},"471":{"title":"esdl Utility","titles":[]},"472":{"title":"manifest","titles":["esdl Utility"]},"473":{"title":"Manifest File","titles":["esdl Utility","manifest"]},"474":{"title":"Example","titles":["esdl Utility","manifest"]},"475":{"title":"Syntax","titles":["esdl Utility","manifest"]},"476":{"title":"Manifest","titles":["esdl Utility","manifest","Syntax"]},"477":{"title":"ServiceBinding","titles":["esdl Utility","manifest","Syntax"]},"478":{"title":"Standard Attributes","titles":["esdl Utility","manifest","Syntax","ServiceBinding"]},"479":{"title":"Service-Specific Attributes","titles":["esdl Utility","manifest","Syntax","ServiceBinding"]},"480":{"title":"Auxillary Attributes","titles":["esdl Utility","manifest","Syntax","ServiceBinding"]},"481":{"title":"EsdlDefinition","titles":["esdl Utility","manifest","Syntax"]},"482":{"title":"Include","titles":["esdl Utility","manifest","Syntax"]},"483":{"title":"Scripts","titles":["esdl Utility","manifest","Syntax"]},"484":{"title":"Transform","titles":["esdl Utility","manifest","Syntax"]},"485":{"title":"Usage","titles":["esdl Utility","manifest"]},"486":{"title":"Output","titles":["esdl Utility","manifest"]},"487":{"title":"Bundle","titles":["esdl Utility","manifest","Output"]},"488":{"title":"Binding","titles":["esdl Utility","manifest","Output"]},"489":{"title":"Developer README for ESP API Command Line Tool","titles":[]},"490":{"title":"Overview","titles":["Developer README for ESP API Command Line Tool"]},"491":{"title":"Usage Notes","titles":["Developer README for ESP API Command Line Tool"]},"492":{"title":"Ideas for Expansion","titles":["Developer README for ESP API Command Line Tool"]},"493":{"title":"Tagging new versions","titles":[]},"494":{"title":"General","titles":["Tagging new versions"]},"495":{"title":"Pre-requisites","titles":["Tagging new versions"]},"496":{"title":"Tagging new versions","titles":["Tagging new versions"]},"497":{"title":"Taking a build gold:","titles":["Tagging new versions"]},"498":{"title":"Creating a new rc for an existing point release:","titles":["Tagging new versions"]},"499":{"title":"Create a new minor/major branch","titles":["Tagging new versions"]}},"dirtCount":0,"index":[["\\t\\t",{"2":{"449":4}}],["\\t",{"2":{"449":2,"452":1}}],["©",{"2":{"395":1}}],["`manage",{"2":{"335":2}}],["`apply`",{"2":{"335":1}}],["`admin`",{"2":{"335":1}}],["`add",{"2":{"335":1}}],["`add`",{"2":{"335":1}}],["`save",{"2":{"335":1}}],["`save`",{"2":{"335":2}}],["`select",{"2":{"335":1}}],["`value`",{"2":{"335":1}}],["`filter`",{"2":{"335":1}}],["`lower",{"2":{"335":1}}],["`resource",{"2":{"335":1}}],["`custom`",{"2":{"335":1}}],["`create`",{"2":{"335":1}}],["`dashboard`",{"2":{"335":1}}],["`hamburger",{"2":{"335":1}}],["®",{"2":{"301":2}}],[">body",{"2":{"367":1,"381":1,"390":1}}],[">status",{"2":{"367":1,"381":1,"383":2}}],[">size",{"2":{"358":1}}],[">setresultstring",{"2":{"266":2,"275":2}}],[">querycodecontext",{"2":{"275":1}}],[">append",{"2":{"269":2,"275":2}}],[">addrange",{"2":{"269":1,"275":1}}],[">getresultstring",{"2":{"269":1,"275":1}}],[">executegraph",{"2":{"266":1,"275":1}}],[">isresult",{"2":{"266":1,"275":1}}],[">inc",{"2":{"187":1}}],[">compile",{"2":{"262":1,"274":1}}],[">",{"2":{"240":2,"254":4,"256":7,"260":2,"261":4,"262":1,"266":4,"269":18,"274":32,"355":1,"366":1,"391":1,"474":11,"486":20,"487":43,"488":29}}],[">dosomething",{"2":{"233":1}}],["$transactionid",{"2":{"486":1,"487":1,"488":1}}],["$logcontent",{"2":{"486":2,"487":2,"488":2}}],["$query",{"2":{"486":4,"487":4,"488":4}}],["$",{"2":{"479":6}}],["$bundledir",{"2":{"347":2}}],["$cat",{"2":{"134":1}}],["$hpcc",{"2":{"134":1}}],["zip",{"2":{"345":1}}],["zstd",{"2":{"321":1,"460":2,"467":1}}],["zones",{"2":{"196":2}}],["zone",{"2":{"196":3,"254":1}}],["zero",{"2":{"90":1,"313":1,"423":1}}],["z",{"2":{"64":2,"299":3,"347":3}}],["zlib",{"0":{"387":1},"2":{"12":1,"13":3,"387":1}}],["zlib1g",{"2":{"3":1,"4":1}}],["=>",{"2":{"378":2}}],["=1",{"2":{"340":4}}],["==",{"2":{"269":1,"275":1,"327":1,"367":1}}],["=0",{"2":{"233":4}}],["=",{"2":{"64":7,"68":2,"70":6,"73":1,"75":8,"76":2,"187":4,"188":2,"193":3,"196":2,"220":2,"234":3,"240":17,"241":1,"252":4,"266":1,"269":3,"270":1,"274":1,"275":4,"309":1,"325":2,"327":1,"349":4,"351":3,"352":1,"353":6,"355":1,"356":3,"358":3,"360":1,"366":1,"367":2,"368":3,"369":2,"370":2,"371":2,"372":1,"373":1,"374":2,"376":2,"377":2,"378":1,"381":1,"383":2,"389":1,"390":1,"428":1}}],["95",{"2":{"309":1}}],["95th",{"2":{"170":1,"308":1}}],["9",{"2":{"244":4,"333":1,"460":4}}],["999",{"2":{"56":1,"310":1}}],["9m",{"2":{"26":2}}],["52",{"2":{"460":2}}],["599",{"2":{"381":2}}],["512mb",{"2":{"363":1}}],["512",{"2":{"363":1}}],["5173",{"2":{"114":1,"115":1}}],["5th",{"2":{"308":1}}],["5u",{"2":{"266":2,"275":2}}],["50",{"2":{"193":3,"349":1,"378":1,"497":1}}],["50way",{"2":{"193":2}}],["5000",{"2":{"325":1}}],["500",{"2":{"56":1,"58":1,"381":2}}],["5",{"0":{"312":1},"2":{"163":1,"204":6,"270":1,"274":2,"308":1,"309":1,"339":1,"361":1,"362":4,"375":4,"460":3}}],["5m",{"2":{"26":1}}],["q",{"2":{"26":2,"53":1,"56":1,"333":1}}],["queuemanager",{"2":{"329":1}}],["queued",{"2":{"185":1}}],["queue",{"2":{"169":1,"170":1,"179":1,"264":8,"265":1,"266":1,"324":1,"329":2,"330":5,"365":1,"366":1}}],["queues",{"0":{"265":1},"2":{"169":1,"170":1,"265":4,"330":4}}],["questions",{"2":{"192":1,"279":2,"307":1,"326":1,"327":1}}],["question",{"2":{"106":3,"192":1,"308":1,"320":1,"322":1}}],["queries",{"2":{"32":1,"48":2,"50":1,"53":1,"58":1,"61":1,"64":1,"75":1,"79":1,"83":1,"99":1,"145":3,"164":1,"166":1,"170":3,"245":2,"249":3,"254":1,"260":1,"264":2,"270":1,"272":1,"317":4,"318":2,"327":4,"328":1,"329":1,"330":1,"340":6,"456":1,"458":1,"460":1}}],["querymaskstyle",{"2":{"420":1}}],["querymeta",{"2":{"195":1}}],["querymetricsmanager",{"2":{"187":1}}],["querycompare",{"2":{"311":1}}],["querychild",{"2":{"59":1}}],["query>",{"2":{"274":1}}],["querydiskrecordsize",{"2":{"269":1,"275":1}}],["querydatasetcursor",{"2":{"65":1}}],["querydataset",{"2":{"62":1}}],["queryfoo",{"2":{"221":1}}],["queryoutputmeta",{"2":{"269":1,"275":2}}],["queryoptions",{"2":{"195":1}}],["queryoriginalrecord",{"2":{"69":1}}],["queryannotation",{"2":{"67":1}}],["queryattribute",{"2":{"63":1,"69":1}}],["queryset>",{"2":{"340":1}}],["queryselectordataset",{"2":{"65":1}}],["queryslaverowmanager",{"2":{"164":1}}],["queryscope",{"2":{"61":1}}],["querynormalizedselector",{"2":{"64":1,"66":1}}],["querytable",{"2":{"62":1}}],["querytypeinfo",{"2":{"270":2,"275":1}}],["querytype",{"2":{"59":1}}],["queryvaluetype",{"2":{"420":1}}],["queryvalue",{"2":{"59":1}}],["queryproperty",{"2":{"59":1,"408":1}}],["querybody",{"2":{"59":1,"70":1}}],["query",{"0":{"79":1,"455":1},"1":{"456":1,"457":1,"458":1},"2":{"29":3,"33":1,"34":1,"47":1,"50":3,"56":2,"58":1,"59":1,"64":1,"68":1,"70":1,"79":2,"92":3,"133":1,"145":4,"148":2,"157":1,"192":4,"199":2,"245":1,"249":12,"250":2,"251":2,"254":2,"256":1,"261":2,"262":2,"264":15,"266":1,"267":2,"268":2,"270":3,"271":1,"272":2,"274":1,"309":2,"310":1,"311":1,"313":1,"317":1,"318":7,"319":1,"324":1,"326":1,"327":7,"328":3,"329":2,"340":4,"455":2,"456":1,"458":2,"460":4,"490":1}}],["quantiles",{"2":{"308":1,"309":1,"310":1}}],["quantile",{"0":{"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1,"315":1},"2":{"308":13,"309":2,"310":7,"311":3,"312":3,"313":4,"314":1}}],["quantifiable",{"2":{"172":1}}],["qualifies",{"2":{"186":1}}],["qualified",{"2":{"85":1,"476":1}}],["qualify",{"2":{"184":1,"186":1,"188":1}}],["quad",{"2":{"20":1}}],["quite",{"2":{"59":1,"62":1,"325":1}}],["quicksort",{"2":{"313":1,"337":1}}],["quick",{"2":{"211":1,"251":1}}],["quickest",{"2":{"56":1}}],["quicker",{"2":{"54":1}}],["quickly",{"2":{"17":1,"29":1,"48":1,"56":1,"58":1,"103":1,"108":1,"155":1,"238":1,"308":1,"313":1,"321":1,"331":1}}],["quotes",{"2":{"439":1}}],["quote",{"2":{"439":1}}],["quot",{"0":{"65":2},"2":{"44":4,"53":2,"56":2,"59":2,"62":4,"63":2,"64":4,"65":4,"73":2,"74":2,"81":2,"84":2,"90":2,"91":4,"103":2,"104":2,"106":2,"108":2,"109":2,"112":2,"117":2,"144":2,"146":2,"148":2,"149":1,"160":2,"169":2,"170":2,"178":2,"181":2,"187":8,"234":2,"240":6,"241":2,"245":12,"247":4,"249":14,"250":2,"256":2,"260":2,"261":2,"266":6,"269":60,"270":10,"299":10,"308":4,"310":8,"313":2,"326":8,"327":14,"328":6,"329":4,"339":14,"342":2,"347":6,"397":2,"408":4,"410":2,"414":2,"419":6,"420":4,"421":24,"432":2,"451":22,"452":2,"470":2,"483":6,"486":8}}],["+d",{"2":{"325":1}}],["+",{"0":{"26":1},"2":{"70":2,"75":1,"193":2,"194":1,"196":1,"269":2,"275":2,"377":1}}],["kbectl",{"2":{"340":1}}],["kicked",{"2":{"242":1}}],["kind=",{"2":{"262":1,"274":1}}],["kinds",{"2":{"110":1}}],["kind",{"2":{"63":1,"64":1,"81":2,"88":2,"90":1,"106":1,"133":1,"257":1,"269":6,"270":2,"274":2,"310":2,"311":1,"313":1,"448":1}}],["kr",{"2":{"204":3}}],["k8s",{"2":{"196":1,"326":1}}],["k",{"2":{"194":2,"196":1}}],["kubectl",{"2":{"133":1,"340":3}}],["kubernetes",{"0":{"133":1},"2":{"133":2,"140":2,"202":1,"204":2,"247":2}}],["kernel",{"2":{"163":1}}],["kernels",{"2":{"163":1}}],["kept",{"2":{"83":2}}],["keeping",{"2":{"131":1,"326":1}}],["keeps",{"2":{"85":1,"144":1,"187":1}}],["keep",{"0":{"361":1,"382":1},"2":{"63":1,"85":1,"90":1,"108":1,"155":1,"169":1,"170":1,"171":1,"204":1,"266":1,"280":1,"287":1,"299":3,"310":1,"319":2,"320":1,"361":1,"382":2,"437":2}}],["keyed",{"2":{"252":1,"269":1,"274":1,"321":4}}],["keyencipherment",{"2":{"240":1}}],["keyusage",{"2":{"240":1}}],["keyout",{"2":{"240":1}}],["keygen",{"2":{"239":1}}],["keyword",{"0":{"283":1},"2":{"224":2,"283":1,"290":1,"310":1}}],["keywords",{"2":{"85":1,"219":1,"309":1,"349":1}}],["key1",{"2":{"188":2}}],["keysize",{"2":{"241":1}}],["keys",{"2":{"95":1,"132":2,"192":1,"196":2,"321":1}}],["keyid",{"2":{"18":1,"239":1}}],["keychain",{"2":{"18":1,"241":1}}],["key",{"0":{"49":1,"71":1,"95":1,"241":1,"272":1,"348":1},"1":{"72":1,"73":1,"74":1,"75":1,"76":1,"77":1},"2":{"18":1,"33":1,"50":1,"58":2,"70":2,"90":1,"104":1,"106":1,"131":3,"133":1,"183":1,"186":1,"188":1,"191":1,"192":1,"204":1,"239":3,"240":7,"241":10,"249":1,"250":1,"269":1,"270":1,"308":1,"310":1,"321":2,"337":5,"339":1,"351":2,"385":1,"448":4,"451":2,"461":1,"470":3}}],["knows",{"2":{"330":1,"427":1}}],["known",{"2":{"128":1,"169":1,"181":1,"191":1,"192":1,"217":1,"222":1,"313":1,"318":1,"330":1,"419":2,"421":1,"441":2,"448":2}}],["knowledge",{"2":{"106":1,"270":1,"339":1,"437":1,"438":1,"439":1}}],["know",{"0":{"316":1},"1":{"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1},"2":{"21":1,"69":3,"106":2,"162":1,"187":1,"192":1,"308":1,"310":2,"319":1,"322":1,"408":1,"410":1,"432":2,"435":1,"439":1}}],["kafka",{"2":{"18":1}}],["~=",{"2":{"311":1}}],["~",{"2":{"18":4,"23":1,"53":1,"133":1,"340":1,"495":1}}],["3b",{"2":{"421":1}}],["3b12a587b",{"2":{"340":1}}],["3a",{"2":{"421":1}}],["32",{"2":{"381":1}}],["30",{"2":{"448":1}}],["301",{"2":{"383":1}}],["300",{"2":{"375":1}}],["300000",{"2":{"375":1}}],["30da006df9ae01c9aa784e91129457883e9bb8f3",{"2":{"313":1}}],["345",{"2":{"359":1}}],["3d623d1c6cd151a0a5608aa20ae4739a008f6e44",{"2":{"310":1}}],["365",{"2":{"240":2}}],["389directoryserver",{"2":{"204":1}}],["389",{"2":{"204":1}}],["38",{"2":{"163":1}}],["3m",{"2":{"26":12}}],["3month",{"2":{"26":1}}],["3rd",{"2":{"23":1}}],["3",{"0":{"310":1},"2":{"16":2,"18":2,"26":1,"75":3,"123":1,"128":1,"204":1,"245":1,"256":3,"257":1,"267":1,"269":6,"270":1,"274":5,"328":1,"335":1,"359":3,"458":4,"460":6}}],["|",{"2":{"15":1,"485":1}}],["mtu",{"2":{"330":1}}],["msg",{"2":{"318":1}}],["mx1",{"2":{"269":2,"270":2,"275":7}}],["mf1",{"2":{"261":1,"274":1,"275":1}}],["m",{"2":{"194":1,"327":2}}],["md",{"2":{"115":2,"116":1,"117":1,"221":1,"240":1,"277":3,"284":1,"285":1}}],["myhpcccluster",{"2":{"449":1}}],["mybundle",{"2":{"347":5}}],["mynamespace",{"2":{"340":1}}],["mynewdocument",{"2":{"115":2,"116":2}}],["myself",{"2":{"318":1}}],["mysqlfunctioniscool",{"2":{"220":1}}],["mysqlsuperclass",{"2":{"220":1}}],["mysqlembed",{"2":{"18":1}}],["myeclprocess",{"2":{"255":2,"266":1,"275":2}}],["myeclccserver",{"2":{"133":1}}],["myprocess",{"0":{"255":1},"2":{"266":1}}],["myfoo",{"2":{"234":2}}],["myfield",{"2":{"73":1}}],["myfile",{"2":{"56":4,"64":3}}],["myimpl",{"2":{"233":1}}],["myip",{"2":{"192":1}}],["mydataset",{"2":{"65":1}}],["my",{"2":{"53":1,"116":1,"128":3,"133":3,"194":1,"277":1,"340":1,"349":1,"449":2}}],["mb",{"2":{"48":1}}],["much",{"2":{"54":1,"73":1,"84":1,"99":1,"104":1,"106":1,"108":2,"114":1,"145":1,"148":1,"157":1,"162":1,"196":1,"204":1,"247":1,"310":1,"321":2,"326":1}}],["must",{"2":{"43":1,"45":1,"59":1,"70":1,"91":1,"128":1,"140":1,"169":2,"187":4,"204":3,"214":1,"233":1,"234":1,"242":1,"266":1,"267":1,"270":1,"278":1,"319":1,"349":1,"401":1,"410":1,"419":1,"427":3,"431":1,"432":2,"439":1,"441":2,"442":1,"443":2,"444":1,"451":3,"474":1,"476":1,"482":1}}],["multicast",{"2":{"320":2,"330":3}}],["multipartformdata",{"2":{"357":1}}],["multipartformdataitems",{"2":{"357":1,"371":1}}],["multipart",{"0":{"356":1,"371":1},"2":{"356":1,"357":1,"371":1}}],["multipe",{"2":{"271":1}}],["multiple",{"0":{"44":1,"352":1},"2":{"53":1,"56":1,"73":1,"83":1,"93":1,"106":1,"109":2,"139":1,"140":1,"147":1,"153":1,"154":1,"155":1,"162":1,"164":1,"166":1,"175":2,"190":1,"193":1,"194":1,"196":2,"211":1,"233":1,"268":1,"271":2,"320":1,"322":1,"327":3,"329":1,"330":1,"399":1,"402":1,"404":1,"405":1,"407":1,"408":1,"420":1,"425":2,"441":1,"443":1,"444":1,"452":1,"496":1}}],["multi",{"2":{"29":1,"158":1,"302":1}}],["mm",{"2":{"26":1}}],["mount",{"2":{"353":11}}],["mounted",{"2":{"326":1}}],["mounts",{"2":{"190":1}}],["moxie",{"2":{"317":1}}],["moment",{"2":{"310":1,"330":1}}],["moving",{"2":{"76":3}}],["movement",{"2":{"217":1}}],["moves",{"2":{"144":1}}],["moved",{"2":{"70":1,"161":2}}],["move",{"2":{"69":1,"196":3,"310":1}}],["most",{"2":{"67":1,"70":1,"75":1,"86":1,"92":1,"119":1,"128":1,"169":1,"196":1,"216":1,"217":6,"219":1,"221":2,"233":1,"234":1,"238":1,"245":1,"251":1,"256":1,"264":1,"266":3,"272":1,"301":3,"310":1,"312":1,"313":1,"326":2,"327":1,"330":1,"336":1,"348":1,"397":1,"428":1,"432":2,"437":1,"446":1,"450":1,"474":1,"479":1}}],["modifying",{"0":{"283":1}}],["modify",{"2":{"83":1,"260":1,"313":1,"448":1,"453":2,"454":2,"494":1}}],["modifies",{"2":{"66":1}}],["modified",{"2":{"49":2,"58":2,"70":2,"108":1,"313":1,"451":1}}],["modifications",{"2":{"45":1}}],["modes",{"2":{"456":1}}],["mode",{"0":{"324":1,"329":1},"2":{"267":1,"325":1,"328":1,"329":5,"346":1,"423":1,"427":1,"456":2,"473":1,"487":1}}],["mode=",{"2":{"266":2,"274":2}}],["model",{"2":{"58":1,"172":1,"203":1,"219":1}}],["modern",{"2":{"31":1,"123":1,"238":1}}],["module",{"2":{"72":3,"203":1,"255":1,"271":1,"346":3}}],["modulesbasedn",{"2":{"204":3}}],["modules=on",{"2":{"18":1}}],["modules",{"2":{"18":2,"23":1,"36":2,"53":1,"58":1,"61":1,"85":1,"239":2}}],["modular",{"2":{"30":1}}],["morepeopletoavoid",{"2":{"349":1}}],["more",{"2":{"27":1,"45":1,"50":2,"56":2,"58":2,"59":1,"63":1,"64":1,"65":1,"70":1,"75":1,"84":1,"87":1,"90":1,"91":1,"92":2,"99":1,"104":2,"105":1,"108":1,"110":1,"113":1,"116":1,"127":1,"131":2,"135":1,"145":1,"148":1,"150":1,"155":1,"156":1,"157":2,"159":1,"160":1,"162":1,"163":1,"182":1,"190":1,"192":3,"196":1,"197":1,"209":1,"219":1,"221":1,"225":1,"233":1,"235":1,"236":2,"249":1,"253":1,"254":1,"263":1,"265":1,"269":2,"270":3,"271":1,"279":2,"285":1,"287":1,"299":1,"308":5,"311":2,"312":2,"313":1,"317":1,"318":1,"321":2,"326":1,"327":2,"329":1,"331":1,"338":1,"339":2,"358":1,"359":1,"388":1,"401":3,"403":1,"406":1,"407":1,"416":1,"423":1,"425":1,"432":3,"436":1,"446":1,"471":1,"482":1}}],["monolithic",{"2":{"317":1}}],["monotonically",{"2":{"178":1}}],["monitors",{"2":{"251":1}}],["monitor",{"2":{"31":1,"166":1,"170":1,"471":3}}],["monitoring",{"2":{"27":1,"166":4,"196":1,"266":1,"471":1,"495":2}}],["months",{"2":{"26":1,"245":1,"466":1}}],["me",{"2":{"346":1}}],["media",{"2":{"339":1}}],["median",{"2":{"308":1}}],["messy",{"2":{"329":1}}],["messaging",{"2":{"325":1,"431":2}}],["messages",{"2":{"108":1,"245":1,"320":1,"330":2,"431":1,"485":1}}],["message",{"2":{"106":1,"108":3,"330":3,"360":1}}],["mental",{"2":{"339":1}}],["mention",{"2":{"286":1,"301":2}}],["mentioned",{"2":{"99":1,"170":2,"270":1,"313":1}}],["menu",{"2":{"299":2}}],["meeting",{"2":{"181":1}}],["meet",{"2":{"174":1}}],["measuring",{"2":{"170":1}}],["measured",{"2":{"170":1,"174":1,"179":1}}],["measure",{"2":{"170":1,"174":1,"187":1,"461":1}}],["measurements",{"2":{"172":2,"175":2,"176":1,"181":5,"182":2}}],["measurement",{"2":{"167":1,"170":1,"174":1,"175":1,"181":2,"182":1,"189":1}}],["meant",{"2":{"249":1,"330":1,"338":1,"416":1}}],["mean",{"2":{"85":1,"161":1,"201":1,"290":1,"394":2}}],["means",{"2":{"66":3,"67":1,"72":2,"85":2,"103":1,"149":1,"159":1,"234":1,"245":1,"266":1,"267":2,"268":1,"323":1,"346":1,"419":2}}],["meanings",{"2":{"201":1,"400":1}}],["meaning",{"2":{"65":1,"67":1,"70":1,"74":1,"170":1,"204":1,"453":1,"454":1}}],["mechanical",{"2":{"108":1}}],["mechanism",{"0":{"330":1},"2":{"69":1,"148":1,"162":1,"272":1,"311":2,"330":1,"410":1,"414":1}}],["mechanisms",{"2":{"49":1,"58":1,"69":1}}],["merging",{"2":{"107":1,"109":3,"195":1,"245":1,"495":1,"496":1}}],["merged",{"2":{"289":1,"309":1,"497":1,"498":1}}],["merge",{"2":{"106":2,"109":1,"494":1,"496":1}}],["mermaid",{"2":{"26":1}}],["metricmetadata",{"2":{"188":1}}],["metricbase",{"2":{"188":1}}],["metrictype",{"2":{"187":3}}],["metricname",{"2":{"187":2}}],["metric",{"0":{"178":1,"179":1,"180":1,"181":1,"182":1,"184":1,"188":1,"189":1},"1":{"185":1,"186":1},"2":{"166":4,"167":8,"170":3,"172":7,"174":6,"177":1,"178":1,"179":3,"180":7,"181":2,"182":1,"184":5,"185":3,"186":4,"187":20,"188":3,"189":2}}],["metricsmanager",{"2":{"172":1,"187":3}}],["metrics",{"0":{"165":1,"174":1,"176":1,"177":1},"1":{"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":2,"179":2,"180":2,"181":2,"182":2,"183":1,"184":1,"185":1,"186":1,"187":1,"188":1,"189":1},"2":{"166":4,"167":1,"169":5,"170":9,"172":10,"174":10,"175":1,"176":3,"180":1,"183":7,"184":1,"186":4,"187":7,"188":1,"199":2}}],["metacrc",{"2":{"191":1}}],["metal",{"0":{"134":1},"2":{"134":1,"190":2,"196":13,"247":1,"282":1,"303":1,"324":2,"326":2,"330":1,"340":2}}],["metadata1",{"2":{"188":2}}],["metadata",{"2":{"133":1,"271":1,"346":1}}],["meta",{"0":{"89":1,"186":1,"188":1},"2":{"87":1,"184":2,"186":8,"188":7,"189":1,"191":3,"192":4,"194":2,"195":5,"196":4,"249":3,"269":1,"270":1,"272":1}}],["method=",{"2":{"486":1,"487":1,"488":1}}],["method>",{"2":{"474":1,"487":1,"488":1}}],["methods>",{"2":{"474":2,"487":2,"488":2}}],["methods",{"2":{"67":1,"87":1,"122":1,"130":1,"172":3,"174":2,"179":1,"180":1,"202":1,"210":1,"214":1,"219":1,"220":1,"221":1,"233":1,"234":1,"250":1,"269":1,"295":1,"313":3,"329":1,"340":1,"351":1,"353":1,"420":2,"470":1,"479":1,"490":4}}],["method",{"2":{"21":1,"80":1,"81":1,"108":2,"164":1,"170":1,"174":1,"188":1,"189":1,"209":3,"210":3,"214":2,"220":1,"269":1,"270":1,"313":1,"340":1,"420":2,"424":2,"428":2,"471":4,"474":1,"479":5,"487":1,"488":1}}],["megabytes",{"2":{"48":1}}],["meminfo",{"2":{"326":1}}],["memcpy",{"2":{"269":1,"275":1}}],["memcached",{"2":{"18":1}}],["memberof",{"2":{"412":8,"416":3,"421":2,"438":2,"443":2}}],["memberships",{"2":{"412":1,"416":1}}],["membership",{"2":{"410":2,"412":2,"414":2,"416":2,"432":4,"438":1,"443":2}}],["members",{"2":{"61":2,"69":1,"83":1,"226":1,"233":1,"234":1,"270":1,"312":1,"412":3,"416":1}}],["member",{"2":{"59":1,"65":1,"220":1,"221":1,"311":2,"312":4,"313":1}}],["memorybuffers",{"2":{"329":1}}],["memorybuffer",{"2":{"269":1,"275":1}}],["memory",{"0":{"160":1,"162":1,"164":1},"2":{"33":1,"58":6,"87":1,"93":2,"105":1,"145":9,"146":4,"147":2,"148":3,"149":8,"150":2,"152":1,"153":1,"157":7,"158":2,"159":4,"160":6,"161":3,"162":5,"163":10,"164":3,"169":2,"179":1,"199":2,"270":1,"272":1,"308":1,"312":1,"326":6,"329":1,"400":1}}],["mkdir",{"2":{"18":1}}],["mit",{"2":{"395":1}}],["mitigating",{"2":{"339":1}}],["mitigate",{"2":{"145":1,"329":1}}],["mimetype",{"2":{"353":3}}],["mime",{"2":{"353":2,"386":1}}],["mi1",{"2":{"270":2,"275":2}}],["milestones",{"0":{"194":1},"2":{"196":1,"309":1}}],["milliseconds",{"2":{"362":1,"375":1}}],["millisecond",{"2":{"182":1}}],["midway",{"2":{"319":1}}],["middle",{"2":{"309":1}}],["middleware",{"2":{"27":1}}],["midstream",{"2":{"192":1}}],["mix",{"2":{"327":1}}],["mixed",{"2":{"105":1,"479":2}}],["mixture",{"2":{"104":1,"216":1}}],["misleading",{"2":{"339":1}}],["mistake",{"2":{"330":1}}],["mistakes",{"2":{"105":1,"108":2}}],["missed",{"2":{"310":1,"319":1}}],["misses",{"2":{"178":1}}],["miss",{"2":{"103":1,"310":1}}],["missing",{"2":{"22":1,"106":1,"191":1,"245":2,"309":1}}],["migrate",{"2":{"196":1}}],["migrated",{"2":{"68":1,"425":1}}],["might",{"2":{"58":1,"73":1,"81":1,"88":1,"90":1,"91":3,"149":1,"196":2,"221":1,"269":1,"278":1,"282":1,"285":2,"289":1,"308":3,"310":1,"313":1,"318":1,"326":1,"327":1,"328":1,"329":1,"400":4,"402":1,"412":1,"427":2,"431":3,"435":1,"439":1}}],["minus",{"2":{"483":1,"484":1}}],["minumum",{"2":{"436":1,"443":1}}],["minute",{"2":{"326":1}}],["minutes",{"2":{"204":1,"448":1}}],["minimise",{"2":{"270":1}}],["minimize",{"2":{"148":1,"268":1,"339":1}}],["minimal",{"2":{"83":1,"145":1,"308":1}}],["minimumversion",{"2":{"436":1,"441":5,"443":4}}],["minimum",{"2":{"18":1,"83":1,"195":1,"323":1,"401":1,"441":2}}],["mind",{"2":{"70":2,"105":1,"192":1}}],["min",{"2":{"45":1,"358":1}}],["minor",{"0":{"499":1},"2":{"26":1,"106":4,"110":1,"245":2,"290":1,"499":1}}],["microsoft",{"2":{"13":1}}],["masks",{"2":{"441":1}}],["maskstyle",{"2":{"418":3,"419":1,"432":1,"443":4}}],["mask",{"0":{"404":1},"2":{"402":1,"403":1,"404":4,"419":2,"420":3,"425":1,"427":1,"428":2,"431":2,"432":1,"436":1,"437":3,"438":1,"441":2,"443":3,"445":1}}],["maskmarkupvalue",{"0":{"426":1},"1":{"427":1,"428":1,"429":1},"2":{"400":1,"405":1,"426":1,"427":1,"428":1,"431":1,"432":1}}],["maskcontent",{"0":{"422":1},"1":{"423":1,"424":1,"425":1},"2":{"400":1,"402":1,"405":1,"422":1,"427":1,"431":1,"432":3,"438":2,"442":2,"445":1}}],["maskvalueunconditionally",{"2":{"418":1}}],["maskvalueconditionally",{"2":{"418":1}}],["maskvalue",{"0":{"418":1},"1":{"419":1,"420":1,"421":1},"2":{"400":1,"405":1,"418":1,"423":1,"427":3,"428":3,"431":1,"432":2,"441":3,"445":1}}],["maskers",{"2":{"400":1}}],["masker",{"0":{"400":1},"2":{"400":4,"401":2,"402":1,"407":1,"408":1}}],["masked",{"2":{"149":1,"402":1,"403":1,"436":1,"437":2,"439":4,"441":2}}],["masking",{"0":{"397":1},"1":{"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"431":1,"432":1},"2":{"397":2,"400":1,"402":1,"403":1,"411":2,"431":1,"438":1,"439":1,"441":3,"443":1}}],["masterfilecollection",{"2":{"192":2}}],["master",{"2":{"38":1,"53":1,"110":1,"128":2,"148":1,"192":2,"245":1,"496":1,"499":1}}],["maxlength",{"2":{"349":2}}],["max",{"2":{"143":3,"249":1,"273":1,"326":1,"361":1,"363":1}}],["maxconnections",{"2":{"143":1}}],["maximumversion",{"2":{"436":1,"441":4,"443":3}}],["maximum",{"0":{"363":1},"2":{"69":1,"181":1,"269":2,"308":1,"323":1,"326":1,"349":1,"401":1,"436":1,"441":2,"443":1}}],["material",{"2":{"480":1}}],["matter",{"2":{"308":3,"322":1}}],["matters",{"2":{"58":1,"278":1}}],["matrix",{"2":{"238":3}}],["match=",{"2":{"486":1,"487":1,"488":1}}],["matchcase",{"2":{"439":1}}],["matching",{"2":{"252":1,"308":1,"310":1,"321":1,"349":2,"421":1,"423":1,"438":2,"439":1,"441":1,"452":1}}],["matched",{"2":{"87":1}}],["matches",{"2":{"59":2,"70":1,"102":1,"133":1,"192":1,"194":1,"212":1,"219":1,"308":1,"310":2,"351":1,"439":2,"448":1}}],["match",{"2":{"59":1,"104":1,"105":1,"106":2,"141":1,"217":1,"269":2,"299":1,"308":3,"327":1,"328":1,"428":1,"438":1,"439":2,"478":1,"480":1,"494":1}}],["making",{"2":{"53":1,"103":1,"267":1,"347":1}}],["makefile",{"2":{"123":2,"129":1}}],["makefiles",{"2":{"18":2,"23":1}}],["makes",{"2":{"84":1,"103":1,"104":1,"106":4,"108":1,"112":1,"216":1,"217":1,"219":1,"245":1,"287":1,"327":1}}],["make",{"2":{"5":2,"7":1,"8":1,"10":1,"11":1,"13":1,"18":1,"20":4,"21":1,"23":3,"69":1,"103":2,"106":2,"108":1,"114":1,"124":4,"128":1,"129":2,"187":2,"188":2,"217":2,"219":1,"232":1,"247":1,"307":2,"310":2,"313":1,"321":1,"326":2,"327":2,"337":3,"347":2,"349":1,"381":4,"427":1,"428":1,"441":1,"451":1,"473":1,"492":1}}],["mainly",{"2":{"108":1,"124":1,"324":1}}],["maintaining",{"2":{"187":1,"330":1,"446":1}}],["maintain",{"2":{"143":1,"187":1,"245":1,"280":2,"299":1,"300":1,"327":1,"473":1,"474":1}}],["maintained",{"2":{"128":1,"321":1,"327":1,"349":1,"486":1}}],["maintainable",{"2":{"102":1}}],["maintains",{"2":{"87":1,"143":1,"180":1,"181":1,"326":1,"330":3}}],["maintenance",{"2":{"26":2}}],["main",{"0":{"117":1,"146":1,"253":1},"1":{"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"154":1,"155":1,"156":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"262":1,"263":1},"2":{"50":1,"58":1,"59":1,"64":1,"96":1,"99":1,"108":1,"117":1,"130":2,"135":1,"137":1,"162":1,"172":1,"175":1,"195":1,"196":3,"254":1,"255":2,"264":1,"268":1,"271":1,"313":2,"351":1,"367":1,"462":1,"474":1,"487":1,"488":1}}],["mapped",{"2":{"190":1,"264":1}}],["mappings",{"2":{"163":1,"353":2}}],["mapping",{"2":{"163":1,"190":1,"196":1,"326":1,"353":3}}],["map",{"2":{"48":1,"60":1,"73":1,"196":4,"302":1,"448":1}}],["marginal",{"2":{"313":1}}],["markup",{"2":{"427":1,"438":2,"439":1,"473":1,"474":4,"476":1,"482":3}}],["marking",{"2":{"156":1}}],["marks",{"2":{"144":1}}],["markdown",{"2":{"113":3,"114":1,"115":1,"197":2,"277":1}}],["marked",{"2":{"58":1,"74":1,"141":1,"143":1,"155":2,"211":1,"264":1,"266":1,"267":1,"270":2,"313":1,"329":1}}],["mark",{"2":{"44":1,"45":1,"103":1,"149":1,"266":1,"301":1}}],["mariadb",{"2":{"7":1}}],["mandatory",{"2":{"432":2}}],["manipulate",{"2":{"345":1,"407":1}}],["manipulation",{"2":{"30":1,"221":1}}],["manifest>",{"2":{"474":1}}],["manifests",{"0":{"263":1}}],["manifest",{"0":{"472":1,"473":1,"476":1},"1":{"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1},"2":{"250":1,"471":2,"472":3,"473":7,"474":7,"476":8,"477":3,"480":3,"481":3,"482":1,"483":1,"484":1,"485":5,"486":5}}],["manual",{"2":{"131":1,"200":1,"451":2}}],["manually",{"2":{"124":1,"204":2,"211":1}}],["manner",{"2":{"103":1,"308":1}}],["managing",{"2":{"96":1,"176":1,"471":1}}],["managed",{"2":{"204":1,"254":1,"402":1,"408":1,"441":1}}],["manages",{"2":{"171":1,"172":2,"435":1,"441":1,"443":1}}],["manage",{"2":{"150":1,"174":2,"471":1}}],["managers",{"0":{"203":1,"205":1},"1":{"204":1,"205":1,"206":2,"207":2,"208":2},"2":{"164":1,"203":2,"205":1}}],["manager",{"0":{"135":1,"206":1,"207":1,"208":1,"210":1,"446":1},"1":{"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"144":1,"211":1,"212":1,"213":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1},"2":{"33":1,"135":2,"136":2,"139":1,"140":1,"141":2,"142":1,"143":1,"144":2,"145":3,"146":1,"157":1,"159":1,"160":1,"161":1,"163":3,"164":2,"192":1,"199":2,"201":1,"203":2,"204":1,"205":1,"209":2,"210":2,"211":2,"212":2,"213":2,"301":1,"329":2,"451":1}}],["management",{"2":{"27":1}}],["many",{"2":{"23":1,"29":1,"48":1,"50":1,"59":1,"69":3,"70":2,"85":1,"88":1,"99":1,"113":1,"128":1,"158":1,"161":1,"169":1,"171":1,"190":1,"191":1,"196":1,"216":1,"234":1,"260":1,"277":1,"300":1,"308":1,"310":1,"311":2,"312":1,"317":1,"319":1,"321":2,"328":1,"330":1,"437":1}}],["made",{"2":{"26":1,"106":2,"108":1,"120":1,"123":1,"143":2,"267":1,"279":1,"310":1,"327":1,"396":1,"449":1,"476":1}}],["majority",{"2":{"67":1,"174":1,"246":1}}],["major",{"0":{"499":1},"2":{"26":1,"110":1,"172":1,"245":3}}],["maybe",{"2":{"328":1}}],["may",{"2":{"12":1,"13":1,"18":2,"20":1,"22":1,"23":1,"26":1,"50":1,"51":1,"61":1,"64":2,"70":1,"72":1,"73":2,"77":1,"82":1,"83":2,"92":2,"93":1,"99":1,"102":1,"103":1,"106":5,"108":2,"114":1,"122":1,"141":1,"142":1,"158":1,"159":1,"160":5,"162":1,"166":1,"169":1,"170":4,"172":2,"179":1,"182":1,"183":1,"184":1,"188":1,"192":3,"194":1,"195":3,"196":9,"204":1,"220":2,"258":1,"261":1,"269":2,"270":2,"271":1,"278":1,"287":1,"297":1,"308":4,"309":1,"310":2,"311":1,"312":2,"318":3,"319":3,"320":2,"321":4,"322":1,"324":2,"325":1,"326":2,"327":2,"329":2,"330":2,"340":3,"342":1,"399":1,"400":3,"401":1,"402":4,"403":2,"404":1,"405":4,"406":1,"407":5,"410":1,"416":1,"419":2,"420":1,"423":3,"424":1,"427":1,"431":7,"432":8,"437":1,"438":1,"439":1,"441":3,"443":1,"444":1,"448":1,"473":2,"474":2,"475":1,"476":2,"478":1,"479":1,"480":1,"494":1,"496":1}}],["macro",{"0":{"39":1},"2":{"37":2,"38":1,"219":1,"234":1}}],["macros",{"0":{"37":1,"38":1},"2":{"36":1,"37":1,"38":1,"45":1,"85":5}}],["macports",{"2":{"23":1}}],["macos",{"0":{"23":1},"2":{"240":1,"242":1}}],["machine",{"2":{"21":1,"114":1,"124":1,"131":2,"162":1,"239":1}}],["mac",{"0":{"12":1}}],["29",{"2":{"460":1}}],["26",{"2":{"458":1}}],["24",{"2":{"458":1,"460":1}}],["241",{"2":{"260":1,"261":1,"274":2}}],["2nd",{"2":{"353":1}}],["2s",{"2":{"328":1}}],["27885568",{"2":{"262":1,"274":1}}],["255",{"2":{"260":3,"261":3,"274":6}}],["2u",{"2":{"255":2,"266":1,"275":2}}],["22",{"2":{"246":1,"458":1}}],["2+2",{"2":{"233":1}}],["206",{"2":{"381":1}}],["2048",{"2":{"240":3}}],["200",{"2":{"193":11,"367":1,"376":1,"383":1,"458":1}}],["20",{"2":{"77":1,"217":1,"338":1,"339":1,"381":2}}],["2020",{"2":{"395":1}}],["2022",{"2":{"242":1}}],["2024",{"2":{"26":3}}],["2023",{"2":{"26":4}}],["2",{"0":{"309":1},"2":{"16":2,"18":1,"23":2,"26":1,"75":1,"94":1,"123":1,"128":2,"140":1,"163":2,"196":1,"204":1,"244":1,"254":1,"255":1,"256":6,"257":1,"261":2,"266":4,"267":1,"269":8,"270":2,"274":7,"308":2,"327":1,"328":1,"330":3,"335":1,"349":1,"361":1,"416":1,"441":4,"443":4,"446":2,"458":8,"460":9}}],["2344844820",{"2":{"274":1}}],["23",{"0":{"11":1},"2":{"486":1,"487":1,"488":1}}],["2o",{"2":{"5":1}}],["xpath",{"2":{"492":1}}],["xhtml+xml",{"2":{"353":1,"386":1}}],["xhtml",{"2":{"353":1}}],["xxx",{"2":{"310":1,"311":1,"345":1}}],["xxxxx",{"2":{"45":6,"108":2,"314":1}}],["xe018",{"2":{"260":1,"261":2,"274":3}}],["xe001",{"2":{"260":2,"261":6,"274":8}}],["xe004",{"2":{"260":1,"261":3,"274":4}}],["xe000",{"2":{"260":8,"261":24,"274":32}}],["xerces",{"2":{"10":1,"11":1}}],["xsd",{"2":{"471":2}}],["xsi=",{"2":{"274":1}}],["xsi",{"2":{"260":1,"261":2,"274":3}}],["xsl>",{"2":{"474":2,"487":2,"488":2}}],["xsl=",{"2":{"474":1,"486":1,"487":2,"488":2}}],["xsl",{"0":{"230":1},"2":{"38":2,"486":14,"487":14,"488":14}}],["xslts",{"2":{"473":1,"482":1}}],["xsltproc",{"2":{"38":4,"122":1}}],["xslt",{"2":{"19":1,"474":2,"482":3,"486":1}}],["xgmml>",{"2":{"256":2,"269":2,"274":2}}],["xgmml",{"2":{"256":1}}],["x509",{"2":{"240":2}}],["x=y",{"2":{"195":1}}],["x26",{"2":{"187":1,"256":4,"260":16,"261":40,"269":16,"270":5,"274":67,"275":15,"340":5,"351":10,"354":2,"355":2,"356":4,"357":8,"358":11,"359":4,"360":4,"369":1,"376":4,"377":1,"426":1,"428":2,"430":1,"460":15,"486":2,"487":2,"488":2}}],["xmlsoap",{"2":{"486":4,"487":4,"488":4}}],["xmlschema",{"2":{"274":1}}],["xmlns",{"2":{"274":1,"474":1,"476":1,"483":2,"486":9,"487":9,"488":9}}],["xml",{"0":{"230":1,"274":1},"2":{"29":1,"32":1,"38":2,"79":2,"80":1,"81":2,"82":2,"86":3,"89":1,"94":1,"128":3,"140":1,"191":1,"195":1,"202":1,"204":3,"225":1,"250":2,"252":1,"254":2,"256":3,"259":2,"260":1,"261":2,"262":1,"266":2,"269":3,"270":2,"274":4,"353":2,"386":1,"400":1,"438":2,"439":3,"461":1,"470":1,"471":2,"472":2,"473":2,"474":8,"481":2,"482":3,"483":1,"484":1,"486":10,"487":3,"488":3,"490":1,"492":1}}],["xcode",{"2":{"20":1}}],["x3c",{"2":{"17":1,"18":1,"123":1,"129":1,"183":2,"187":2,"188":4,"233":3,"234":2,"240":4,"254":7,"256":18,"259":14,"260":6,"261":10,"262":3,"266":7,"269":30,"274":86,"275":1,"340":7,"351":1,"355":4,"358":1,"366":1,"367":6,"394":4,"460":1,"474":30,"486":49,"487":103,"488":79,"498":1}}],["x",{"2":{"15":3,"23":1,"26":7,"64":5,"68":3,"70":4,"75":2,"76":4,"90":3,"164":1,"196":2,"221":4,"244":4,"249":1,"273":1,"299":1,"310":1,"347":4,"353":4,"369":1,"495":1,"496":10,"498":1}}],["x64",{"2":{"13":1}}],["xalanc",{"2":{"12":1}}],["xalan",{"2":{"10":1,"11":1,"19":1}}],["43",{"2":{"458":1}}],["417",{"2":{"360":2}}],["4k",{"2":{"314":1}}],["47f850d827f1655fd6a78fb9c07f1e911b708175",{"2":{"312":1}}],["470622073u",{"2":{"269":1,"275":1}}],["401",{"2":{"360":1,"492":1}}],["40u",{"2":{"269":2,"275":2}}],["40",{"2":{"269":4,"270":2,"275":5,"349":1}}],["4096",{"2":{"241":1}}],["400",{"2":{"193":5}}],["4294967295u",{"2":{"266":2,"269":1,"275":3}}],["42",{"2":{"204":1,"464":1}}],["4",{"0":{"8":1,"311":1},"2":{"13":1,"23":1,"26":1,"130":1,"152":1,"204":1,"217":1,"222":1,"244":1,"245":1,"269":1,"274":1,"327":1,"335":1,"358":1,"393":1,"458":6,"460":6}}],["64k",{"2":{"330":1}}],["64bit",{"2":{"13":1,"187":1}}],["636",{"2":{"204":1}}],["60000",{"2":{"327":1}}],["60",{"2":{"204":3}}],["664",{"2":{"42":1}}],["6m",{"2":{"26":6}}],["6",{"0":{"8":1,"313":1},"2":{"20":1,"22":1,"26":1,"163":1,"164":1,"244":1,"261":1,"460":3}}],["8888",{"2":{"474":1,"487":1,"488":1}}],["86",{"2":{"340":1}}],["8k",{"2":{"330":1}}],["87u",{"2":{"275":1}}],["8080",{"2":{"367":2,"385":1}}],["80",{"2":{"269":1,"270":2,"275":2}}],["8010",{"2":{"124":1,"458":5,"460":15,"491":1}}],["8",{"0":{"315":1},"2":{"7":1,"15":2,"26":1,"95":1,"123":1,"130":1,"194":1,"244":1,"342":1,"393":1,"460":4,"495":1,"497":1}}],["jpg",{"2":{"353":2}}],["jpeg",{"2":{"353":1}}],["jhtree",{"2":{"326":2}}],["jutil",{"2":{"491":1}}],["jump",{"2":{"310":1}}],["justification",{"2":{"245":1}}],["just",{"0":{"42":1},"2":{"21":1,"22":1,"23":1,"128":1,"196":2,"242":1,"243":1,"267":1,"299":4,"309":1,"328":2,"329":1,"340":1,"350":1,"402":1}}],["japis",{"2":{"495":1}}],["jargon",{"2":{"280":1}}],["javascript",{"0":{"230":1},"2":{"353":1,"386":1}}],["javadoc",{"2":{"223":1,"283":1,"338":1,"349":2}}],["javaembed",{"2":{"18":1,"53":1}}],["java",{"0":{"219":1},"2":{"7":1,"195":1,"217":3,"219":2,"226":1,"233":2,"471":2}}],["jwtseccmgr",{"2":{"451":1}}],["jwtsecmgr",{"2":{"451":2}}],["jwtsecurity",{"2":{"447":1}}],["jwt",{"0":{"208":1,"446":1,"452":1},"1":{"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":2,"454":2},"2":{"446":1,"448":7,"449":3,"450":1,"451":5,"452":1}}],["jmetrics",{"2":{"187":1}}],["jstatscodes",{"2":{"189":1}}],["json",{"2":{"131":1,"175":1,"340":1,"353":2,"371":2,"386":1,"400":1,"438":1,"439":1,"449":2,"461":1,"490":1,"492":1}}],["jssidebar",{"2":{"116":1}}],["js",{"2":{"112":2,"116":2,"353":1}}],["jira",{"0":{"290":1},"2":{"103":1,"105":1,"106":4,"108":8,"242":2,"283":1,"289":1,"290":2,"308":2,"310":1}}],["jlib",{"2":{"83":1,"106":1,"173":1,"187":2,"217":1}}],["john",{"2":{"370":2}}],["join",{"2":{"77":1,"164":1,"308":1}}],["joins",{"2":{"76":3}}],["jobs",{"2":{"48":1,"166":1,"242":2,"266":1}}],["job",{"2":{"31":2,"48":1,"72":1,"81":1,"157":1,"196":1,"238":2,"242":2,"269":1}}],["j",{"2":{"20":1,"194":1,"196":2,"337":1}}],["j6",{"2":{"18":1,"20":1}}],["jdk",{"2":{"3":1,"4":1}}],["n>",{"2":{"498":1}}],["nice",{"2":{"447":1}}],["nine",{"2":{"403":1}}],["ninja",{"2":{"20":1}}],["nl",{"2":{"371":2,"447":1}}],["nlppp",{"2":{"34":1}}],["n^2",{"2":{"330":1}}],["npm",{"2":{"114":1}}],["nutshell",{"2":{"217":1}}],["nulls",{"2":{"313":1}}],["null",{"2":{"59":2,"73":1,"266":2,"270":3,"274":1,"275":3,"313":2,"466":1}}],["numdivisions",{"2":{"313":1}}],["numdevices",{"2":{"190":1,"193":3,"194":1}}],["numchannels",{"2":{"192":1}}],["numchildren",{"2":{"59":1}}],["numrows",{"2":{"191":2}}],["numparts",{"2":{"191":1}}],["numeric",{"2":{"170":1,"437":2,"464":1}}],["num",{"2":{"84":1,"309":1}}],["numbers",{"2":{"103":1,"108":1,"148":1,"155":2,"169":1,"308":2,"309":1,"330":1,"351":3,"399":1,"437":4,"441":1}}],["number",{"2":{"56":1,"58":1,"66":1,"69":2,"76":1,"81":5,"83":1,"85":1,"103":1,"106":2,"108":1,"122":1,"143":1,"148":1,"152":1,"154":1,"156":1,"161":2,"163":1,"170":1,"172":1,"174":1,"178":2,"179":1,"184":1,"190":5,"191":2,"192":4,"196":5,"242":1,"269":5,"270":1,"271":1,"308":4,"309":6,"310":3,"311":1,"312":2,"323":1,"327":1,"330":1,"337":4,"338":1,"397":1,"400":2,"403":2,"412":1,"416":1,"423":1,"427":1,"432":4,"436":2,"441":5,"443":2,"479":1}}],["numactl",{"2":{"7":1,"8":1,"11":1}}],["nth",{"2":{"59":1}}],["n",{"2":{"59":1,"63":1,"196":2,"204":1,"308":2,"314":1,"321":1,"326":2,"330":2,"337":2,"338":1,"366":2,"371":5,"378":1,"412":10,"421":6,"439":1,"476":1,"481":1}}],["nagios",{"2":{"495":1}}],["nature",{"2":{"419":1}}],["natural",{"2":{"317":1}}],["naturally",{"2":{"308":1}}],["native",{"2":{"45":1,"172":1,"302":1,"340":1}}],["navigation",{"2":{"280":1}}],["navigating",{"2":{"115":2}}],["navigate",{"2":{"216":1}}],["nanoseconds",{"2":{"189":1}}],["naming",{"0":{"184":1},"1":{"185":1,"186":1},"2":{"184":1,"221":1,"441":1}}],["name1",{"2":{"356":2}}],["name=john1",{"2":{"369":1}}],["name=",{"2":{"256":2,"260":1,"261":2,"269":21,"270":1,"274":16,"474":4,"486":7,"487":24,"488":11}}],["nameindex",{"2":{"252":2}}],["named",{"2":{"70":1,"87":4,"115":1,"185":1,"221":1,"264":1,"290":1,"310":1,"348":1,"400":1,"411":1,"412":5,"415":1,"416":4,"419":3,"421":3,"427":3,"432":2,"441":1,"477":1}}],["namespace>",{"2":{"340":1}}],["namespace",{"0":{"434":1},"1":{"435":1,"436":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1},"2":{"187":2,"238":1,"340":1,"351":1,"433":1,"473":2,"474":1,"476":2,"479":2,"486":1}}],["namespaces",{"0":{"225":1},"2":{"69":1,"84":1,"225":2,"236":1,"483":1}}],["names",{"2":{"54":1,"60":1,"67":1,"73":1,"83":1,"91":1,"184":1,"185":4,"186":1,"190":2,"192":1,"218":1,"220":4,"240":4,"252":1,"256":1,"259":1,"269":4,"274":3,"275":1,"310":3,"311":1,"335":2,"349":3,"403":2,"408":1,"411":2,"415":2,"419":1,"420":1,"432":1,"433":1,"441":3,"443":2,"490":2}}],["name",{"0":{"185":1},"2":{"22":1,"59":1,"64":1,"73":1,"81":2,"123":1,"133":3,"183":4,"184":2,"185":1,"186":3,"189":1,"190":7,"191":2,"192":2,"193":8,"194":1,"204":2,"220":2,"221":1,"233":2,"240":2,"241":1,"243":1,"244":1,"252":3,"261":2,"264":3,"269":7,"270":3,"274":6,"275":1,"301":2,"302":3,"308":1,"311":1,"335":3,"340":2,"349":1,"370":2,"384":2,"400":1,"408":5,"409":1,"410":1,"411":3,"412":25,"413":1,"414":1,"415":3,"416":7,"419":1,"420":3,"421":10,"425":1,"427":4,"432":12,"436":1,"438":2,"439":1,"440":1,"441":8,"443":6,"451":2,"452":2,"478":1,"479":4,"480":2,"485":1}}],["name>=on",{"2":{"18":1}}],["name>",{"2":{"17":1,"183":1,"460":1}}],["net",{"2":{"446":1,"449":4}}],["network",{"0":{"384":1},"2":{"192":1}}],["ne",{"2":{"371":1}}],["negative",{"2":{"338":1}}],["neutral",{"2":{"338":1}}],["neither",{"2":{"190":1,"450":1}}],["nextingroup",{"2":{"313":1}}],["next",{"2":{"69":1,"85":1,"110":1,"143":1,"144":1,"148":1,"152":2,"204":1,"266":1,"308":3,"310":1,"311":1,"313":2,"317":1,"318":1,"326":1,"327":1,"330":2,"335":1}}],["near",{"2":{"287":1}}],["nearest",{"2":{"64":1}}],["nearly",{"2":{"49":1}}],["never",{"2":{"49":2,"58":1,"63":2,"70":1,"102":1,"217":1,"233":1,"313":1,"327":2,"410":1,"412":1,"446":1,"449":1}}],["newpartialmaskserialtoken",{"0":{"445":1}}],["newline",{"2":{"425":1,"439":1}}],["newly",{"2":{"59":1}}],["newer",{"2":{"320":1}}],["news",{"2":{"276":1}}],["newkey",{"2":{"240":1}}],["newusertable",{"2":{"73":1}}],["new",{"0":{"65":1,"115":1,"116":1,"277":1,"284":1,"324":1,"493":1,"496":1,"498":1,"499":1},"1":{"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"494":1,"495":1,"496":1,"497":1,"498":1,"499":1},"2":{"26":2,"49":1,"53":1,"59":2,"62":1,"65":4,"70":3,"74":1,"85":1,"87":1,"103":2,"104":1,"108":1,"112":2,"115":3,"116":3,"128":3,"143":4,"149":1,"160":3,"190":2,"194":2,"195":1,"196":1,"221":1,"222":2,"234":1,"239":1,"240":1,"245":7,"246":2,"247":1,"255":1,"257":2,"264":1,"270":1,"275":3,"276":2,"277":1,"279":1,"280":1,"282":1,"283":1,"286":1,"291":1,"292":1,"293":1,"294":1,"295":1,"296":1,"297":1,"299":3,"308":3,"309":2,"310":13,"311":7,"312":5,"313":3,"317":1,"324":2,"326":1,"333":1,"335":2,"337":1,"340":3,"358":1,"366":2,"397":1,"420":1,"425":1,"449":1,"483":2,"496":2,"497":1,"499":1}}],["nestedctx",{"2":{"312":2}}],["nested",{"2":{"23":1,"65":2,"73":1,"93":1,"106":2,"183":1,"191":1,"195":1,"270":1,"312":1,"482":2}}],["needless",{"2":{"64":1}}],["needsallocator",{"2":{"313":1}}],["needs",{"2":{"48":1,"50":1,"65":2,"66":1,"90":1,"103":1,"127":1,"149":1,"157":1,"160":1,"162":2,"164":1,"170":1,"172":1,"186":1,"191":1,"196":1,"221":3,"235":1,"254":1,"282":1,"283":1,"299":1,"308":2,"309":1,"312":1,"319":1,"329":1,"397":1,"427":1,"448":1,"450":1,"482":1}}],["needed",{"2":{"44":1,"45":2,"58":1,"83":1,"108":1,"143":1,"163":1,"170":1,"179":1,"181":1,"187":2,"189":1,"190":1,"192":1,"204":1,"205":1,"209":1,"239":1,"240":1,"245":1,"249":1,"276":1,"278":1,"285":1,"313":1,"317":1,"318":1,"320":1,"321":2,"329":1,"402":1,"419":3,"431":1,"436":2,"437":1,"443":2,"448":1,"452":1,"477":1}}],["need",{"2":{"13":1,"18":3,"23":1,"48":1,"54":1,"64":1,"67":1,"69":1,"70":3,"77":1,"81":1,"84":1,"103":2,"104":1,"108":2,"112":2,"114":1,"115":1,"116":1,"123":1,"126":1,"128":2,"132":1,"133":2,"149":1,"155":1,"156":1,"160":2,"163":1,"169":4,"186":1,"187":1,"190":1,"196":8,"220":1,"233":1,"238":1,"240":1,"241":1,"245":1,"249":1,"250":1,"254":1,"265":1,"270":1,"278":1,"308":2,"310":7,"311":3,"313":1,"318":2,"319":2,"320":1,"321":1,"326":1,"329":1,"340":2,"407":1,"410":1,"428":1,"448":1,"451":2,"478":1}}],["necessary",{"2":{"12":1,"18":2,"20":1,"74":1,"166":1,"169":1,"172":1,"180":1,"183":1,"205":1,"211":1,"214":1,"234":1,"321":1,"349":1,"388":1,"405":1,"416":1,"474":2}}],["noflylist",{"2":{"349":1}}],["nor",{"2":{"201":1}}],["normal",{"2":{"128":1,"194":1,"266":4,"274":4,"309":1}}],["normalization",{"2":{"73":1}}],["normalizing",{"0":{"73":1},"2":{"65":1}}],["normalized",{"2":{"64":2,"65":1}}],["normalize",{"2":{"51":2,"65":1,"310":1}}],["normally",{"2":{"18":1,"59":1,"61":1,"62":1,"68":1,"69":1,"108":1,"195":1,"245":1,"269":1,"272":1,"308":1,"311":1,"329":1,"345":1,"497":1,"498":1}}],["noun",{"2":{"185":3,"306":1}}],["no",{"2":{"49":2,"53":1,"58":3,"59":2,"63":2,"64":3,"65":3,"66":1,"68":2,"70":1,"73":1,"74":1,"90":1,"103":1,"106":2,"126":1,"130":1,"141":1,"143":3,"145":1,"149":1,"156":2,"163":1,"170":1,"179":1,"180":1,"182":1,"187":1,"193":1,"196":1,"217":2,"219":1,"221":1,"233":1,"238":1,"240":1,"246":1,"270":1,"299":1,"301":1,"303":1,"306":1,"308":1,"309":1,"310":5,"311":1,"313":1,"318":2,"320":1,"321":1,"324":1,"329":1,"342":1,"349":1,"358":1,"359":1,"371":1,"407":2,"412":1,"415":1,"421":4,"423":1,"425":1,"427":1,"428":2,"432":2,"439":1,"451":1,"456":1,"479":3,"480":6,"482":1,"496":2}}],["nonce",{"2":{"448":1,"449":2}}],["nonblockingconnect",{"2":{"327":2}}],["nonrepudiation",{"2":{"240":1}}],["none",{"2":{"66":1,"76":1,"91":1,"204":5,"404":1,"451":3,"456":2,"475":1,"487":1}}],["non",{"2":{"30":1,"106":2,"149":1,"158":1,"192":1,"247":1,"278":1,"308":1,"309":1,"402":1,"408":1,"438":1,"439":1,"479":1}}],["nowhere",{"2":{"172":1}}],["now",{"2":{"18":1,"73":1,"76":1,"128":1,"190":1,"196":1,"241":2,"266":3,"299":2,"310":1,"313":1,"327":1,"335":1,"340":1,"367":1,"412":1,"421":1}}],["node>",{"2":{"256":3,"269":3,"274":3}}],["node400",{"2":{"193":4}}],["node",{"2":{"28":1,"29":1,"49":1,"56":2,"58":7,"59":7,"64":3,"65":1,"68":1,"69":1,"70":3,"90":1,"125":1,"169":1,"193":1,"196":2,"247":1,"256":4,"269":6,"273":1,"274":3,"302":1,"309":1,"310":1,"311":1,"313":1,"317":1,"318":1,"320":7,"325":1,"326":2,"440":3}}],["nodes",{"2":{"24":1,"28":3,"29":1,"49":3,"56":1,"58":6,"59":1,"63":1,"66":2,"75":1,"90":1,"193":4,"196":1,"240":1,"308":1,"310":1,"311":1,"320":5,"322":2,"327":2,"330":1}}],["nodesource",{"2":{"15":1}}],["nodejs",{"0":{"15":1},"2":{"3":1,"7":1,"8":1,"11":1,"15":3}}],["noting",{"2":{"311":1}}],["notices",{"2":{"327":1}}],["notice",{"2":{"277":1,"285":1,"448":1}}],["notifications",{"2":{"265":1}}],["notified",{"2":{"109":1,"270":1}}],["notable",{"2":{"270":1}}],["nothing",{"2":{"128":1,"162":1,"219":1}}],["note=coder",{"2":{"369":1}}],["noteworthy",{"2":{"286":1}}],["noterecordsizeingraph>",{"2":{"259":1,"274":1}}],["noterecordsizeingraph>1",{"2":{"259":1,"274":1}}],["notes",{"0":{"329":1,"330":1,"451":1,"491":1},"2":{"108":1,"204":1,"277":1,"286":1,"328":1}}],["noted",{"2":{"103":1,"106":2,"217":1,"326":1}}],["note",{"0":{"19":1,"392":1},"1":{"393":1,"394":1},"2":{"22":1,"23":1,"59":1,"106":2,"122":1,"128":1,"133":1,"135":1,"139":1,"143":1,"145":1,"153":1,"174":1,"183":1,"187":2,"192":2,"195":1,"196":2,"211":1,"213":1,"233":1,"249":1,"259":1,"266":1,"270":1,"301":1,"302":1,"309":1,"311":1,"313":1,"319":1,"321":1,"326":1,"353":1,"367":1,"370":2,"379":1,"380":1,"384":1,"385":1,"394":1,"446":1,"448":2,"449":1,"460":1,"478":1}}],["not",{"2":{"12":2,"15":1,"23":1,"45":1,"48":1,"50":1,"51":1,"58":3,"62":1,"69":1,"70":4,"73":1,"75":1,"77":1,"83":1,"84":1,"91":1,"102":2,"103":7,"104":1,"106":8,"108":3,"109":1,"112":1,"123":1,"124":3,"132":1,"140":1,"143":3,"145":1,"146":2,"149":2,"150":1,"153":2,"154":1,"155":1,"158":1,"160":1,"163":5,"168":1,"169":2,"170":2,"172":2,"175":1,"187":2,"190":1,"191":2,"192":4,"196":5,"201":2,"202":1,"203":1,"204":6,"211":4,"212":1,"214":1,"216":1,"219":2,"220":1,"221":1,"222":2,"225":1,"233":4,"234":2,"238":1,"241":2,"242":3,"245":2,"246":1,"249":2,"254":1,"263":1,"266":1,"269":2,"270":1,"271":1,"280":1,"281":1,"287":1,"291":2,"299":3,"301":1,"302":1,"303":1,"307":1,"308":5,"309":4,"310":5,"311":3,"312":2,"313":5,"318":2,"319":3,"321":2,"322":1,"324":2,"326":2,"327":10,"328":1,"329":2,"330":5,"339":1,"340":3,"347":1,"349":3,"353":1,"384":1,"394":1,"397":1,"399":1,"400":1,"402":1,"403":1,"404":3,"405":2,"406":1,"410":3,"411":1,"412":4,"414":1,"416":1,"419":4,"420":1,"421":4,"423":1,"424":2,"427":5,"428":1,"435":2,"436":2,"438":1,"439":2,"440":2,"441":1,"443":3,"446":2,"448":3,"451":2,"452":1,"453":3,"454":3,"470":1,"474":1,"476":1,"478":1,"479":1,"480":2,"481":1,"485":2,"491":1}}],["yield",{"2":{"407":1}}],["yuji",{"2":{"395":1}}],["yum",{"2":{"6":1,"7":1,"8":1,"10":1,"16":1}}],["yahoo",{"2":{"383":1}}],["yaml",{"2":{"133":2,"183":1,"201":1}}],["yml",{"2":{"183":1}}],["yes",{"2":{"312":1,"340":1,"421":8,"478":1,"482":1,"486":3,"487":3,"488":3}}],["year",{"2":{"245":1}}],["years",{"2":{"75":1,"217":1,"312":1}}],["yet",{"2":{"143":1,"182":1,"204":1,"217":1,"266":1,"309":1,"330":1,"336":1,"384":1,"414":1,"420":1}}],["yyyy",{"2":{"26":1}}],["y",{"2":{"6":1,"15":1,"16":1,"26":1,"64":2,"68":5,"75":1,"299":1,"310":1,"347":4,"412":15,"476":1}}],["yourthreadpool",{"2":{"366":1}}],["yourthreadpooltaskqueue",{"2":{"366":3}}],["yours",{"0":{"366":1}}],["yourself",{"2":{"320":1}}],["your",{"2":{"15":1,"16":1,"18":2,"21":2,"23":1,"44":1,"108":2,"111":1,"114":1,"123":1,"124":1,"128":6,"131":1,"198":1,"221":2,"233":2,"234":2,"239":5,"241":4,"242":2,"243":1,"246":1,"271":1,"276":2,"280":2,"281":1,"289":2,"299":2,"302":1,"308":1,"335":7,"336":2,"338":2,"340":2,"350":1,"354":1,"447":2,"449":1,"451":1,"473":1,"494":1,"495":1}}],["you",{"0":{"316":1,"320":1},"1":{"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1},"2":{"6":2,"12":1,"13":1,"18":5,"19":2,"20":2,"21":2,"22":2,"23":3,"44":1,"45":1,"54":1,"56":3,"65":1,"68":1,"69":1,"70":6,"74":2,"76":1,"84":1,"90":1,"95":1,"103":1,"108":2,"112":3,"113":1,"114":5,"115":4,"116":3,"120":3,"123":5,"124":1,"125":1,"126":5,"128":10,"129":2,"130":1,"131":2,"133":1,"156":1,"159":4,"160":3,"161":1,"162":2,"169":1,"219":1,"221":4,"233":3,"234":1,"238":1,"239":1,"240":3,"241":2,"242":5,"243":1,"246":2,"247":1,"249":5,"270":2,"276":4,"277":7,"279":1,"280":3,"281":1,"282":1,"289":1,"290":5,"299":4,"301":1,"302":1,"304":4,"306":2,"307":1,"308":6,"309":1,"310":4,"312":1,"313":1,"319":1,"327":1,"331":1,"335":3,"336":3,"338":1,"339":2,"340":7,"342":2,"349":2,"358":2,"359":1,"365":1,"376":2,"377":1,"378":1,"447":2,"450":1,"451":4,"460":2,"473":2,"474":2,"477":1,"480":3,"496":1,"497":3}}],["ecm",{"2":{"474":1,"481":1,"486":1}}],["ecosystem",{"2":{"461":1}}],["eclmy",{"2":{"349":1}}],["eclcmdcommon",{"2":{"348":1}}],["eclccserver",{"0":{"132":1},"2":{"132":1,"133":3,"251":1,"264":4}}],["eclcc",{"0":{"55":1,"97":1},"2":{"33":1,"48":2,"49":1,"50":1,"51":1,"53":2,"54":9,"55":4,"56":2,"58":1,"69":1,"70":1,"72":1,"84":1,"85":1,"92":1,"99":2,"128":4,"199":1,"251":2,"259":1,"262":2,"264":2,"271":1,"274":4,"301":1,"310":1,"311":1,"346":2}}],["eclqueries",{"2":{"340":1}}],["eclversion=",{"2":{"274":1}}],["eclprocess",{"2":{"255":1,"275":1}}],["eclserver",{"2":{"265":1}}],["eclstring",{"2":{"252":1}}],["eclscheduler",{"2":{"251":1,"265":1,"266":1}}],["ecllanguagereference",{"2":{"200":1}}],["ecllibrariespath",{"2":{"54":1}}],["ecllibrary",{"2":{"54":1}}],["eclformatoptions",{"2":{"192":1}}],["eclrtl",{"2":{"275":1,"325":2}}],["eclrtpl",{"2":{"96":1}}],["eclreadoptions",{"2":{"192":1}}],["ecldemo",{"2":{"130":1}}],["eclagentexec",{"2":{"264":1}}],["eclagent",{"2":{"126":1,"251":1,"266":3,"287":1,"328":1}}],["eclhelper",{"0":{"272":1},"2":{"81":2,"257":1,"269":3,"270":1,"272":1,"311":3}}],["eclinclude4",{"2":{"275":1}}],["eclide",{"2":{"238":1,"242":6,"495":1}}],["eclir",{"2":{"56":4}}],["eclipse",{"2":{"55":1,"301":1}}],["eclbundlespath",{"0":{"55":1}}],["eclbundle",{"0":{"55":1}}],["eclwatch",{"2":{"31":1,"124":1,"196":1,"204":1,"274":1,"333":2,"340":4}}],["ecl",{"0":{"30":1,"31":1,"97":1,"229":1,"341":1,"343":1,"349":1},"1":{"342":1,"344":1,"345":1,"346":1,"347":1,"348":1},"2":{"15":1,"18":1,"28":1,"29":1,"30":2,"31":3,"32":2,"33":1,"34":2,"47":1,"48":3,"49":2,"50":1,"51":1,"53":6,"56":11,"59":1,"61":3,"63":1,"64":1,"65":2,"68":1,"72":5,"77":1,"81":2,"82":1,"85":1,"90":5,"91":4,"96":4,"99":1,"130":1,"196":3,"198":2,"200":2,"214":2,"229":1,"249":1,"250":1,"251":1,"252":1,"254":4,"255":1,"257":2,"260":1,"264":5,"266":1,"267":1,"269":9,"270":1,"271":1,"274":7,"275":4,"283":1,"287":1,"290":1,"301":7,"302":5,"308":2,"309":2,"310":3,"311":1,"314":1,"317":2,"325":1,"338":5,"340":10,"342":1,"345":6,"346":3,"347":1,"348":2,"349":3,"452":1,"456":2,"458":14,"460":39,"461":1,"462":11,"464":1,"465":1,"468":2,"470":1,"471":3}}],["eq",{"2":{"376":1}}],["equal",{"2":{"181":1,"308":2}}],["equally",{"2":{"104":1,"196":1,"311":1}}],["equivalent",{"2":{"73":1,"269":1,"423":1,"427":1}}],["err",{"2":{"367":1,"485":1}}],["error",{"0":{"355":1},"2":{"53":1,"106":1,"108":1,"245":1,"278":1,"310":3,"318":1,"321":1,"327":1,"342":1,"355":1,"367":1,"449":1,"485":1}}],["errors",{"2":{"36":1,"108":1,"238":1,"242":1,"278":1,"297":1,"310":1,"319":1,"402":1,"485":1}}],["ep",{"2":{"327":1}}],["epel",{"2":{"6":2}}],["eth0",{"2":{"384":1}}],["etl",{"2":{"249":1}}],["etc",{"0":{"230":1},"2":{"18":2,"20":1,"56":1,"63":1,"69":3,"81":1,"83":1,"87":2,"95":1,"103":1,"108":1,"149":1,"169":1,"175":1,"196":2,"234":1,"250":1,"265":1,"269":1,"276":1,"299":1,"317":1,"318":1,"329":1,"337":1,"345":1}}],["eof",{"2":{"240":4}}],["ezlheuohxxxxxxxxxxxxxxxxxxxxol3986ss=",{"2":{"133":1,"134":1}}],["edit",{"2":{"335":1}}],["editors",{"2":{"113":1}}],["edits",{"2":{"108":1}}],["editing",{"0":{"117":1},"2":{"56":1,"276":1,"277":1}}],["education",{"2":{"106":2}}],["edges",{"2":{"269":9}}],["edge",{"2":{"104":2,"256":2,"269":8,"270":1,"274":1}}],["eg",{"2":{"103":1}}],["elapsed",{"2":{"458":1,"460":1}}],["elastic4hpcclogs",{"2":{"340":1}}],["elastic",{"2":{"340":1}}],["elasticsearch",{"2":{"175":1}}],["eliminating",{"2":{"428":1}}],["eliminates",{"2":{"187":1}}],["elfutils",{"2":{"328":1}}],["else",{"2":{"273":1,"357":1,"358":1,"367":1}}],["elsewhere",{"2":{"23":1}}],["elected",{"2":{"217":1,"310":2}}],["element",{"2":{"93":2,"255":1,"400":3,"403":1,"426":1,"427":2,"428":3,"432":8,"438":1,"439":2,"440":2,"441":4,"443":5,"474":7,"476":1,"477":3,"478":1,"479":1,"481":4,"482":2,"483":7,"484":3,"486":1}}],["elements",{"0":{"253":1},"1":{"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"262":1,"263":1},"2":{"58":1,"195":1,"249":1,"250":2,"262":1,"264":1,"310":2,"337":2,"432":6,"473":2,"474":6,"475":2,"477":2,"483":1,"484":1,"486":1}}],["evaluable",{"2":{"432":1}}],["evaluates",{"2":{"313":1,"462":1}}],["evaluate",{"2":{"70":1,"73":1,"91":3,"92":1,"99":1,"346":1,"461":2,"470":1}}],["evaluated",{"2":{"49":1,"50":5,"68":3,"83":1,"87":1,"92":3,"266":2,"425":2,"432":2}}],["evaluation",{"0":{"470":1},"2":{"50":1,"99":1,"318":1,"425":1,"432":1,"470":1}}],["evaluating",{"2":{"49":1,"90":1,"266":1}}],["evidence",{"2":{"339":2}}],["evicted",{"2":{"158":1}}],["evolves",{"2":{"280":1}}],["ever",{"0":{"316":1},"1":{"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1},"2":{"153":1,"209":1,"217":1,"327":2,"441":1}}],["everywhere",{"2":{"310":1}}],["everyone",{"2":{"216":1}}],["every",{"2":{"26":1,"103":1,"108":1,"192":1,"238":1,"245":1,"254":1,"291":1,"320":1,"327":2,"328":2,"348":1,"400":1,"419":1,"431":1,"448":1}}],["everything",{"0":{"97":1,"316":1},"1":{"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1},"2":{"18":1,"58":1,"103":1,"124":1,"174":1,"250":1,"448":1}}],["events",{"0":{"364":1},"2":{"189":1,"251":2,"254":2,"265":1,"266":2,"322":1}}],["event",{"2":{"178":1,"181":1,"187":1,"254":1,"266":1,"386":1}}],["even",{"2":{"21":1,"58":1,"84":1,"110":1,"160":1,"169":1,"192":1,"196":1,"234":1,"242":1,"245":1,"249":1,"324":1,"327":2,"339":1,"411":1,"415":1}}],["effort",{"2":{"336":1}}],["efforts",{"2":{"276":1}}],["effecient",{"2":{"182":1}}],["effective",{"2":{"339":1}}],["effect",{"2":{"53":1,"149":1,"161":1,"245":1,"264":1,"432":1,"441":1,"448":1,"480":1,"496":1}}],["effects",{"0":{"68":1},"2":{"49":1,"69":1}}],["efficiency",{"2":{"104":1,"106":3}}],["efficiencies",{"2":{"24":1}}],["efficiently",{"2":{"50":1,"69":1,"92":1,"164":1,"299":1,"313":1,"321":1}}],["efficient",{"2":{"24":1,"65":2,"69":1,"91":1,"99":1,"145":1,"149":1,"159":1,"163":1,"270":1,"299":1,"321":1,"425":1}}],["em=",{"2":{"474":1}}],["em",{"2":{"474":28,"477":4,"481":2,"482":1,"483":6,"484":4}}],["email",{"2":{"239":1,"241":2,"337":1}}],["emerge",{"2":{"170":1}}],["embed",{"2":{"474":1,"477":1,"481":1}}],["embedded",{"2":{"12":1,"428":4,"474":1}}],["embarrassed",{"2":{"108":1}}],["emplace",{"2":{"370":2}}],["empirical",{"2":{"326":1}}],["empowers",{"2":{"280":1}}],["empt",{"2":{"279":1}}],["empty",{"2":{"75":2,"143":1,"149":1,"156":1,"188":1,"308":2,"309":3,"310":1,"311":1,"407":1,"424":1,"438":1,"439":1,"445":1,"460":2,"462":2,"469":1,"485":1,"496":1}}],["emphasis",{"2":{"48":1}}],["es=",{"2":{"486":4,"487":4,"488":4}}],["escaping=",{"2":{"486":2,"487":2,"488":2}}],["escaping",{"2":{"482":3}}],["escalating",{"2":{"327":1}}],["esxdl>",{"2":{"487":1}}],["esxdl",{"2":{"474":1,"487":1}}],["establish",{"2":{"405":1,"412":1,"420":1,"424":1,"428":1,"431":1}}],["established",{"2":{"143":1}}],["es",{"2":{"170":1,"486":16,"487":16,"488":16}}],["esoteric",{"2":{"75":1}}],["esdlmethod",{"2":{"487":2}}],["esdlresponse>",{"2":{"487":2}}],["esdlresponse",{"2":{"487":2}}],["esdlrequest>",{"2":{"487":2}}],["esdlrequest",{"2":{"487":2}}],["esdlelement",{"2":{"487":5}}],["esdlbundle>",{"2":{"487":2}}],["esdlservice>",{"2":{"487":1}}],["esdlservice",{"2":{"477":1,"478":1,"480":1,"487":1}}],["esdlservice=",{"2":{"474":1,"487":1,"488":1}}],["esdldefinitionid",{"2":{"478":1}}],["esdldefinition",{"0":{"481":1},"2":{"474":1,"482":1}}],["esdldefinition>",{"2":{"474":2}}],["esdl",{"0":{"471":1},"1":{"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1},"2":{"44":4,"471":23,"472":1,"473":5,"474":2,"476":1,"477":2,"478":2,"479":3,"480":4,"481":2,"482":1,"483":1,"484":1,"485":1,"486":8,"487":5,"488":4,"491":3,"492":2}}],["espmethod",{"2":{"486":1}}],["espservice",{"2":{"486":1}}],["espresponse",{"2":{"486":1}}],["esprequest",{"2":{"486":1}}],["espprocess",{"2":{"480":1}}],["espbinding",{"2":{"480":1}}],["especially",{"2":{"64":1,"83":1,"103":1,"105":1,"107":1,"163":1,"164":1,"270":1,"310":1,"326":1}}],["esp",{"0":{"32":1,"170":1,"489":1},"1":{"490":1,"491":1,"492":1},"2":{"32":1,"170":8,"185":3,"192":3,"194":1,"196":2,"251":1,"264":2,"301":2,"448":2,"449":2,"451":2,"471":4,"472":1,"473":2,"474":1,"479":1,"480":3,"486":1,"487":1,"488":1,"490":4,"491":1}}],["essentially",{"2":{"196":1}}],["essential",{"2":{"3":1,"4":1,"70":1,"339":1,"435":1}}],["earliest",{"2":{"324":1}}],["earlier",{"2":{"102":1,"105":1,"110":1,"245":1,"310":1,"431":1}}],["early",{"2":{"72":1,"100":1,"102":1,"308":1}}],["easy",{"2":{"32":1,"55":1,"103":1,"113":2,"196":1,"232":1,"280":1,"310":1,"313":1,"350":1,"474":1}}],["easiest",{"2":{"312":1}}],["easier",{"2":{"13":1,"73":1,"103":1,"108":2,"196":1,"216":1,"217":1,"247":1,"271":1,"309":1,"313":1,"326":1,"329":1,"337":2,"347":1,"473":1,"492":1}}],["easily",{"2":{"30":1,"48":1,"172":1,"284":1,"321":1}}],["each",{"2":{"27":1,"41":1,"49":1,"50":1,"51":1,"53":3,"56":4,"58":2,"60":1,"69":1,"76":1,"77":1,"80":1,"81":3,"87":2,"88":2,"89":1,"91":2,"92":1,"93":1,"94":1,"106":2,"131":1,"136":2,"139":2,"140":1,"143":4,"147":1,"148":2,"149":6,"150":1,"152":1,"153":2,"154":1,"155":2,"157":1,"161":1,"162":1,"163":1,"164":1,"170":2,"172":1,"173":1,"174":1,"175":2,"181":4,"184":3,"185":3,"187":5,"191":1,"192":3,"193":1,"196":1,"203":1,"204":1,"205":1,"209":1,"210":2,"222":1,"234":1,"238":1,"245":1,"249":1,"254":4,"256":1,"257":1,"261":1,"264":2,"267":5,"268":1,"269":6,"270":7,"272":4,"273":1,"282":1,"308":3,"310":2,"311":5,"318":2,"320":1,"321":1,"324":1,"326":1,"327":1,"330":4,"337":4,"339":1,"399":3,"400":2,"401":4,"403":1,"411":1,"412":1,"415":1,"419":1,"420":1,"423":1,"425":2,"431":1,"432":1,"439":1,"441":3,"442":1,"443":3,"444":1,"452":1,"455":1,"485":1,"486":1,"497":1}}],["either",{"2":{"20":1,"23":1,"26":1,"36":1,"54":1,"55":1,"56":2,"87":1,"91":1,"103":1,"106":1,"108":1,"196":1,"202":1,"211":3,"221":1,"243":1,"266":1,"267":1,"284":1,"287":1,"308":1,"312":1,"320":1,"400":1,"412":1,"419":1,"432":3,"437":3,"448":3,"451":3,"476":3,"482":1,"485":1}}],["env",{"2":{"494":3}}],["envelope",{"2":{"486":4,"487":4,"488":4}}],["environments",{"2":{"190":1,"204":2,"478":1}}],["environment",{"0":{"408":1},"1":{"409":1,"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1},"2":{"27":1,"31":1,"54":2,"55":1,"105":1,"133":1,"134":1,"140":1,"194":1,"196":1,"202":1,"204":3,"213":2,"407":1,"451":1,"474":1,"491":1,"494":2,"496":1}}],["enabling",{"2":{"441":1}}],["enables",{"2":{"400":1,"441":1,"481":1,"490":1}}],["enablewrite",{"2":{"220":2}}],["enabled",{"2":{"163":1,"211":3,"320":1,"421":1,"477":2,"485":2}}],["enable",{"2":{"6":1,"18":2,"65":1,"206":1,"321":1,"385":1,"411":1,"415":1,"441":1,"448":1,"474":3}}],["enqueue",{"2":{"366":2}}],["energy",{"2":{"339":1}}],["enhance",{"2":{"280":1,"336":1}}],["enclosed",{"2":{"479":1,"481":1,"483":1,"484":1}}],["enc",{"2":{"260":1,"261":2,"274":3}}],["encoding",{"0":{"359":1},"2":{"368":2,"390":1,"482":1}}],["encoded",{"2":{"239":1,"439":1,"446":1,"482":1}}],["encountered",{"2":{"402":1}}],["encounter",{"2":{"278":1,"297":1}}],["encourage",{"2":{"277":1,"308":1,"339":1}}],["encouraged",{"2":{"223":1,"411":1,"415":1}}],["encapsulates",{"2":{"175":1}}],["encapsulate",{"2":{"89":1}}],["encapsulated",{"2":{"86":1,"105":1,"182":1,"260":1}}],["ensuring",{"2":{"180":1,"254":1,"339":1}}],["ensures",{"2":{"74":1,"254":1,"262":1,"313":1,"320":1}}],["ensure",{"0":{"307":1},"2":{"18":1,"37":1,"50":1,"53":1,"58":1,"76":1,"91":1,"102":1,"106":1,"108":1,"109":1,"111":1,"124":1,"164":1,"196":1,"198":1,"233":1,"270":1,"271":1,"307":3,"309":1,"310":1,"313":2,"322":1,"326":1,"339":1,"347":1,"486":1}}],["en",{"2":{"131":3}}],["enforce",{"2":{"123":1,"219":2,"432":1}}],["enum",{"2":{"81":1,"187":1,"269":1}}],["enumeration",{"2":{"59":1,"189":1,"272":1,"310":3,"311":3}}],["enumerated",{"2":{"59":1}}],["enough",{"2":{"64":1,"106":1,"108":3,"123":1,"128":1,"163":1,"217":1,"339":1,"397":1}}],["entities",{"2":{"300":1}}],["entirely",{"2":{"339":1,"405":1}}],["entire",{"2":{"36":1,"92":1,"191":1,"428":1,"441":1,"460":1,"483":1,"484":1}}],["entered",{"2":{"241":1}}],["entering",{"2":{"241":1}}],["enter",{"2":{"131":1,"240":1,"241":2,"335":1,"451":1}}],["enterprise",{"2":{"24":1,"30":1,"32":1,"301":1,"302":1}}],["entrypoint=",{"2":{"325":1}}],["entry",{"0":{"444":1},"1":{"445":1},"2":{"116":3,"132":1,"139":1,"196":1,"254":1,"264":3,"272":1,"310":4,"311":2,"406":2,"440":1,"444":2,"451":1}}],["entries",{"2":{"59":1,"163":1,"265":1,"266":1,"309":2,"310":1,"326":1}}],["engineering",{"2":{"167":1}}],["engines",{"2":{"47":1,"50":1,"79":1,"89":1,"99":1,"148":1,"192":1,"199":1,"249":2,"250":1,"255":2,"268":1,"270":2,"272":1,"273":1,"309":1,"311":8,"313":2,"407":1}}],["engine",{"0":{"311":1,"407":1},"2":{"29":2,"80":1,"81":1,"145":1,"163":1,"192":1,"193":1,"249":1,"264":3,"266":7,"269":2,"270":5,"272":3,"308":1,"312":1,"317":1,"397":5,"407":4}}],["ending",{"2":{"482":1}}],["endian",{"2":{"95":1}}],["ended",{"2":{"479":1}}],["endtoken",{"2":{"439":1}}],["endl",{"2":{"367":1}}],["endpoints",{"2":{"327":1,"451":1}}],["endpoint",{"2":{"327":4,"448":2,"450":1,"451":2}}],["endoffset",{"2":{"192":1}}],["ends",{"2":{"68":1}}],["endforeach",{"2":{"41":1}}],["end",{"2":{"27":1,"59":1,"68":1,"147":1,"194":1,"249":1,"255":1,"269":1,"270":1,"277":1,"280":1,"285":1,"288":2,"308":1,"313":2,"319":2,"320":1,"323":1,"325":1,"331":1,"349":3,"425":2,"437":1,"439":3,"482":1}}],["exisitng",{"2":{"340":1}}],["existence",{"2":{"420":1}}],["exist",{"2":{"192":1,"201":1,"340":2,"353":1,"452":1}}],["existing",{"0":{"498":1},"2":{"85":1,"104":1,"160":1,"174":1,"180":1,"190":2,"193":1,"194":1,"196":3,"245":2,"282":2,"283":1,"285":2,"290":1,"313":1,"317":1,"441":1,"448":1,"471":5}}],["exists",{"2":{"65":5,"115":1,"187":1,"193":1,"211":1,"254":1,"400":1,"420":1,"424":1,"425":1,"428":1}}],["exhaustive",{"2":{"309":1}}],["ex",{"2":{"233":2,"234":1}}],["exceeding",{"2":{"339":1}}],["exceeds",{"2":{"157":1}}],["exception",{"2":{"203":1,"221":1,"270":1,"319":1,"329":1,"437":1,"452":1,"482":1}}],["exceptions",{"2":{"49":1,"62":1,"83":1,"88":1,"103":1,"105":1,"217":1,"238":1}}],["exceptional",{"2":{"18":1}}],["except",{"2":{"18":1,"269":1,"386":1,"449":1}}],["exclude",{"2":{"438":1}}],["excludes",{"2":{"431":1}}],["excluded",{"2":{"112":2,"269":1}}],["excluding",{"2":{"421":2}}],["exclusive",{"2":{"163":1}}],["exclusively",{"2":{"149":1}}],["exclusion",{"2":{"112":2}}],["exact",{"2":{"308":1,"321":1}}],["exactly",{"2":{"56":1,"94":1,"299":1,"452":1}}],["examine",{"2":{"270":1,"405":1,"437":1}}],["examined",{"2":{"70":1}}],["example2",{"2":{"233":1}}],["example",{"0":{"252":1,"351":1,"367":1,"474":1},"1":{"352":1,"353":1,"354":1,"355":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1},"2":{"23":1,"45":1,"50":1,"65":1,"99":1,"102":1,"112":1,"115":1,"116":1,"164":1,"169":1,"172":1,"179":1,"182":1,"183":1,"184":1,"186":1,"187":4,"191":1,"193":1,"204":1,"217":2,"220":1,"233":1,"238":1,"240":3,"243":1,"252":2,"254":2,"256":1,"260":1,"261":1,"265":1,"266":1,"269":3,"270":2,"272":1,"277":1,"278":1,"287":1,"290":2,"299":3,"302":2,"309":1,"310":1,"312":1,"318":2,"321":1,"325":1,"326":2,"327":1,"337":6,"338":2,"339":5,"340":5,"349":1,"364":2,"400":3,"402":1,"410":1,"412":5,"416":2,"421":3,"431":1,"432":1,"438":1,"441":2,"443":1,"449":3,"451":1,"452":1,"460":2,"474":2,"486":1,"494":2,"496":1}}],["examples",{"0":{"193":1},"1":{"194":1},"2":{"18":1,"70":1,"75":2,"76":1,"92":1,"104":1,"105":1,"108":1,"169":1,"175":2,"178":1,"185":1,"187":1,"188":1,"190":1,"236":1,"245":1,"269":1,"270":1,"278":1,"281":1,"294":1,"306":1,"309":3,"310":1,"337":2,"464":1,"486":1}}],["expiring",{"2":{"448":1}}],["expiry",{"2":{"324":1}}],["expire",{"2":{"241":1}}],["expires",{"2":{"211":1,"449":2}}],["expiration",{"2":{"241":1}}],["explore",{"2":{"490":1}}],["explored",{"2":{"170":1}}],["explictly",{"2":{"438":1}}],["explicitly",{"2":{"103":1,"330":1,"348":1,"412":1,"416":2,"421":2,"431":1,"441":1,"443":1,"474":1}}],["explicit",{"2":{"63":1,"65":1,"103":1,"192":1,"233":1,"311":1,"416":1,"432":2,"441":1,"476":1,"497":1}}],["explanatory",{"2":{"312":1}}],["explanations",{"2":{"299":1}}],["explanation",{"2":{"199":1,"253":1,"333":1}}],["explain",{"2":{"278":3,"293":1,"337":2,"339":2}}],["explaining",{"2":{"105":1}}],["expert",{"2":{"325":1,"328":2}}],["experts",{"2":{"109":1}}],["experienced",{"2":{"279":1}}],["experience",{"2":{"106":1,"279":2,"342":1}}],["expecting",{"2":{"309":1}}],["expect",{"0":{"360":1},"2":{"168":1,"217":3,"360":3,"376":1}}],["expectation",{"2":{"145":1,"360":1,"410":1}}],["expectations",{"2":{"101":1}}],["expectedmeta",{"2":{"192":1}}],["expected",{"2":{"103":1,"106":9,"139":1,"170":1,"183":1,"269":1,"330":1,"397":1,"399":1,"403":1,"423":1,"425":1,"439":2,"470":1,"483":1,"484":1}}],["expects",{"2":{"88":1}}],["expensive",{"2":{"69":2,"155":1,"156":1,"321":1}}],["exposures",{"2":{"303":1}}],["exposes",{"2":{"218":1,"397":1}}],["expose",{"2":{"196":1}}],["exposed",{"2":{"169":1}}],["exponential",{"2":{"70":1}}],["exporting",{"2":{"196":1}}],["exported",{"2":{"80":1,"81":1,"250":1,"255":1,"269":1,"346":1,"349":1,"444":1}}],["export",{"2":{"23":2,"68":1,"134":1,"196":1,"240":2,"241":2,"349":1,"444":2,"496":1}}],["expansions",{"2":{"83":1}}],["expansion",{"0":{"492":1},"2":{"61":1,"72":1}}],["expandpersistinputdependencies>",{"2":{"274":1}}],["expandpersistinputdependencies>1",{"2":{"274":1}}],["expanding",{"2":{"70":1,"76":1}}],["expanded",{"2":{"61":1,"64":1,"72":3,"87":1,"100":1,"150":1,"160":2,"328":1,"492":1}}],["expand",{"2":{"51":1,"87":1}}],["expr2",{"2":{"56":1}}],["expr1",{"2":{"56":1}}],["expr",{"2":{"56":4,"63":1,"69":1,"70":2,"91":5,"92":3}}],["expressed",{"2":{"437":1}}],["express",{"2":{"317":1}}],["expresssion",{"2":{"56":1}}],["expressions",{"0":{"57":1,"90":1,"91":1},"1":{"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"65":1,"66":1,"67":1,"68":1,"69":1,"70":1,"91":1,"92":1,"93":1,"94":1,"95":1},"2":{"49":1,"56":3,"59":1,"65":1,"66":1,"70":3,"73":2,"74":1,"75":2,"76":1,"84":1,"87":1,"90":1,"91":1,"492":1}}],["expression",{"0":{"58":1,"76":1},"1":{"59":1,"60":1,"61":1,"62":1,"63":1},"2":{"49":6,"50":1,"51":2,"56":11,"58":7,"59":3,"64":2,"65":3,"66":2,"67":3,"68":2,"69":7,"70":6,"72":3,"73":1,"75":1,"76":1,"77":1,"84":1,"85":1,"87":1,"88":1,"90":10,"91":2,"92":2,"96":2,"100":1,"267":3,"310":3,"312":1}}],["exe",{"2":{"44":2}}],["execution",{"0":{"264":1,"268":1},"1":{"265":1,"266":1,"269":1,"270":1},"2":{"70":1,"79":1,"163":1,"169":1,"170":2,"182":1,"249":2,"264":3,"266":2,"270":3,"273":1,"311":1,"313":2,"328":1,"348":1,"455":1}}],["executing",{"0":{"270":1},"2":{"31":1,"33":1,"199":1,"249":2,"255":1,"256":2,"264":1,"266":4,"268":1,"270":3,"313":1,"455":1}}],["executegraph",{"2":{"266":1}}],["execute",{"2":{"53":1,"79":1,"80":2,"84":1,"249":3,"250":1,"251":2,"255":1,"264":2,"265":2,"266":3,"268":2,"270":4,"272":1,"319":1}}],["executes",{"2":{"53":1,"249":1,"251":1,"264":2,"268":1,"270":1,"346":1}}],["executed",{"2":{"18":1,"53":1,"64":1,"80":1,"81":2,"192":2,"250":2,"253":1,"254":3,"256":1,"264":3,"265":1,"266":8,"267":5,"269":1,"270":5,"272":1,"310":1,"313":1,"318":2,"345":1,"456":1}}],["executable",{"0":{"97":1},"2":{"37":2,"264":3,"313":2,"325":1,"326":1,"345":2}}],["executables",{"2":{"20":1,"23":1,"123":1}}],["extremely",{"2":{"350":1}}],["extracted",{"2":{"309":1}}],["extracts",{"2":{"252":1,"313":1}}],["extracting",{"2":{"170":1}}],["extraction",{"2":{"28":1,"492":2}}],["extract",{"2":{"133":1,"241":1,"249":1,"270":1,"346":1}}],["extra",{"2":{"56":2,"103":3,"108":1,"191":1,"192":2,"221":1,"247":1,"267":2,"309":1,"313":3}}],["extfile",{"2":{"240":1}}],["ext",{"2":{"240":2}}],["extern",{"2":{"257":2,"275":6}}],["externals",{"2":{"45":1}}],["external",{"2":{"12":1,"27":2,"45":1,"182":1,"191":2,"196":3,"254":1,"277":1,"340":1,"449":1,"473":1,"474":1,"482":1,"483":1,"484":1,"485":1}}],["extensions",{"2":{"217":1,"240":1,"311":1}}],["extension",{"2":{"115":1,"183":1,"218":4,"249":1,"273":1,"301":1,"317":1,"353":5,"425":1,"437":1,"439":1,"441":1,"442":1,"470":1}}],["extensibility",{"2":{"106":1}}],["extensible",{"2":{"30":1,"48":1}}],["extensive",{"2":{"84":1,"461":1}}],["extent",{"2":{"66":1,"329":1}}],["extending",{"0":{"286":1},"2":{"219":1,"234":1,"401":2,"402":2,"407":1,"412":1,"421":1}}],["extends",{"2":{"113":1,"219":1,"233":2}}],["extended",{"2":{"103":1,"190":1,"309":1,"310":1,"311":1,"313":1}}],["extend",{"2":{"30":1,"103":1,"196":1,"233":1,"312":1}}],["e",{"2":{"13":1,"49":1,"53":3,"54":1,"58":2,"59":1,"62":3,"63":2,"64":2,"65":2,"67":1,"69":5,"70":5,"73":3,"75":1,"76":2,"77":1,"81":3,"83":2,"84":1,"85":1,"87":2,"88":2,"89":1,"91":1,"92":2,"93":2,"94":1,"100":1,"104":1,"105":1,"106":3,"108":3,"109":1,"110":1,"122":1,"128":2,"130":1,"131":1,"134":1,"145":1,"149":1,"160":1,"163":2,"191":2,"192":3,"194":3,"195":2,"196":3,"220":1,"245":2,"249":2,"250":1,"254":2,"255":1,"258":1,"263":1,"264":2,"266":3,"269":10,"270":7,"272":4,"308":1,"309":3,"310":8,"311":3,"312":4,"313":2,"319":1,"321":1,"327":4,"328":1,"397":1,"400":1,"439":1,"442":1,"448":1,"496":1,"497":1}}],["illustrate",{"2":{"441":1,"443":1}}],["illustrates",{"2":{"425":1}}],["ilargememcallback",{"2":{"162":1}}],["ibundlecollection",{"2":{"348":1}}],["ibundleinfoset",{"2":{"348":1}}],["ibundleinfo",{"2":{"348":1}}],["ibyti",{"0":{"324":1},"2":{"320":4,"324":7,"325":1,"330":1}}],["ibar>",{"2":{"233":1}}],["ibar",{"2":{"233":1}}],["iirc",{"2":{"319":1}}],["iindexreadcontext",{"2":{"269":1,"275":1}}],["iinterface",{"2":{"234":1}}],["ixmlwriter",{"2":{"275":1}}],["ienginerowallocator",{"2":{"272":1}}],["ieclprocess",{"2":{"255":1,"272":1,"275":1}}],["ieclsourcecollection",{"2":{"72":1}}],["ieclsource",{"2":{"72":1}}],["iglobalcodecontext",{"2":{"255":1,"272":1,"275":1}}],["ignoring",{"2":{"103":1}}],["ignored",{"2":{"196":1,"245":1,"320":1,"330":1,"419":2,"449":1}}],["ignore",{"2":{"53":1,"67":1,"128":1,"313":1,"320":1,"497":1}}],["iostream>",{"2":{"367":1}}],["io",{"0":{"323":1},"2":{"239":2}}],["ioutputmetadata",{"2":{"269":2,"270":2,"272":1,"275":4}}],["ioutputmeta",{"2":{"89":1}}],["ico",{"2":{"353":1}}],["icon",{"2":{"353":1}}],["icon`",{"2":{"335":1}}],["iconstwuresult",{"2":{"260":1}}],["icodecontext",{"2":{"269":1,"272":1,"275":2}}],["icommon",{"2":{"233":3}}],["icu4c",{"2":{"23":3}}],["icu",{"2":{"12":1,"13":1,"83":1}}],["i+j",{"2":{"196":1}}],["immediate",{"2":{"327":1}}],["immediately",{"2":{"326":1,"427":2,"439":1,"448":1}}],["image",{"2":{"246":5,"247":1,"340":1,"353":5,"386":1}}],["images",{"0":{"246":1},"2":{"246":1,"247":2}}],["imagination",{"2":{"338":1}}],["imagine",{"2":{"196":1}}],["imagicrowstream",{"2":{"192":1}}],["imetric",{"2":{"188":1}}],["impact",{"2":{"106":2,"278":1,"282":1,"339":1,"412":1,"431":1}}],["improvement",{"2":{"310":1}}],["improvements",{"0":{"314":1},"2":{"245":3,"314":1}}],["improve",{"2":{"106":1,"142":1,"245":1,"308":1,"337":2,"425":1,"432":1}}],["improved",{"2":{"106":2,"411":1,"415":1}}],["imports",{"2":{"348":1,"482":1}}],["imported",{"2":{"270":1,"481":1,"482":1,"483":1,"484":1}}],["import",{"2":{"196":1,"347":2,"481":1,"482":1,"483":1,"484":1}}],["importance",{"2":{"104":1}}],["importantly",{"2":{"217":1,"219":1,"221":1,"348":1}}],["important",{"2":{"63":1,"90":1,"104":1,"135":1,"143":1,"145":1,"148":1,"166":1,"196":1,"217":1,"249":1,"311":1,"313":1,"319":1}}],["imposed",{"2":{"414":1}}],["impose",{"2":{"195":1}}],["impossible",{"2":{"100":1,"159":1,"217":1}}],["imperative",{"0":{"50":1,"99":1},"2":{"50":1,"99":1}}],["implied",{"2":{"438":1,"439":1}}],["implies",{"2":{"411":2,"415":2,"425":1,"432":1,"441":5}}],["implications",{"0":{"450":1},"2":{"109":1,"194":1}}],["implication",{"2":{"84":1}}],["implicit",{"0":{"77":1},"2":{"75":1,"77":1,"190":1,"192":2}}],["implicitly",{"2":{"30":1,"64":1,"310":1,"407":1,"416":1,"441":1}}],["implementing",{"2":{"88":1,"108":1,"159":1,"219":1,"270":1,"272":1,"309":4,"310":1,"313":1,"461":1}}],["implements",{"2":{"88":2,"219":1,"233":8,"257":1,"269":2,"433":1}}],["implemented",{"2":{"60":1,"85":1,"88":1,"91":2,"92":1,"96":1,"106":1,"173":1,"193":1,"203":1,"233":1,"256":1,"265":1,"269":1,"270":2,"272":1,"273":1,"308":1,"310":3,"311":1,"313":2,"340":1,"405":1,"425":1,"445":5}}],["implement",{"2":{"51":1,"81":1,"166":1,"174":1,"191":1,"194":2,"195":1,"233":1,"234":2,"249":1,"270":2,"296":1,"308":1,"313":2,"321":1,"446":1,"451":1}}],["implementations",{"0":{"177":1,"421":1,"425":1,"429":1},"1":{"178":1,"179":1,"180":1,"181":1,"182":1},"2":{"88":1,"91":1,"177":1,"192":1,"217":1,"233":2,"234":1,"269":2,"270":1,"308":2,"311":2,"313":1,"397":1,"402":1,"408":1,"411":2,"412":1,"415":2,"416":1,"419":2,"421":1,"438":1}}],["implementation",{"0":{"84":1,"173":1,"199":1,"412":1,"416":1,"432":1},"1":{"85":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1},"2":{"30":1,"58":1,"67":1,"81":1,"83":1,"88":1,"89":1,"91":1,"95":1,"172":1,"174":2,"196":1,"217":2,"219":1,"221":1,"233":4,"234":2,"250":1,"257":2,"258":1,"260":1,"262":1,"264":2,"269":1,"270":2,"272":1,"308":5,"309":4,"310":1,"311":3,"312":2,"313":2,"324":4,"401":1,"405":1,"408":1,"414":1,"421":1,"425":5,"427":3,"435":2,"436":1,"439":1,"440":1,"441":4,"443":3,"448":1,"450":1,"474":1}}],["ihthorquantilearg",{"2":{"311":2}}],["ihthorindexreadarg",{"2":{"270":1}}],["ihthorxyz",{"2":{"88":1}}],["ihthorsortarg",{"2":{"88":1,"272":1}}],["ihthorarg",{"2":{"88":1,"257":2,"269":2,"272":1,"275":3,"311":2}}],["ihqlcppdatasetcursor",{"2":{"93":1}}],["ihqlsimplescope",{"0":{"60":1},"2":{"60":1}}],["ihqlscope",{"0":{"61":1},"2":{"58":1,"61":2}}],["ihqldataset",{"0":{"62":1},"2":{"58":1,"62":1}}],["ihqlexpression",{"0":{"59":1},"2":{"56":2,"58":2,"61":1,"62":2,"67":3,"69":5,"84":1}}],["iptree",{"2":{"430":1}}],["ips",{"2":{"320":1,"327":1,"330":4}}],["ip=",{"2":{"274":1}}],["iplanet",{"2":{"204":1}}],["ip",{"2":{"139":1,"196":1,"204":1,"240":1,"322":1,"340":4,"384":1}}],["ipropertytree",{"2":{"86":1,"194":2,"474":1}}],["ipp",{"2":{"56":1,"218":1}}],["iworkunit",{"2":{"86":1,"250":1,"258":1,"262":1}}],["id>",{"2":{"486":2,"487":2,"488":2}}],["idtoken",{"2":{"449":1}}],["idatamaskingdependencycallback",{"2":{"426":1,"428":1}}],["idatamaskingengineinspector",{"2":{"407":1}}],["idatamaskingengine",{"2":{"407":1,"433":1}}],["idatamaskingprofileiterator",{"2":{"406":1,"433":1,"440":1}}],["idatamaskingprofileinspector",{"2":{"401":1,"435":1}}],["idatamaskingprofilemaskstyle",{"2":{"404":1,"436":1,"443":1}}],["idatamaskingprofilevaluetype",{"2":{"403":1,"404":1,"441":1,"443":1}}],["idatamaskingprofilecontextinspector",{"2":{"402":1,"435":1}}],["idatamaskingprofilecontext",{"2":{"402":1,"435":1,"441":1}}],["idatamaskingprofile",{"2":{"401":1,"435":1}}],["idatamaskerinspector",{"2":{"400":1,"401":1,"402":1,"403":1,"420":2,"424":1,"428":1}}],["idatamasker",{"2":{"400":1,"401":1,"402":1,"407":1,"420":1,"424":1,"428":1}}],["idle",{"2":{"362":1}}],["id=3",{"2":{"269":1}}],["id=1",{"2":{"269":1}}],["id=2",{"2":{"256":1,"269":1}}],["id=",{"2":{"256":4,"269":8,"274":4,"474":1,"487":2,"488":2}}],["idiom",{"2":{"233":1}}],["idiosyncrasies",{"2":{"85":1}}],["id",{"2":{"64":6,"66":1,"81":1,"109":1,"149":2,"190":1,"204":1,"254":1,"256":1,"266":1,"269":10,"321":3,"349":1,"441":1,"449":1,"451":1,"477":1,"478":2,"480":2}}],["ids",{"2":{"56":1,"66":1}}],["ides",{"2":{"446":1}}],["identify",{"2":{"403":1,"428":1,"432":1,"444":1,"461":1}}],["identifying",{"2":{"218":1,"403":1,"428":1,"438":1,"441":4,"443":1}}],["identifier",{"2":{"269":1,"399":1,"432":1,"436":1,"441":3,"443":1}}],["identifiers",{"0":{"220":1},"2":{"399":1,"441":1}}],["identifies",{"2":{"204":1,"427":1,"432":1,"439":1,"441":3,"443":2}}],["identified",{"2":{"170":1,"399":1,"400":1,"403":1,"437":1,"440":1,"444":1,"461":1}}],["identity",{"2":{"209":1,"446":1}}],["identical",{"2":{"49":1,"128":1,"308":1,"400":4}}],["idea",{"2":{"73":1,"76":1,"128":1,"285":1,"308":1,"337":2}}],["ideal",{"2":{"216":1}}],["idealised",{"2":{"51":1}}],["ideally",{"2":{"30":1,"192":1,"308":1,"309":1,"321":1}}],["ideas",{"0":{"49":1,"492":1},"2":{"216":1,"308":1,"336":1,"337":2,"338":1}}],["ide",{"0":{"31":1,"341":1},"1":{"342":1},"2":{"31":2,"301":1,"340":1,"342":1}}],["iroxielazyfileio",{"2":{"323":1}}],["irowmanager",{"0":{"148":1},"2":{"147":1,"148":2,"161":1}}],["iresourcecontext",{"2":{"275":1}}],["ireferenceselector",{"2":{"94":1}}],["irc",{"2":{"269":3,"275":3}}],["irrelevant",{"2":{"128":1,"339":1}}],["ir",{"2":{"56":5,"310":1}}],["i",{"0":{"317":1,"320":1},"2":{"22":1,"49":1,"53":3,"66":1,"100":2,"106":1,"122":1,"128":1,"162":1,"169":1,"192":1,"194":1,"196":8,"218":1,"220":2,"233":1,"240":1,"249":1,"263":2,"266":1,"270":2,"272":1,"277":1,"310":2,"311":1,"313":1,"317":1,"318":1,"319":1,"325":1,"327":7,"335":2,"340":10,"400":1,"439":1,"442":1,"482":1,"485":1}}],["if>",{"2":{"486":2,"487":2,"488":2}}],["ifileio",{"2":{"323":1}}],["iface",{"2":{"233":1}}],["iface>",{"2":{"233":1}}],["ifoo>",{"2":{"233":1}}],["ifoo",{"2":{"233":8}}],["ifs",{"2":{"73":1,"76":1}}],["ifblock",{"2":{"60":1}}],["ifblocks",{"2":{"60":1}}],["if",{"2":{"12":3,"15":1,"18":3,"36":1,"37":2,"48":1,"49":2,"50":1,"56":4,"58":2,"59":3,"60":1,"62":1,"65":1,"66":2,"68":1,"70":7,"72":1,"73":2,"74":3,"75":4,"76":1,"84":2,"85":3,"87":1,"90":1,"91":2,"92":1,"103":12,"104":5,"105":3,"106":4,"108":4,"112":4,"115":1,"120":1,"123":3,"124":2,"128":2,"130":1,"131":2,"140":2,"143":7,"149":2,"153":2,"155":4,"156":1,"157":1,"158":1,"159":5,"160":2,"161":2,"162":1,"163":1,"169":1,"170":2,"180":1,"187":2,"190":3,"191":6,"192":3,"196":2,"204":4,"209":2,"211":8,"217":2,"220":2,"221":6,"233":1,"242":4,"245":7,"246":2,"247":1,"249":1,"254":1,"266":5,"267":5,"269":1,"270":3,"271":1,"275":2,"278":3,"279":1,"282":1,"283":1,"285":2,"286":1,"290":2,"293":1,"295":1,"296":1,"307":1,"308":13,"309":1,"310":6,"311":1,"312":3,"313":6,"318":1,"319":1,"320":5,"321":1,"322":1,"323":1,"324":2,"325":1,"326":2,"327":8,"328":3,"330":3,"331":1,"338":1,"339":1,"340":3,"342":1,"349":1,"351":2,"353":1,"357":1,"358":3,"359":1,"367":2,"376":2,"377":1,"378":1,"397":1,"400":1,"404":2,"407":2,"408":2,"411":2,"415":2,"419":5,"420":1,"421":2,"424":1,"425":1,"427":5,"428":4,"432":3,"437":1,"439":1,"440":2,"441":1,"444":1,"447":1,"448":2,"449":1,"451":1,"479":2,"480":2,"482":3,"485":1,"486":2,"487":2,"488":2,"491":1,"496":2,"497":2}}],["isocketconnectwait",{"2":{"327":1}}],["isolates",{"2":{"175":1}}],["isolate",{"2":{"145":1}}],["isall",{"2":{"311":1}}],["istringset>",{"2":{"269":1,"275":1}}],["isscalar=",{"2":{"261":1,"274":1}}],["issuer",{"2":{"240":1}}],["issues",{"0":{"290":1,"341":1},"1":{"342":1},"2":{"102":1,"103":3,"106":5,"108":2,"194":1,"196":1,"245":1,"278":1,"297":1,"309":1,"310":3,"327":1,"329":1,"333":2}}],["issue",{"2":{"50":1,"83":2,"103":4,"104":1,"106":10,"108":1,"128":1,"246":1,"283":1,"290":1,"307":1,"308":1,"327":1,"329":2,"340":1,"342":1,"431":1}}],["isecmanager",{"2":{"209":1}}],["isgrouped",{"2":{"69":1}}],["isindependentofscope",{"2":{"69":1}}],["isn",{"2":{"50":1,"64":1,"69":1,"70":1,"83":1,"85":1,"103":1,"108":1,"112":1,"160":1,"161":1,"195":1,"327":1}}],["is",{"0":{"79":1,"251":1,"285":1,"308":1,"322":1},"2":{"6":1,"13":1,"15":1,"16":1,"18":5,"21":1,"23":2,"24":1,"26":1,"27":1,"28":1,"29":1,"30":3,"31":2,"44":1,"45":1,"47":2,"48":3,"49":7,"50":11,"51":2,"53":5,"54":1,"56":13,"58":13,"59":5,"60":3,"61":4,"62":6,"63":2,"64":19,"65":4,"66":2,"67":1,"68":3,"69":9,"70":8,"72":11,"73":4,"74":3,"75":2,"76":3,"77":1,"79":3,"80":2,"81":4,"82":1,"83":3,"84":8,"85":1,"86":3,"87":9,"88":1,"89":3,"90":5,"91":2,"92":1,"93":3,"94":1,"95":1,"99":3,"100":3,"101":1,"102":3,"103":7,"104":7,"105":6,"106":13,"108":4,"109":6,"112":2,"113":7,"114":4,"117":1,"123":4,"124":3,"128":3,"129":2,"131":5,"132":3,"133":1,"135":3,"139":3,"140":2,"141":4,"142":1,"143":15,"144":2,"145":3,"146":2,"148":4,"149":14,"150":2,"152":1,"153":4,"154":4,"155":8,"156":11,"157":5,"158":2,"159":2,"160":2,"161":1,"162":2,"163":6,"164":4,"166":2,"167":2,"168":1,"169":5,"170":8,"172":7,"173":1,"176":1,"178":1,"179":4,"180":3,"181":2,"182":2,"183":3,"184":4,"186":5,"187":7,"188":2,"189":2,"190":7,"191":5,"192":11,"193":2,"195":2,"196":11,"200":1,"202":2,"203":4,"204":10,"205":1,"209":8,"210":2,"211":15,"212":1,"213":2,"216":2,"217":4,"219":1,"220":3,"221":5,"222":1,"223":2,"229":1,"233":3,"234":9,"238":5,"242":3,"243":1,"245":1,"246":1,"247":2,"249":15,"250":6,"251":3,"252":2,"253":2,"254":9,"255":6,"256":2,"257":1,"258":1,"260":1,"263":1,"264":17,"266":10,"267":15,"268":1,"269":12,"270":18,"271":1,"272":8,"273":1,"276":3,"277":4,"278":2,"279":1,"280":1,"281":2,"282":3,"283":1,"284":1,"285":3,"287":1,"288":4,"289":1,"290":1,"299":3,"301":5,"302":3,"307":4,"308":25,"309":7,"310":23,"311":13,"312":12,"313":21,"318":6,"319":5,"320":13,"321":5,"322":2,"326":14,"327":24,"328":12,"329":3,"330":10,"331":2,"339":4,"340":2,"342":1,"345":1,"346":2,"347":2,"348":2,"349":2,"357":1,"358":1,"361":1,"365":2,"367":1,"379":1,"380":1,"384":1,"385":1,"387":1,"388":1,"394":1,"397":3,"399":3,"400":1,"401":1,"402":3,"403":3,"404":5,"405":1,"406":3,"407":6,"408":2,"410":4,"411":2,"412":6,"414":4,"415":2,"416":7,"419":13,"420":4,"421":8,"423":4,"424":3,"425":6,"427":6,"428":9,"431":3,"432":8,"433":2,"435":2,"436":8,"437":11,"438":4,"439":7,"440":2,"441":20,"442":1,"443":12,"444":2,"445":1,"446":8,"447":1,"448":11,"449":2,"450":2,"451":4,"452":2,"455":1,"456":1,"460":2,"461":1,"473":6,"474":10,"476":6,"477":5,"478":2,"479":3,"480":5,"481":8,"482":4,"483":4,"484":3,"485":1,"486":2,"487":1,"490":1,"492":1,"495":1,"499":1}}],["iterative",{"2":{"308":1,"339":1}}],["iterating",{"2":{"161":1}}],["iterator",{"2":{"92":1,"406":1}}],["iterators",{"2":{"83":1}}],["iterate",{"2":{"92":1,"93":2}}],["iterated",{"2":{"65":1}}],["item>",{"2":{"254":1,"266":1,"274":1}}],["items",{"0":{"267":1},"1":{"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1},"2":{"80":1,"138":1,"149":1,"169":2,"171":1,"234":1,"251":1,"254":4,"255":1,"258":1,"266":9,"267":4,"272":1,"308":2,"309":1,"371":2,"449":1,"451":2}}],["item",{"2":{"41":1,"42":2,"80":3,"108":1,"149":1,"254":5,"255":3,"266":10,"267":14,"269":1,"272":1,"274":2,"312":1}}],["item5",{"2":{"41":1}}],["item4",{"2":{"41":1}}],["item3",{"2":{"41":1}}],["item2",{"2":{"41":1}}],["item1",{"2":{"41":1}}],["itself",{"2":{"53":1,"73":1,"108":1,"234":1,"249":1,"309":1,"317":1,"347":1,"440":1,"449":1,"482":1}}],["its",{"2":{"48":1,"49":1,"51":1,"58":1,"63":1,"74":1,"75":1,"80":1,"99":1,"106":3,"117":1,"136":1,"143":1,"149":1,"157":1,"169":1,"172":2,"181":2,"187":1,"205":1,"214":2,"217":1,"233":1,"266":1,"270":3,"292":1,"294":1,"313":2,"318":2,"330":1,"339":1,"397":1,"400":1,"405":1,"419":1,"431":1,"432":1}}],["it",{"0":{"79":1,"281":1,"308":1,"317":1,"320":1},"1":{"282":1,"283":1,"284":1,"285":1,"286":1},"2":{"6":1,"13":2,"18":1,"21":2,"23":1,"28":1,"44":2,"47":1,"48":1,"50":7,"51":1,"53":3,"54":1,"55":1,"56":3,"58":5,"59":5,"61":4,"62":1,"63":1,"64":5,"65":3,"66":4,"67":1,"69":10,"70":2,"72":2,"73":1,"74":1,"75":1,"79":2,"81":3,"83":3,"84":2,"85":7,"87":3,"89":2,"90":4,"91":2,"92":3,"93":1,"95":1,"99":3,"100":2,"102":3,"103":9,"104":5,"105":6,"106":7,"108":7,"109":3,"112":3,"113":2,"114":6,"115":2,"123":3,"124":2,"128":1,"129":1,"131":3,"132":1,"135":1,"141":1,"142":1,"143":3,"145":1,"148":1,"149":5,"152":1,"153":2,"154":2,"155":1,"156":3,"157":2,"158":2,"159":5,"160":3,"161":1,"162":4,"163":2,"168":1,"169":2,"170":2,"171":1,"172":4,"176":2,"181":1,"182":1,"183":2,"187":4,"188":1,"190":4,"191":1,"192":4,"194":2,"195":1,"196":12,"201":1,"203":1,"211":1,"212":1,"214":1,"219":7,"221":3,"222":1,"234":3,"241":1,"242":1,"245":6,"247":3,"249":6,"251":1,"252":2,"254":5,"255":1,"256":1,"258":1,"263":1,"264":3,"266":3,"267":7,"269":3,"270":2,"271":2,"277":1,"278":4,"279":1,"280":1,"282":1,"283":1,"285":2,"287":1,"288":1,"299":3,"301":3,"307":3,"308":14,"309":2,"310":9,"311":7,"312":3,"313":12,"318":8,"319":2,"320":6,"321":4,"322":3,"324":2,"325":1,"326":6,"327":15,"328":1,"329":2,"330":4,"335":1,"337":2,"339":4,"340":3,"347":3,"348":1,"350":1,"397":1,"400":3,"402":1,"404":1,"405":1,"407":1,"408":1,"411":2,"412":2,"415":2,"421":1,"423":1,"424":1,"427":6,"431":4,"432":3,"435":2,"436":3,"437":1,"441":4,"442":1,"443":2,"446":1,"448":2,"452":1,"455":1,"471":1,"474":3,"476":1,"477":1,"478":1,"480":1,"481":1,"483":1,"492":1,"494":1,"496":1}}],["inappropriately",{"2":{"441":1}}],["inherited",{"2":{"437":1}}],["inheritance",{"2":{"219":2}}],["inherently",{"2":{"421":1,"439":1}}],["inherent",{"2":{"339":1}}],["innovative",{"2":{"336":1}}],["inner",{"2":{"85":1,"234":1}}],["injected",{"2":{"329":2}}],["injection",{"2":{"106":1}}],["inbound",{"2":{"329":2}}],["inefficient",{"2":{"319":1,"425":1,"431":1}}],["inefficiency",{"2":{"106":1}}],["inefficiencies",{"2":{"77":1,"102":1,"329":1}}],["inkey",{"2":{"240":1}}],["ingested",{"2":{"175":1}}],["ingestion",{"2":{"167":1}}],["invoked",{"2":{"490":2}}],["involving",{"2":{"266":1}}],["involves",{"2":{"256":1,"266":1,"270":1,"310":1,"311":1}}],["involved",{"2":{"158":1,"308":1,"321":1}}],["involve",{"2":{"132":1,"149":1,"160":1,"196":1}}],["investigate",{"2":{"196":1}}],["invalid",{"2":{"48":1,"74":1,"106":1,"309":1,"310":1,"441":1,"443":1}}],["influences",{"2":{"423":1}}],["inferred",{"2":{"407":1}}],["informing",{"2":{"476":1}}],["informed",{"2":{"308":1}}],["informal",{"2":{"104":1,"288":1}}],["information",{"0":{"5":1,"141":1},"2":{"58":1,"61":1,"63":1,"67":4,"69":9,"70":1,"79":2,"81":3,"87":1,"89":2,"102":2,"106":1,"108":2,"116":1,"120":1,"130":1,"141":3,"149":1,"153":1,"171":1,"183":1,"186":1,"190":1,"191":4,"192":12,"194":4,"195":3,"196":7,"201":1,"204":1,"219":1,"221":1,"249":1,"250":4,"251":3,"258":2,"260":1,"263":1,"264":1,"269":4,"270":4,"271":1,"272":1,"276":1,"278":1,"282":1,"283":1,"285":1,"288":4,"298":1,"310":2,"311":2,"313":3,"320":2,"326":3,"330":2,"332":4,"337":2,"339":3,"346":1,"400":1,"401":1,"402":1,"405":1,"407":1,"424":1,"444":1,"446":1,"448":2,"451":1,"455":1,"482":1,"485":2}}],["info",{"2":{"288":1,"326":2,"328":2,"330":1,"332":1,"428":2,"485":2}}],["infromation",{"2":{"195":1}}],["infrastructure",{"2":{"128":1}}],["inf",{"2":{"181":1}}],["infield",{"2":{"73":1}}],["indirectly",{"2":{"348":1}}],["indistinguishable",{"2":{"308":1}}],["indicative",{"2":{"318":1}}],["indicating",{"2":{"190":1,"310":1,"441":2,"451":1}}],["indication",{"2":{"106":1,"170":1}}],["indicated",{"2":{"330":1}}],["indicates",{"2":{"221":1,"310":1,"420":1,"424":2,"428":1,"432":2}}],["indicate",{"2":{"90":1,"103":1,"147":1,"149":1,"157":1,"192":2,"196":1,"221":1,"269":2,"308":2,"313":1,"320":1,"438":1}}],["individually",{"2":{"405":1}}],["individuals",{"2":{"399":1}}],["individual",{"0":{"237":1},"1":{"238":1,"239":1,"240":1,"241":1,"242":1,"243":1},"2":{"70":1,"80":2,"93":1,"169":1,"238":1,"242":1,"321":1,"330":1,"348":1,"400":2,"405":1,"410":1,"411":1,"415":1,"427":2}}],["indeed",{"2":{"449":1}}],["indeterminate",{"2":{"245":1}}],["indented",{"2":{"222":1,"349":3}}],["indent",{"2":{"105":1,"106":1,"108":1,"222":1,"349":1}}],["indentation",{"0":{"222":1},"2":{"103":1,"104":1,"106":1,"108":1}}],["independently",{"2":{"172":1,"175":1,"254":1}}],["independent",{"0":{"79":1},"2":{"69":1,"104":1,"172":1,"174":1,"175":1,"254":3,"266":1,"267":1}}],["indexes",{"2":{"90":1,"95":1,"317":3}}],["index",{"0":{"321":1},"2":{"59":1,"73":2,"95":2,"117":1,"144":1,"190":1,"194":1,"252":2,"256":2,"269":5,"270":5,"274":2,"317":5,"318":2,"321":4,"329":1,"447":2}}],["indexing",{"2":{"28":2}}],["inputs",{"0":{"82":1},"2":{"76":1,"79":1,"82":1,"105":1,"270":2,"278":1,"308":1,"315":1,"318":2,"319":1}}],["inputdatset",{"2":{"64":1}}],["inputdataset",{"2":{"64":1}}],["input",{"0":{"260":1},"2":{"64":6,"73":1,"74":1,"109":1,"169":1,"172":1,"191":1,"212":1,"241":3,"250":1,"260":2,"266":1,"269":1,"270":7,"274":1,"308":10,"309":6,"310":1,"311":2,"312":2,"313":4,"318":3,"319":1,"335":4,"400":2,"406":1,"420":1,"424":1,"428":1,"472":2,"483":2,"484":1,"486":1}}],["ini",{"2":{"54":2,"55":2}}],["initiate",{"2":{"242":1}}],["initiating",{"2":{"175":1}}],["initially",{"2":{"217":1,"249":1,"308":1}}],["initializes",{"2":{"313":1}}],["initialize",{"2":{"220":1,"460":1,"494":1}}],["initialized",{"2":{"183":1,"205":1}}],["initialization",{"0":{"137":1},"1":{"138":1,"139":1,"140":1,"141":1},"2":{"33":1,"135":1,"137":1,"141":2,"172":1,"176":1,"182":1,"213":1,"460":1}}],["initial",{"2":{"103":1,"132":1,"194":1,"211":1,"308":2,"311":1,"324":1,"339":1,"449":1,"450":1}}],["initfiles",{"0":{"39":1}}],["init",{"0":{"135":1},"1":{"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"144":1},"2":{"23":1}}],["inquiry",{"2":{"29":1}}],["inclusion",{"2":{"474":1}}],["including",{"2":{"55":1,"113":1,"192":1,"196":1,"224":1,"234":1,"249":1,"250":1,"261":1,"280":1,"293":1,"294":1,"295":1,"308":2,"310":2,"319":1,"321":1,"339":1,"397":1,"402":1,"446":1,"448":1,"460":1,"464":1,"483":1,"484":1}}],["includes",{"2":{"89":1,"102":1,"108":1,"136":1,"140":1,"249":2,"251":1,"256":1,"266":1,"328":1,"397":1,"408":1,"410":1,"411":1,"414":1,"415":1,"438":1,"439":1,"440":1,"441":2,"443":1,"448":2,"473":1,"486":1}}],["includedeviceinpath",{"2":{"190":1}}],["included",{"2":{"68":2,"83":2,"108":1,"112":2,"190":1,"196":1,"224":1,"250":1,"252":1,"258":1,"259":1,"262":1,"269":2,"283":1,"308":1,"309":2,"310":2,"313":1,"330":1,"338":2,"411":1,"412":1,"415":1,"416":1,"421":1,"423":1,"425":1,"431":1,"440":1,"441":1,"474":1,"479":2,"481":2,"482":2,"485":1,"486":2}}],["includepath",{"2":{"54":1}}],["include",{"0":{"482":1},"2":{"18":1,"23":9,"26":1,"27":1,"45":2,"54":2,"56":1,"76":1,"83":1,"96":1,"103":1,"108":1,"109":1,"178":1,"187":1,"191":1,"192":1,"196":1,"234":1,"257":1,"263":1,"269":3,"270":1,"275":3,"277":1,"278":2,"279":2,"289":1,"302":1,"308":1,"309":1,"310":3,"311":1,"313":1,"328":1,"337":8,"339":1,"348":2,"350":1,"351":1,"367":2,"394":6,"397":1,"403":2,"421":1,"432":2,"436":1,"438":1,"439":1,"461":1,"473":1,"474":9,"479":1,"481":1,"482":2,"483":1,"484":1,"485":1}}],["incidentally",{"2":{"308":1}}],["increments",{"2":{"187":1}}],["increment",{"2":{"156":1}}],["incrementing",{"2":{"141":1}}],["increasing",{"2":{"58":1,"178":1}}],["increase",{"2":{"108":2,"179":2}}],["increased",{"2":{"58":1,"84":1,"156":1}}],["increases",{"2":{"43":1,"152":1}}],["incoming",{"2":{"313":1,"324":2,"328":1}}],["incomplete",{"2":{"103":1,"108":1,"416":1}}],["inconsistent",{"2":{"108":1,"310":1}}],["inconsistencies",{"2":{"56":1}}],["incorporated",{"2":{"288":1}}],["incorporates",{"2":{"27":1}}],["incorrectly",{"2":{"90":1}}],["incorrect",{"2":{"73":1,"102":1,"106":2,"339":2}}],["int64",{"2":{"466":1}}],["int32",{"2":{"466":1}}],["int16",{"2":{"466":1}}],["int8",{"2":{"466":1}}],["intialised",{"2":{"254":1}}],["intrusive",{"2":{"234":2}}],["introducing",{"2":{"73":1}}],["introduces",{"2":{"95":1,"106":1,"310":2}}],["introduced",{"2":{"56":1,"103":1,"455":1,"492":1}}],["introduce",{"2":{"53":1,"103":1,"105":1}}],["introduction",{"0":{"46":1,"145":1,"166":1,"249":1,"344":1},"1":{"47":1,"48":1,"49":1,"50":1,"51":1,"167":1,"250":1,"251":1,"252":1,"345":1,"346":1,"347":1,"348":1},"2":{"33":1,"292":1}}],["int",{"2":{"220":1,"255":1,"275":3,"351":1,"367":1,"378":1,"486":2,"487":2}}],["intuitive",{"2":{"105":1,"310":1}}],["intend",{"2":{"451":1}}],["intended",{"2":{"276":1,"331":1,"410":1,"412":1,"414":1,"416":1,"419":1,"437":1,"441":1,"475":1}}],["intention",{"2":{"446":1}}],["intentionally",{"2":{"416":1,"460":3}}],["intelligence",{"2":{"339":2}}],["integrated",{"2":{"461":1}}],["integration",{"2":{"407":1}}],["integrating",{"2":{"279":1,"310":1}}],["integer4",{"2":{"349":1}}],["integer",{"2":{"95":3,"187":1,"437":1,"464":1}}],["intervention",{"2":{"448":1}}],["interval",{"2":{"362":1,"466":2}}],["interrupting",{"2":{"327":1}}],["interrogating",{"2":{"58":1}}],["interrogated",{"2":{"424":1}}],["interrogates",{"2":{"67":1}}],["interrogate",{"2":{"58":1,"59":1,"408":1}}],["interchangeable",{"2":{"307":1}}],["interchangeably",{"2":{"204":1}}],["interested",{"2":{"308":1,"310":1,"335":1,"432":1}}],["interest",{"2":{"285":1}}],["interesting",{"2":{"49":1,"170":1,"179":1,"266":1,"308":1,"309":1,"328":1}}],["interacts",{"2":{"446":1}}],["interact",{"2":{"279":1,"326":1,"490":1}}],["interactive",{"2":{"279":2}}],["interactions",{"2":{"172":1}}],["interaction",{"2":{"132":3}}],["interacting",{"2":{"131":1,"491":1}}],["inter",{"2":{"265":1}}],["intermixed",{"2":{"219":1}}],["intermediate",{"2":{"56":1,"310":1,"349":1}}],["intergrating",{"2":{"192":1}}],["interpreted",{"2":{"310":1}}],["interpreting",{"2":{"192":1}}],["interpretations",{"2":{"419":1}}],["interpretation",{"2":{"100":1,"419":1}}],["interpret",{"2":{"65":2}}],["internals",{"2":{"199":1}}],["internally",{"0":{"285":1},"2":{"139":1,"160":1}}],["internal",{"2":{"56":1,"63":1,"88":1,"166":1,"167":1,"169":3,"174":2,"179":1,"187":1,"196":2,"217":1,"218":2,"233":1,"242":1,"272":1,"274":1,"277":1,"310":1,"347":1}}],["interface",{"0":{"311":1,"384":1},"2":{"32":2,"59":1,"60":1,"61":2,"69":2,"84":2,"86":1,"88":2,"89":1,"92":1,"93":1,"94":2,"148":1,"172":1,"174":2,"175":1,"188":1,"194":1,"203":1,"205":1,"218":1,"219":4,"220":4,"233":8,"234":1,"250":1,"257":1,"258":1,"260":1,"262":1,"269":3,"270":2,"272":7,"275":1,"302":2,"311":13,"384":2,"397":1,"399":1,"400":1,"403":1,"404":1,"407":1,"408":2,"428":2,"442":1}}],["interfaces",{"0":{"233":1,"272":1,"352":1},"2":{"27":1,"58":5,"72":1,"81":1,"96":3,"194":2,"196":1,"219":2,"223":1,"233":6,"257":1,"269":1,"311":6,"397":2}}],["interfacing",{"2":{"12":1}}],["into",{"0":{"391":1},"2":{"18":1,"30":1,"44":1,"47":1,"50":1,"51":2,"56":1,"62":1,"67":1,"69":1,"70":1,"72":1,"74":1,"76":2,"80":2,"82":1,"84":2,"87":1,"99":1,"106":1,"107":1,"108":1,"110":1,"146":1,"160":1,"161":1,"167":1,"169":2,"170":1,"172":2,"181":1,"183":2,"184":1,"186":2,"189":1,"194":1,"196":2,"203":1,"214":1,"240":1,"241":2,"243":1,"246":1,"250":1,"251":1,"254":1,"266":1,"272":1,"279":1,"288":1,"308":5,"309":1,"310":2,"311":1,"313":2,"321":1,"325":1,"326":2,"329":3,"330":1,"338":2,"345":1,"347":1,"440":1,"452":1,"473":1,"474":1,"481":1,"482":2,"498":1}}],["inspection",{"2":{"405":1}}],["inspected",{"2":{"405":1}}],["inspiration",{"2":{"194":1}}],["insights",{"2":{"328":1}}],["inside",{"2":{"21":1,"56":1,"74":1,"255":1,"266":2,"272":1,"310":2,"312":2,"313":2,"326":1,"482":1}}],["insurmountable",{"2":{"329":1}}],["insurance",{"2":{"307":2}}],["insures",{"2":{"174":1}}],["insure",{"0":{"307":1},"2":{"16":1,"307":2}}],["insensitivie",{"2":{"439":1}}],["insensitive",{"2":{"259":1,"439":2,"485":1}}],["inserted",{"2":{"267":2,"312":1,"474":1,"482":1}}],["insertimplicitprojects",{"0":{"77":1}}],["insecure",{"2":{"204":2}}],["instruct",{"2":{"299":1}}],["instructional",{"2":{"337":1}}],["instructions",{"0":{"0":1,"304":1},"1":{"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1},"2":{"15":2,"17":1,"23":1,"279":1,"288":1,"294":1,"299":1,"332":1,"339":1,"403":2,"451":1}}],["instructing",{"2":{"299":1}}],["instrument",{"2":{"187":1}}],["instruments",{"2":{"172":1,"178":1}}],["instrumenting",{"2":{"172":1}}],["instrumented",{"2":{"170":1,"175":1}}],["instrumentation",{"0":{"187":1},"1":{"188":1,"189":1},"2":{"166":1,"186":1,"187":1}}],["instead",{"2":{"58":1,"70":1,"84":1,"85":1,"87":1,"129":1,"131":1,"152":1,"163":1,"170":1,"186":1,"196":1,"214":1,"217":1,"250":1,"310":1,"326":2,"339":1,"414":1,"428":1,"431":1,"441":1,"446":1,"449":1,"477":1}}],["instantly",{"2":{"280":1}}],["instantiate",{"2":{"174":1}}],["instantiated",{"2":{"172":2,"323":1,"402":1,"441":2,"443":1}}],["instance",{"2":{"77":1,"81":2,"82":1,"87":1,"90":1,"99":1,"103":1,"136":2,"148":2,"180":1,"190":1,"251":1,"255":1,"257":1,"258":1,"264":3,"266":1,"269":5,"270":4,"272":1,"274":1,"312":2,"313":1,"327":2,"340":2,"400":1,"401":3,"407":1,"420":1,"421":4,"432":1,"433":1,"435":1,"439":1,"441":2,"443":1,"445":1,"491":1}}],["instances",{"0":{"136":1},"2":{"73":1,"80":1,"83":1,"88":1,"89":1,"103":1,"147":1,"250":1,"255":1,"257":1,"270":1,"273":1,"313":1,"318":1,"339":1,"405":1,"407":1,"433":1,"437":1,"438":1,"441":1,"443":1}}],["instanceid",{"2":{"56":1}}],["installations",{"2":{"122":1}}],["installation",{"2":{"22":1,"25":1,"124":2,"278":1,"333":2,"345":1}}],["installed",{"2":{"16":1,"23":1,"123":1,"212":1,"246":1,"345":1,"347":1,"348":1,"447":1}}],["installing",{"0":{"22":1,"341":1},"1":{"342":1},"2":{"13":1,"122":1,"125":1}}],["installs",{"0":{"42":1},"2":{"5":1,"23":1,"42":4}}],["install",{"0":{"42":1},"2":{"3":1,"4":1,"5":2,"6":1,"7":1,"8":1,"10":1,"11":1,"12":1,"13":16,"15":2,"16":4,"21":2,"22":2,"23":8,"42":4,"114":1,"122":3,"123":1,"128":1,"246":1,"278":1,"340":1,"342":1,"491":1}}],["in",{"0":{"40":1,"286":1},"1":{"41":1,"42":1,"43":1,"44":1},"2":{"12":1,"18":5,"19":1,"20":1,"23":1,"26":1,"27":2,"29":1,"31":1,"33":1,"36":1,"37":1,"44":3,"45":1,"49":5,"51":3,"53":3,"54":1,"55":2,"56":8,"58":6,"59":2,"60":2,"61":2,"62":2,"63":3,"64":9,"65":3,"66":2,"67":1,"68":4,"69":5,"70":6,"73":1,"75":3,"77":2,"80":2,"81":5,"82":1,"83":2,"84":2,"85":4,"86":1,"87":1,"88":3,"90":3,"91":5,"92":3,"93":3,"94":2,"95":3,"99":2,"100":3,"103":5,"104":2,"105":5,"106":11,"107":1,"108":7,"109":1,"112":8,"113":1,"114":2,"116":1,"117":2,"122":1,"123":4,"128":5,"129":4,"131":2,"132":1,"133":2,"134":1,"139":1,"140":3,"142":1,"143":3,"146":2,"149":9,"150":1,"153":1,"157":2,"158":1,"159":1,"160":1,"161":3,"162":1,"163":2,"164":1,"166":2,"167":2,"169":1,"170":3,"172":5,"174":1,"175":2,"179":4,"182":3,"184":1,"185":1,"187":3,"188":1,"189":3,"190":4,"191":3,"192":11,"194":6,"195":5,"196":17,"199":1,"200":1,"201":2,"202":2,"204":6,"205":1,"209":2,"210":1,"211":3,"212":3,"213":2,"214":2,"216":3,"217":5,"218":2,"220":1,"223":1,"224":2,"226":1,"233":6,"234":4,"238":3,"239":2,"240":5,"241":2,"242":2,"243":1,"245":3,"246":3,"249":11,"250":5,"251":2,"252":1,"253":1,"254":2,"256":3,"257":2,"258":4,"259":1,"260":2,"261":3,"262":3,"263":1,"264":5,"265":1,"266":7,"267":7,"268":1,"269":13,"270":10,"271":8,"272":3,"273":4,"274":1,"275":1,"277":5,"278":1,"279":2,"281":1,"282":2,"283":2,"285":2,"286":1,"287":3,"289":1,"290":3,"292":1,"299":5,"301":4,"302":1,"303":1,"308":27,"309":14,"310":30,"311":14,"312":4,"313":13,"317":3,"318":8,"319":1,"320":5,"321":9,"322":1,"323":3,"324":4,"326":10,"327":10,"328":9,"329":7,"330":9,"331":1,"333":1,"335":5,"336":2,"337":12,"338":1,"339":6,"340":2,"346":2,"347":4,"349":9,"350":1,"353":1,"393":1,"397":3,"399":2,"400":5,"403":2,"404":2,"405":2,"406":1,"407":2,"408":4,"410":4,"412":4,"414":2,"416":2,"419":4,"420":5,"421":6,"423":2,"424":4,"425":2,"427":2,"428":4,"431":2,"432":2,"433":1,"436":4,"438":3,"439":2,"441":6,"443":3,"446":3,"448":2,"449":4,"450":1,"451":5,"452":2,"460":1,"461":1,"462":1,"470":2,"471":1,"473":3,"474":4,"475":1,"477":1,"479":5,"480":4,"481":3,"482":4,"483":1,"484":1,"485":3,"486":2,"487":1,"490":1,"492":1,"495":1,"496":1}}],["inline",{"2":{"3":1,"7":1,"8":1,"16":2,"72":1,"92":2,"106":1,"219":1,"226":1,"270":3,"275":1,"312":1,"338":2,"482":1}}],["lz4",{"2":{"460":2,"467":1}}],["ls",{"2":{"391":1}}],["l=boca",{"2":{"240":1}}],["l",{"2":{"56":1,"194":1,"240":1,"275":1,"310":1}}],["ln2",{"2":{"314":1}}],["lnb",{"2":{"239":2}}],["ln",{"2":{"53":1,"239":1,"242":9,"243":1,"321":1,"495":2}}],["letting",{"2":{"435":1}}],["letters",{"2":{"437":2}}],["letter",{"2":{"220":2,"311":1}}],["let",{"2":{"408":1}}],["len",{"2":{"378":3}}],["lenr",{"2":{"275":1}}],["lenresult",{"2":{"269":2,"275":4}}],["lenl",{"2":{"275":1}}],["lensrc",{"2":{"275":1}}],["lentgt",{"2":{"275":1}}],["lengthy",{"2":{"299":1}}],["length",{"0":{"363":1},"2":{"169":1,"174":1,"270":2,"349":1,"351":2,"357":4,"358":5,"363":1,"376":4,"377":2,"418":3,"419":1,"422":1,"423":1,"426":1,"428":3,"436":1,"442":1}}],["lengths",{"2":{"105":1}}],["led",{"2":{"219":1}}],["leverage",{"2":{"174":1,"180":1,"278":1}}],["levels",{"2":{"93":1,"169":1,"277":1,"330":1}}],["level",{"2":{"44":4,"56":2,"105":1,"169":1,"183":1,"222":1,"254":1,"280":1,"321":1,"326":1,"346":1,"396":1,"397":1,"451":3}}],["lexer",{"2":{"85":2,"310":1}}],["lexisnexis",{"2":{"24":1,"240":1}}],["legally",{"2":{"301":1}}],["legal",{"2":{"64":1,"74":1}}],["legacydomain",{"2":{"441":2}}],["legacy",{"2":{"59":1,"68":1,"202":1,"204":4,"217":1,"414":1}}],["lefthand",{"2":{"239":1}}],["left",{"2":{"64":9,"66":1,"74":1,"77":1,"108":1,"269":4,"275":4,"308":2,"310":2,"311":2,"312":1,"335":4}}],["leaving",{"2":{"441":1}}],["leaves",{"2":{"321":1}}],["leave",{"2":{"103":1,"241":1}}],["least",{"2":{"326":2,"349":1,"399":1,"401":1,"405":1,"407":1,"410":1,"411":1,"414":1,"415":1,"424":1}}],["lean",{"2":{"321":1,"394":2}}],["leaks",{"2":{"105":1,"145":1}}],["leads",{"2":{"327":1}}],["leading",{"2":{"220":1,"308":1,"349":1,"483":1,"484":1}}],["lead",{"2":{"48":1,"75":1,"77":1,"106":1,"217":1,"329":1,"339":2}}],["learners",{"2":{"278":1}}],["learning",{"2":{"56":1,"279":2}}],["learn",{"2":{"25":1,"411":1,"415":1}}],["less",{"2":{"24":2,"56":1,"99":1,"104":2,"181":1,"217":1,"245":1,"308":1,"313":1,"331":1,"400":1}}],["leopard",{"0":{"12":1}}],["ldapdomain",{"2":{"204":1}}],["ldaptimeoutsec",{"2":{"204":1}}],["ldaps",{"2":{"204":1}}],["ldapsecureport",{"2":{"204":2}}],["ldapprotocol",{"2":{"204":1}}],["ldapport",{"2":{"204":2}}],["ldapciphersuite",{"2":{"204":1}}],["ldapadminvaultid",{"2":{"140":2,"204":2}}],["ldapadminsecretkey",{"2":{"140":2,"204":1}}],["ldapaddress",{"2":{"139":1,"204":1}}],["ldap",{"0":{"135":1,"136":1,"204":1,"211":1},"1":{"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"144":1},"2":{"23":3,"135":4,"136":2,"139":1,"140":1,"143":3,"202":1,"203":2,"204":9,"211":1,"301":1,"448":1,"451":1}}],["lt",{"2":{"20":2,"23":2,"36":1,"39":1,"43":1,"44":2,"53":4,"64":1,"74":3,"75":1,"80":1,"91":6,"109":1,"183":2,"185":9,"187":1,"190":10,"191":14,"192":3,"193":3,"196":2,"200":1,"221":5,"234":4,"241":1,"245":4,"259":1,"265":5,"269":22,"270":1,"308":6,"311":2,"314":1,"327":1,"393":1,"400":2,"439":1,"474":11,"477":4,"481":2,"482":2,"483":15,"484":9,"485":6,"486":1,"487":1,"488":1}}],["lts",{"2":{"15":1}}],["lld",{"2":{"378":2}}],["lll",{"2":{"328":2}}],["ll",{"2":{"18":1,"128":1}}],["lost",{"2":{"328":1,"330":1}}],["lose",{"2":{"221":1,"477":1}}],["lot",{"2":{"221":1,"327":1}}],["lots",{"2":{"49":1,"249":1,"310":1,"327":1}}],["lowest",{"2":{"441":2}}],["lowercase",{"2":{"301":1,"302":1,"311":1,"479":2}}],["lower",{"2":{"157":2,"220":1,"259":1,"320":1,"330":2,"349":2}}],["low",{"2":{"169":1}}],["locks",{"2":{"149":1}}],["lockless",{"2":{"149":2}}],["lock",{"2":{"131":1,"149":1,"155":1,"159":3,"160":2,"328":2}}],["locating",{"2":{"114":1,"405":1}}],["locationa",{"2":{"400":1}}],["locations",{"2":{"190":1,"310":1,"312":1}}],["location",{"0":{"112":1},"2":{"44":2,"69":1,"70":1,"81":1,"104":1,"196":1,"243":1,"258":1,"269":1,"288":1,"312":1,"323":2,"332":1,"340":2,"383":1,"427":1,"437":4,"479":1,"485":1,"491":1}}],["located",{"2":{"112":4,"117":1,"123":1,"238":1,"335":5,"346":1,"470":1,"491":1}}],["locate",{"2":{"36":1,"45":1,"321":1,"405":1,"423":1}}],["localoptimization",{"2":{"329":1}}],["localagent",{"0":{"329":1},"2":{"329":3}}],["local>",{"2":{"274":1}}],["localhost",{"2":{"114":1,"115":1,"126":1,"351":1,"367":6,"382":1,"385":1}}],["locally",{"0":{"114":1},"2":{"114":1,"192":1,"266":1,"315":1,"494":1}}],["local",{"0":{"131":1},"2":{"5":3,"23":8,"55":2,"63":1,"85":1,"114":2,"131":2,"172":1,"190":1,"192":2,"196":4,"234":1,"274":1,"308":2,"309":2,"310":1,"323":3,"329":2,"340":1,"347":2,"455":1,"474":1,"487":1,"488":1,"494":1}}],["longevity",{"2":{"217":1}}],["longer",{"2":{"58":1,"106":1,"143":1,"156":1,"163":1,"221":3,"299":1,"310":1,"313":1,"329":1}}],["longstanding",{"2":{"194":1}}],["long",{"2":{"50":1,"66":1,"99":1,"108":1,"308":1,"327":1,"330":1}}],["loosely",{"2":{"452":1}}],["lookahead",{"2":{"310":1}}],["looks",{"2":{"85":1,"204":1,"327":1}}],["lookups",{"2":{"317":1}}],["lookupsymbol",{"2":{"61":1}}],["lookup",{"2":{"61":1,"164":1,"191":1,"211":1,"317":1,"321":2,"327":1}}],["looked",{"2":{"61":1}}],["look",{"2":{"53":1,"108":1,"114":1,"194":1,"195":1,"217":1,"249":1,"308":1,"318":1,"327":2}}],["looking",{"2":{"45":1,"69":1,"108":1,"249":1,"310":1,"313":1,"321":2,"330":1}}],["looping",{"0":{"41":1}}],["logcontent",{"2":{"486":2,"487":2,"488":2}}],["logvalue",{"2":{"486":1,"487":1,"488":1}}],["logdataxpath>",{"2":{"474":2,"487":2,"488":2}}],["logdetail",{"2":{"56":1,"310":1}}],["logagent>",{"2":{"474":1,"487":1,"488":1}}],["logagent",{"2":{"474":1,"487":1,"488":1}}],["logged",{"2":{"448":1}}],["logger",{"2":{"354":2}}],["loggingmanager>",{"2":{"474":2,"487":2,"488":2}}],["logging",{"0":{"354":1},"2":{"56":6,"166":1,"170":1,"245":1,"310":1,"455":1,"474":2,"486":3,"487":3,"488":3}}],["logfullqueries=1",{"2":{"325":1}}],["logfile",{"2":{"56":1,"310":1}}],["logic",{"2":{"172":2,"196":1,"249":1,"313":1,"321":1,"397":1,"477":1,"481":1}}],["logicalfile",{"2":{"192":1}}],["logically",{"2":{"192":2,"249":1,"258":1,"269":1,"399":1,"476":1}}],["logical",{"2":{"51":1,"87":1,"95":2,"191":1,"192":4,"196":8}}],["loginfo",{"2":{"474":1,"487":1,"488":1}}],["login",{"2":{"131":2,"335":1,"448":2,"449":2,"450":1,"451":1}}],["logs",{"2":{"128":1,"340":3}}],["log",{"2":{"37":2,"56":3,"105":1,"108":1,"128":1,"214":2,"310":1,"340":3,"455":1,"458":3,"460":3,"471":4,"474":3,"480":1,"486":1,"487":2,"488":2}}],["loadable",{"2":{"203":2}}],["loadbalanced",{"2":{"196":1}}],["loads",{"2":{"172":1,"318":1,"407":1}}],["load",{"0":{"138":1,"430":1},"1":{"139":1,"140":1,"431":1,"432":1},"2":{"123":2,"166":1,"183":1,"196":2,"204":1,"249":1,"320":2,"327":2,"428":1}}],["loading",{"2":{"27":1,"28":1,"407":1,"474":1}}],["loaded",{"2":{"18":1,"138":1,"187":1,"203":1,"205":1,"249":1,"270":1,"273":1,"397":1}}],["lacks",{"2":{"339":1}}],["launch",{"2":{"473":1}}],["launched",{"2":{"328":1,"487":1}}],["launching",{"2":{"326":1}}],["label",{"2":{"438":2,"441":1,"443":1}}],["labels",{"2":{"311":1,"438":1,"443":1}}],["label=",{"2":{"256":2,"269":2,"274":2}}],["lamda",{"2":{"196":1}}],["lambda",{"2":{"195":1}}],["lastname",{"2":{"349":6}}],["last",{"2":{"145":1,"149":1,"217":1,"308":5,"310":1,"313":2,"321":1,"326":1,"349":1,"382":1,"397":1,"403":1,"431":1,"437":2}}],["lane",{"2":{"330":1}}],["landingzone",{"2":{"196":1}}],["landing",{"0":{"117":1},"2":{"117":1,"196":5,"254":1}}],["language",{"0":{"283":1},"2":{"16":1,"24":1,"28":1,"29":1,"30":3,"49":2,"200":2,"217":1,"276":1,"280":1,"301":1,"302":1,"310":2,"317":1}}],["languages",{"2":{"12":1}}],["late",{"2":{"498":1}}],["latency",{"2":{"169":1,"318":2}}],["later",{"2":{"64":1,"68":1,"72":1,"87":1,"100":1,"102":1,"105":1,"123":1,"192":1,"196":2,"264":1,"272":1,"324":1,"342":1}}],["latest",{"0":{"17":1},"2":{"204":1,"246":1}}],["lazy",{"0":{"323":1},"2":{"50":1,"99":1,"318":1}}],["lazily",{"2":{"49":1,"270":1}}],["largely",{"2":{"448":1}}],["larger",{"2":{"148":1,"267":1,"326":1,"330":1}}],["large",{"0":{"160":1},"2":{"48":1,"53":1,"103":2,"106":1,"108":2,"146":1,"155":2,"158":2,"160":1,"161":1,"163":3,"169":1,"271":1,"289":1,"309":1,"312":1,"326":1,"329":1,"376":1,"425":1,"466":1}}],["layouts",{"2":{"263":1}}],["layout",{"2":{"44":1,"94":1,"117":1}}],["layer",{"2":{"27":1,"446":1}}],["license",{"0":{"395":1},"2":{"395":1}}],["likings",{"2":{"335":1}}],["likelihood",{"2":{"108":1,"339":1}}],["likely",{"2":{"66":1,"104":2,"106":1,"132":1,"154":1,"159":1,"169":2,"192":1,"196":4,"308":1,"399":1,"428":1}}],["like",{"2":{"18":1,"48":1,"68":1,"106":2,"203":1,"217":1,"219":1,"221":1,"233":1,"234":1,"241":1,"267":2,"299":1,"310":1,"325":1,"327":5,"331":1,"342":1,"345":1,"446":1,"460":1,"473":1}}],["living",{"2":{"270":1}}],["live",{"2":{"211":1}}],["lives",{"2":{"190":1}}],["limitations",{"2":{"278":2,"339":1}}],["limit",{"2":{"181":2,"182":2,"326":2,"330":1,"339":1}}],["limits",{"2":{"181":2,"182":1,"323":1,"329":1}}],["limiting",{"2":{"148":1,"423":1}}],["limited",{"2":{"72":1,"143":1,"145":1,"317":1,"323":1,"403":1}}],["little",{"2":{"62":1,"308":1,"330":1}}],["lifeexpectancy",{"2":{"338":1}}],["lifespan",{"2":{"329":2}}],["life",{"2":{"49":1,"145":1,"169":1,"302":1,"308":1}}],["lifetimes",{"2":{"328":1}}],["lifetime",{"2":{"49":1,"58":1,"131":1,"187":1,"196":1,"221":3,"328":1,"448":2}}],["lintain",{"2":{"36":1}}],["linkcount",{"2":{"156":1}}],["linked",{"2":{"56":1,"149":6,"155":2,"221":3,"234":3,"256":2,"318":1,"385":1,"387":1,"388":1}}],["link",{"0":{"318":1},"2":{"49":1,"58":3,"70":1,"81":1,"84":1,"93":3,"116":1,"145":1,"149":1,"156":4,"221":3,"234":2,"249":1,"270":1,"273":1,"275":1,"304":1,"312":1,"313":1,"318":1,"326":1}}],["links",{"2":{"33":1,"131":1,"234":1,"279":2,"318":1}}],["linking",{"2":{"28":2,"105":1}}],["lines",{"2":{"99":1,"103":1,"108":2,"187":1}}],["line",{"0":{"283":1,"489":1},"1":{"490":1,"491":1,"492":1},"2":{"19":1,"53":1,"55":1,"81":1,"106":1,"131":1,"187":2,"196":1,"222":1,"259":1,"264":2,"269":1,"279":1,"283":1,"302":3,"328":1,"340":2,"348":1,"349":2,"472":1,"474":1,"476":3,"478":1,"482":1}}],["linux",{"0":{"241":1},"2":{"15":1,"18":1,"123":1,"124":1,"128":2,"129":1,"163":1,"239":1,"240":1,"241":2,"249":1,"273":1,"321":1,"326":5,"452":1}}],["listen",{"2":{"351":1,"352":1}}],["listening",{"2":{"170":1,"480":1}}],["listed",{"2":{"128":1,"327":4,"340":1,"444":1}}],["list",{"2":{"12":1,"37":1,"53":1,"112":2,"139":1,"144":1,"145":1,"148":1,"149":7,"150":1,"155":2,"161":1,"168":1,"170":2,"172":1,"183":1,"190":1,"191":1,"192":2,"196":1,"214":1,"303":1,"308":1,"309":3,"310":3,"326":1,"327":3,"328":4,"330":2,"335":1,"337":9,"338":1,"339":3,"349":2,"466":2,"471":4,"479":1,"490":6}}],["libdatamasker",{"0":{"433":1},"1":{"434":1,"435":1,"436":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"445":1}}],["libz",{"2":{"387":1}}],["libhpccmetrics",{"2":{"183":1}}],["libhiredis",{"2":{"3":1,"4":1}}],["libldap",{"2":{"23":1}}],["libldap2",{"2":{"3":1,"4":1}}],["librarypath",{"2":{"54":1}}],["library",{"0":{"283":1,"349":1},"2":{"18":1,"23":1,"37":2,"45":1,"54":1,"88":1,"104":1,"217":2,"218":4,"246":1,"249":2,"273":1,"283":1,"349":2,"350":1,"393":1,"396":1,"406":1,"433":2,"440":1,"444":2,"491":1}}],["library=",{"2":{"5":1,"325":1}}],["libraries",{"2":{"12":1,"20":1,"30":1,"36":1,"45":2,"106":1,"122":1,"217":3,"246":1,"249":2,"273":2,"388":1,"397":1,"408":1,"450":1}}],["libraries=",{"2":{"5":1,"23":4}}],["lib",{"2":{"5":2,"23":4,"183":1,"187":1,"193":7}}],["libs",{"2":{"20":1,"23":1,"45":1,"204":1}}],["libssl",{"2":{"3":1,"4":1,"5":2,"385":1}}],["libsqlite3",{"2":{"3":1}}],["libtool",{"2":{"3":1,"4":1}}],["libtbb",{"2":{"3":1,"4":1}}],["libcrypto",{"2":{"385":1}}],["libcurl",{"2":{"7":1,"8":1}}],["libcurl4",{"2":{"3":1,"4":1}}],["libcppunit",{"2":{"3":1,"4":1}}],["libmemcached",{"2":{"3":1,"7":1}}],["libmysqlclient",{"2":{"3":1,"8":1}}],["libblas",{"2":{"3":1,"4":1}}],["libboost",{"2":{"3":1,"4":1}}],["libevent",{"2":{"3":1,"4":1,"7":1,"8":1}}],["libnuma",{"2":{"3":1,"4":1}}],["libxslt",{"2":{"7":1,"8":1,"11":1,"13":1,"19":1}}],["libxslt1",{"2":{"3":1,"4":1}}],["libxalan",{"2":{"3":1,"4":1,"19":2}}],["libiberty",{"2":{"3":1,"4":1}}],["libicuuc",{"2":{"23":1}}],["libicu",{"2":{"3":1,"4":1,"7":1,"8":1,"10":1,"11":1}}],["libatlas",{"2":{"3":1,"4":2}}],["libaprutil1",{"2":{"3":1,"4":1}}],["libapr1",{"2":{"3":1,"4":1}}],["libarchive",{"2":{"3":1,"4":1,"7":1,"8":1,"10":1,"11":1,"12":1,"13":1,"23":4}}],["libv8",{"2":{"3":1,"4":1}}],["w20240815",{"2":{"460":15}}],["w20240526",{"2":{"458":4}}],["w=1",{"2":{"313":1}}],["wsfoobarpingresponse",{"2":{"487":2}}],["wsfoobarpingrequest",{"2":{"487":2}}],["wsfoobar",{"2":{"474":5,"486":4,"487":5,"488":3}}],["wsdl",{"2":{"471":2}}],["wsresources",{"2":{"340":3}}],["wsecl",{"2":{"301":1,"340":1}}],["wsworkunits",{"2":{"170":1,"340":1}}],["w3",{"2":{"274":1,"482":1,"486":1,"487":1,"488":1}}],["w",{"2":{"274":2}}],["wulistqueries",{"2":{"340":2}}],["wu",{"2":{"274":1}}],["wuid=w20240815",{"2":{"460":15}}],["wuid=w20240526",{"2":{"458":10}}],["wuid",{"2":{"192":1,"302":1}}],["wfstateskip",{"2":{"267":1}}],["wfstatereqd",{"2":{"266":1}}],["wfstatenull",{"2":{"266":1}}],["wfid=2",{"2":{"254":1}}],["wfid=1",{"2":{"254":1}}],["wfid=",{"2":{"254":3,"256":1,"266":3,"269":1,"274":4}}],["wfid",{"2":{"254":1,"255":2,"266":4,"267":3,"275":2}}],["wrapping",{"2":{"483":1,"486":1}}],["wrapped",{"2":{"187":1}}],["wrapper",{"2":{"37":3}}],["wrong",{"2":{"108":1,"245":1}}],["writing",{"0":{"298":1},"1":{"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1},"2":{"114":1,"192":1,"194":3,"198":1,"276":1,"280":1,"288":1,"298":1,"299":2,"326":2,"349":1,"468":1}}],["writes",{"2":{"196":1,"269":1}}],["write",{"0":{"281":1,"306":2,"468":1},"1":{"282":1,"283":1,"284":1,"285":1,"286":1},"2":{"108":1,"113":1,"196":1,"219":1,"226":1,"270":6,"277":1,"282":1,"285":1,"306":2,"309":2,"337":2,"338":3,"358":2,"359":3,"362":1,"375":1,"377":1,"460":2,"462":2,"468":1,"469":1}}],["written",{"2":{"77":1,"82":1,"109":1,"113":1,"223":1,"318":2,"326":1,"433":1,"482":1}}],["weight",{"2":{"308":1}}],["weighting",{"2":{"308":3,"311":1}}],["weirdness",{"0":{"95":1}}],["weird",{"2":{"73":1,"319":1}}],["week",{"2":{"246":1}}],["weekly",{"2":{"26":1}}],["weak",{"2":{"187":1}}],["welcome",{"2":{"107":1,"277":1}}],["well",{"2":{"27":1,"58":1,"87":1,"90":1,"111":1,"166":1,"169":1,"170":1,"187":1,"196":1,"198":1,"217":1,"219":1,"223":1,"245":1,"299":2,"309":1,"310":1,"327":1,"416":1,"448":1,"461":1}}],["weren",{"2":{"217":1}}],["were",{"2":{"69":1,"85":1,"128":1,"217":3,"299":1,"308":2,"309":2,"310":5,"317":1,"327":2,"330":2}}],["we",{"2":{"19":1,"53":1,"72":1,"99":1,"106":1,"107":1,"108":1,"128":1,"163":1,"192":1,"195":1,"196":4,"216":1,"217":1,"218":1,"219":3,"222":1,"223":1,"225":2,"226":2,"230":1,"241":2,"242":4,"245":2,"246":3,"247":1,"276":3,"277":1,"286":1,"299":3,"307":1,"308":14,"309":3,"310":6,"311":1,"313":2,"319":1,"321":5,"324":1,"326":8,"327":8,"328":3,"329":3,"340":2,"347":1,"446":1,"474":1}}],["website",{"0":{"334":1}}],["webpage",{"2":{"109":1}}],["web",{"2":{"15":1,"132":1,"263":1}}],["wake",{"2":{"328":1}}],["water",{"2":{"149":1}}],["watch",{"2":{"15":1,"31":1,"103":1,"214":2,"301":1,"340":5,"452":1}}],["waits",{"2":{"270":1}}],["waiting",{"2":{"149":1,"179":1,"185":2,"238":1,"251":1,"266":1,"318":1,"324":1,"330":1}}],["wait",{"2":{"124":2,"265":1,"266":1,"320":1,"327":1,"328":1,"330":1}}],["walkthrough",{"2":{"310":2}}],["walks",{"2":{"266":1,"270":1,"308":1,"313":1}}],["walked",{"2":{"87":1,"149":1}}],["walking",{"2":{"70":2}}],["walk",{"2":{"59":1,"70":3,"199":1,"249":1,"253":1,"264":1,"308":2}}],["wasm",{"2":{"353":2}}],["wasted",{"2":{"148":1,"152":1,"158":1,"318":2}}],["wasting",{"2":{"103":1}}],["was",{"2":{"56":2,"58":1,"70":1,"85":1,"95":1,"105":1,"108":1,"123":2,"145":4,"196":1,"217":2,"245":1,"266":1,"308":6,"309":1,"310":5,"312":1,"317":2,"318":1,"319":1,"321":1,"324":4,"326":1,"327":1,"328":1,"330":1,"497":1}}],["way",{"2":{"56":1,"58":1,"67":1,"70":1,"85":2,"95":1,"100":1,"108":1,"123":1,"128":1,"129":1,"174":2,"193":6,"196":5,"211":1,"233":1,"268":1,"276":1,"279":1,"283":1,"299":1,"308":1,"310":1,"311":1,"317":1,"321":1,"324":1,"326":1,"340":2,"435":1,"446":1,"450":1,"452":1}}],["ways",{"2":{"49":1,"64":1,"77":1,"163":1,"217":1,"264":1,"328":1,"333":1,"337":2,"407":1}}],["warn",{"2":{"485":1}}],["warning",{"2":{"112":1,"114":1,"221":1,"485":1}}],["warnings",{"2":{"36":1,"70":1,"402":1,"485":1}}],["warming",{"2":{"326":1}}],["warm",{"2":{"326":4}}],["warehouse",{"2":{"29":1}}],["wanting",{"2":{"325":1}}],["wanted",{"0":{"316":1},"1":{"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1}}],["wants",{"2":{"106":2,"267":1,"276":2,"318":1}}],["want",{"2":{"18":1,"56":1,"69":1,"124":1,"162":1,"195":1,"196":2,"233":2,"242":2,"308":3,"310":1,"326":1,"327":1,"358":2,"359":1,"376":2,"377":1,"378":1,"431":1}}],["won",{"2":{"159":1,"326":1,"340":1,"431":1,"446":1,"474":1}}],["worst",{"2":{"216":1}}],["worse",{"2":{"155":3}}],["worlds",{"2":{"216":1}}],["world",{"2":{"196":3,"216":1,"278":1,"338":1,"351":1,"371":2,"382":1}}],["wording",{"2":{"299":1}}],["word",{"0":{"305":1},"1":{"306":1,"307":1},"2":{"113":1,"252":1,"282":1,"300":1,"339":1}}],["words",{"2":{"85":1,"280":1,"299":3,"308":1,"339":1}}],["worth",{"2":{"106":1,"190":1,"270":1,"308":1,"311":1,"312":1}}],["workarounds",{"2":{"297":1}}],["worked",{"2":{"277":1}}],["worker",{"2":{"192":3,"324":2}}],["workers",{"2":{"192":1}}],["workflow>",{"2":{"254":2,"266":2,"274":2}}],["workflow",{"0":{"80":1,"254":1,"266":1,"267":1},"1":{"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1},"2":{"79":1,"80":5,"199":1,"238":3,"239":1,"242":2,"243":1,"250":1,"251":3,"254":11,"255":5,"266":23,"267":5,"271":1,"272":4,"336":1}}],["working",{"0":{"52":1,"111":1},"1":{"53":1,"54":1,"55":1,"56":1,"112":1,"113":1,"114":1,"115":1,"116":1,"117":1},"2":{"143":1,"245":1,"319":1,"320":1,"324":1,"482":1}}],["workuniteg1",{"2":{"269":3,"274":4}}],["workunit",{"0":{"250":1,"251":1,"253":1,"274":1,"453":1},"1":{"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"262":1,"263":1},"2":{"48":1,"79":2,"80":1,"82":5,"86":3,"96":1,"166":1,"170":2,"199":1,"249":11,"250":10,"251":5,"253":3,"254":2,"256":1,"258":5,"259":2,"260":2,"261":2,"262":2,"263":1,"264":14,"266":7,"269":2,"270":6,"271":2,"274":2,"302":1,"311":1,"318":1,"451":2,"452":2,"453":6,"455":1,"458":4}}],["workunits",{"0":{"78":1,"248":1},"1":{"79":1,"80":1,"81":1,"82":1,"83":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"262":1,"263":1,"264":1,"265":1,"266":1,"267":1,"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1},"2":{"31":1,"33":1,"53":1,"79":1,"166":1,"199":1,"251":2,"264":3,"265":5,"269":1,"302":2,"311":1,"455":1,"456":5,"458":1}}],["works",{"2":{"20":1,"58":1,"65":1,"73":1,"77":1,"102":1,"106":1,"111":1,"161":1,"198":1,"278":1,"309":1,"335":1}}],["work",{"0":{"320":1},"2":{"15":1,"47":1,"64":1,"70":4,"96":2,"103":1,"108":1,"170":1,"172":1,"194":2,"196":2,"236":1,"242":1,"245":1,"250":1,"254":1,"256":1,"266":2,"271":1,"308":2,"309":2,"310":1,"311":3,"318":2,"320":4,"321":1,"324":1,"326":1,"327":1,"340":2,"431":1,"439":1}}],["wouldn",{"2":{"58":1,"106":2,"299":1}}],["would",{"2":{"18":1,"58":1,"65":2,"66":2,"69":3,"84":3,"85":1,"90":1,"91":1,"102":1,"106":2,"116":1,"132":1,"145":1,"161":1,"184":2,"186":1,"190":1,"192":1,"196":5,"216":2,"234":1,"245":1,"306":1,"308":5,"310":3,"318":1,"319":1,"321":1,"324":1,"326":3,"327":5,"329":1,"331":1,"410":1,"452":1,"478":1,"492":2,"497":1}}],["whomever",{"2":{"340":1}}],["whole",{"2":{"217":1}}],["who",{"0":{"281":1},"1":{"282":1,"283":1,"284":1,"285":1,"286":1},"2":{"109":2,"169":1,"245":1,"270":1,"276":1,"278":1,"335":1}}],["what",{"0":{"308":1,"322":1},"2":{"58":1,"59":2,"79":1,"80":1,"81":4,"82":1,"103":3,"105":1,"106":3,"128":1,"149":1,"167":1,"179":1,"184":1,"190":1,"191":2,"192":2,"196":1,"201":1,"216":1,"219":1,"240":1,"249":1,"273":1,"278":2,"279":1,"280":1,"281":1,"290":2,"299":3,"308":5,"309":1,"310":1,"311":1,"324":1,"329":1,"330":1,"337":2,"339":1,"411":1,"415":1,"416":1,"419":1,"423":1,"427":1,"437":1,"441":1,"446":1,"448":1,"460":1}}],["whitespace",{"2":{"103":1,"313":1,"483":1,"484":1}}],["whilst",{"2":{"62":1,"65":1,"87":1}}],["while",{"2":{"26":1,"56":1,"72":1,"73":1,"81":1,"90":2,"114":1,"147":1,"159":1,"169":1,"186":1,"202":1,"218":1,"233":1,"249":1,"266":1,"268":1,"269":1,"297":1,"301":1,"309":2,"311":1,"326":2,"347":1,"400":1,"423":1,"446":1,"448":1,"473":1,"477":1}}],["whichever",{"2":{"162":1,"266":1,"267":1,"313":1,"319":1}}],["which",{"0":{"97":1},"2":{"6":1,"23":1,"27":1,"53":1,"56":3,"58":4,"59":3,"61":1,"62":1,"64":2,"67":1,"69":3,"72":1,"73":1,"75":1,"77":2,"79":1,"80":1,"81":2,"84":1,"91":1,"92":1,"95":2,"99":2,"103":1,"106":2,"110":1,"120":1,"123":1,"129":1,"132":1,"145":1,"149":5,"153":1,"155":2,"156":1,"160":3,"161":2,"162":1,"164":1,"170":1,"176":1,"187":2,"192":2,"193":1,"196":2,"214":1,"219":3,"223":1,"234":2,"238":1,"242":1,"245":3,"247":1,"249":5,"252":1,"254":3,"256":2,"266":2,"269":5,"270":3,"271":1,"273":1,"308":9,"309":2,"310":3,"311":1,"312":1,"313":3,"320":2,"321":1,"322":1,"323":1,"324":1,"326":1,"327":1,"329":2,"346":1,"347":1,"399":1,"400":1,"401":1,"403":2,"404":1,"407":1,"408":1,"410":2,"412":1,"420":1,"421":1,"423":2,"427":2,"431":3,"432":2,"436":3,"438":2,"441":2,"443":2,"444":1,"446":1,"450":1,"451":1,"474":1,"476":1,"478":2,"482":1,"485":1,"494":1,"498":1}}],["why",{"0":{"216":1,"232":1,"317":1},"1":{"233":1,"234":1,"235":1},"2":{"23":1,"56":1,"103":1,"105":1,"106":4,"108":1,"223":1,"278":1,"326":1,"329":1,"340":1}}],["whether",{"2":{"21":1,"50":1,"74":1,"75":1,"90":1,"91":1,"147":1,"156":1,"170":1,"172":1,"192":2,"209":1,"210":1,"234":1,"267":1,"269":1,"280":1,"310":1,"319":1,"327":2,"328":1,"336":1,"401":1,"408":1,"411":2,"412":1,"415":2,"419":1,"420":2,"424":2,"427":2,"428":2,"436":1,"439":1,"441":1,"451":1}}],["whenever",{"2":{"56":1,"58":1,"68":1,"163":1,"172":1,"178":1,"211":1}}],["when",{"2":{"18":1,"23":2,"44":2,"45":1,"50":1,"53":1,"56":2,"58":4,"64":2,"66":1,"72":2,"80":1,"82":1,"85":1,"87":1,"92":1,"103":1,"105":1,"106":2,"108":2,"109":1,"114":2,"129":1,"131":1,"133":1,"140":1,"143":2,"145":3,"149":4,"150":1,"155":1,"156":3,"157":2,"159":1,"160":1,"163":4,"164":1,"166":1,"174":1,"175":1,"180":1,"181":1,"183":1,"191":1,"192":1,"193":1,"196":4,"204":2,"209":1,"210":1,"211":1,"216":1,"217":1,"219":1,"220":2,"221":2,"233":1,"234":2,"242":1,"250":1,"251":1,"253":1,"254":2,"262":2,"265":1,"266":1,"267":4,"269":1,"270":2,"271":1,"277":1,"299":1,"300":1,"301":1,"302":2,"306":2,"307":1,"308":3,"309":2,"310":2,"311":1,"313":2,"318":3,"319":2,"320":2,"325":1,"326":4,"327":6,"328":2,"329":1,"330":2,"339":1,"342":1,"347":1,"397":1,"405":1,"407":1,"410":1,"420":1,"421":3,"425":1,"427":3,"428":2,"431":1,"432":3,"436":2,"437":5,"438":1,"441":2,"443":3,"448":2,"460":1,"474":1,"475":1,"476":1,"479":1,"480":4,"485":3,"491":1,"494":1,"496":1}}],["wherever",{"2":{"74":1,"75":1,"172":1,"187":1,"299":1}}],["where",{"0":{"319":1},"2":{"17":1,"36":1,"54":1,"56":1,"63":1,"64":1,"68":1,"74":1,"103":1,"108":1,"123":3,"128":1,"149":1,"183":1,"187":2,"190":1,"192":2,"193":2,"196":2,"204":6,"218":1,"245":1,"254":1,"282":1,"287":1,"308":2,"309":1,"310":1,"312":1,"313":2,"318":1,"319":1,"320":1,"321":1,"323":2,"326":1,"327":1,"329":1,"330":2,"339":1,"342":1,"346":1,"397":1,"399":1,"401":1,"421":1,"427":1,"437":2,"479":1,"482":1}}],["wget",{"2":{"16":3}}],["widget=wudetailswidget",{"2":{"458":5,"460":15}}],["widely",{"2":{"170":1}}],["wildcards",{"2":{"452":3}}],["will",{"2":{"6":1,"12":2,"18":1,"20":2,"21":2,"22":1,"23":2,"37":1,"44":2,"45":2,"51":1,"56":6,"58":1,"61":1,"66":2,"68":3,"70":3,"72":2,"76":1,"80":1,"82":1,"87":1,"88":1,"90":1,"92":2,"95":1,"99":1,"103":2,"104":1,"106":3,"108":1,"110":2,"112":2,"114":5,"123":4,"124":3,"128":4,"129":1,"143":1,"153":1,"158":1,"159":1,"163":1,"170":4,"183":1,"190":3,"192":2,"194":1,"196":5,"204":1,"218":2,"221":2,"238":2,"240":3,"241":2,"242":8,"243":2,"245":4,"246":4,"249":2,"252":2,"266":5,"267":4,"269":1,"270":7,"271":1,"272":1,"276":1,"277":1,"291":1,"301":1,"308":9,"310":6,"313":1,"318":4,"320":2,"324":1,"326":4,"327":2,"329":1,"330":2,"336":1,"346":2,"347":3,"397":1,"400":3,"402":1,"404":1,"407":2,"411":2,"412":3,"415":2,"419":2,"421":6,"423":1,"428":2,"431":3,"432":2,"436":1,"438":3,"440":1,"444":1,"446":1,"448":4,"449":2,"451":1,"461":2,"475":1,"479":2,"480":1,"482":3,"495":1,"496":1}}],["win32",{"2":{"394":2}}],["winflexbison3",{"2":{"13":1}}],["windows",{"0":{"13":1,"240":1,"394":1},"2":{"13":2,"123":1,"128":2,"129":1,"239":1,"240":2,"242":1,"249":1,"273":1,"342":1,"384":1,"394":5,"446":1}}],["without",{"2":{"18":1,"56":1,"58":1,"65":2,"83":1,"92":1,"108":1,"145":1,"175":1,"191":1,"196":1,"212":1,"247":3,"264":1,"270":1,"309":1,"310":1,"318":1,"320":1,"327":1,"330":2,"347":1,"358":1,"360":1,"397":1,"427":1,"432":1,"437":1,"438":2,"441":3,"448":2}}],["within",{"2":{"18":1,"50":1,"51":2,"55":1,"58":1,"60":1,"61":1,"65":2,"70":1,"72":1,"79":1,"83":1,"84":2,"85":1,"86":1,"87":1,"92":1,"94":1,"106":1,"129":1,"149":1,"160":1,"164":1,"170":3,"173":1,"190":2,"203":1,"218":1,"250":1,"251":1,"254":1,"266":1,"269":2,"270":4,"277":1,"308":1,"310":2,"311":1,"312":2,"320":1,"321":1,"326":1,"346":1,"349":1,"400":1,"403":1,"427":1,"432":1,"448":2,"451":2,"461":1,"483":1,"484":1,"490":1}}],["with",{"0":{"16":1,"111":1,"114":1,"339":1,"357":1,"358":1,"366":1,"368":1,"370":1,"371":1,"376":1,"377":1,"378":1},"1":{"19":1,"112":1,"113":1,"114":1,"115":1,"116":1,"117":1},"2":{"5":1,"12":1,"18":1,"20":1,"22":1,"23":1,"26":2,"28":1,"36":1,"37":2,"39":1,"42":4,"44":1,"48":1,"50":1,"51":1,"53":3,"55":1,"56":1,"58":3,"59":1,"62":1,"64":2,"65":2,"66":1,"68":2,"70":3,"73":3,"76":1,"77":1,"80":2,"83":1,"84":2,"85":2,"87":1,"95":1,"99":1,"104":1,"105":1,"108":1,"109":4,"111":1,"113":1,"114":1,"128":1,"129":1,"131":8,"132":2,"133":1,"143":2,"145":2,"148":1,"149":3,"153":1,"156":1,"157":2,"158":1,"159":1,"160":2,"163":2,"166":2,"170":2,"172":3,"178":1,"181":1,"183":1,"185":1,"186":2,"187":1,"188":1,"190":2,"192":1,"196":5,"198":2,"204":2,"212":1,"213":1,"217":3,"218":3,"219":4,"220":4,"221":1,"233":3,"234":1,"239":1,"240":1,"242":4,"245":1,"247":2,"249":2,"250":1,"253":1,"254":2,"255":2,"258":1,"261":1,"264":4,"266":2,"267":1,"269":2,"270":1,"271":3,"272":1,"274":1,"278":1,"279":2,"288":1,"289":1,"296":1,"297":1,"299":1,"302":1,"308":8,"309":5,"310":10,"311":4,"312":1,"313":4,"318":3,"319":1,"321":5,"322":1,"326":4,"327":6,"328":1,"330":1,"336":1,"338":6,"339":2,"340":4,"348":1,"349":5,"366":1,"367":1,"382":2,"385":1,"387":1,"388":1,"399":1,"400":3,"402":1,"403":2,"405":1,"406":1,"407":3,"410":2,"412":2,"414":3,"416":2,"419":1,"421":4,"424":1,"428":4,"432":2,"437":1,"438":6,"439":2,"441":2,"443":1,"446":3,"448":3,"449":1,"452":2,"462":1,"469":2,"470":1,"471":7,"473":2,"474":1,"477":1,"479":1,"482":1,"483":2,"490":1,"491":1,"496":1,"497":1}}],["wiki",{"2":{"0":1,"119":1,"122":1,"198":2,"334":2}}],["www2",{"2":{"353":2}}],["www1",{"2":{"353":2}}],["www",{"2":{"5":1,"274":1,"302":1,"353":4,"369":1,"447":2,"482":1,"486":1,"487":1,"488":1}}],["tcat",{"2":{"485":1}}],["tvaluetype",{"0":{"443":1},"2":{"445":1}}],["tserialprofile",{"0":{"442":1},"2":{"439":1}}],["ts=",{"2":{"262":1,"274":1}}],["tqfneedtransform",{"2":{"313":1}}],["tqfxxx",{"2":{"311":1}}],["tuning",{"2":{"318":1}}],["tutorial",{"0":{"296":1},"2":{"296":1}}],["tutorials",{"2":{"279":2}}],["turn",{"2":{"18":1,"92":1,"149":1,"160":1,"310":1,"324":1}}],["tgt",{"2":{"275":1,"311":1}}],["tgz",{"2":{"21":1}}],["ty3",{"2":{"270":2,"275":2}}],["ty2",{"2":{"270":2,"275":2}}],["ty1",{"2":{"270":2,"275":2}}],["typing",{"2":{"106":1,"123":2,"324":1}}],["typical",{"2":{"91":1,"266":1,"321":1,"326":1}}],["typically",{"2":{"26":1,"69":1,"80":1,"87":1,"88":1,"172":1,"218":1,"245":1,"249":3,"266":1,"282":1,"318":1,"329":1,"346":1,"448":1,"452":1}}],["typos",{"2":{"105":1,"108":1}}],["typo",{"2":{"103":1,"106":1}}],["typed",{"2":{"441":1}}],["typen",{"2":{"421":6}}],["type5",{"2":{"412":3}}],["type4",{"2":{"412":3}}],["type3",{"2":{"412":3}}],["type2",{"2":{"412":3,"421":6}}],["type1",{"2":{"412":5,"416":1,"421":6}}],["type=eclqueries",{"2":{"340":1}}],["type=roxie",{"2":{"340":1}}],["type=",{"2":{"256":1,"260":1,"261":2,"266":2,"269":1,"274":7,"487":9}}],["type=debug",{"2":{"129":1}}],["type>23",{"2":{"486":1,"487":1,"488":1}}],["type>",{"2":{"183":1,"486":1,"487":1,"488":1}}],["types",{"0":{"272":1,"466":1},"2":{"27":1,"58":1,"59":1,"87":1,"96":1,"108":1,"141":1,"170":1,"184":1,"185":1,"186":1,"187":2,"245":1,"256":1,"266":1,"270":1,"272":1,"349":1,"386":1,"402":1,"403":2,"410":3,"412":2,"421":3,"424":1,"425":1,"441":3,"443":4,"445":1,"460":2,"461":1,"462":2,"464":4,"465":1,"467":1,"469":1}}],["type",{"0":{"403":1,"409":1,"464":1,"465":1},"1":{"410":1,"411":1,"412":1,"465":1,"466":2},"2":{"18":1,"21":1,"58":4,"59":2,"69":2,"90":1,"94":1,"114":1,"133":1,"141":2,"149":1,"153":1,"169":1,"170":1,"183":5,"186":2,"187":3,"191":1,"204":2,"220":1,"241":1,"267":1,"302":1,"312":1,"313":1,"326":1,"337":1,"353":2,"356":1,"358":2,"386":1,"403":4,"404":4,"405":3,"412":5,"414":2,"416":1,"419":4,"420":1,"421":20,"424":2,"425":1,"428":2,"431":1,"432":5,"436":3,"437":1,"438":4,"439":1,"441":13,"443":5,"445":2,"472":1,"476":5,"478":1,"485":2,"486":1}}],["twice",{"2":{"267":1,"441":1,"443":1}}],["two",{"2":{"13":1,"26":1,"56":1,"58":1,"63":1,"65":1,"70":1,"76":1,"79":1,"88":1,"90":1,"128":3,"163":1,"170":1,"175":1,"184":1,"186":1,"188":1,"193":1,"203":1,"214":1,"234":1,"246":1,"249":1,"254":2,"256":1,"261":1,"267":1,"270":1,"282":1,"308":1,"309":1,"310":4,"311":1,"313":3,"321":1,"326":1,"412":1,"419":1,"421":2,"425":1,"450":1,"479":1}}],["tprofile",{"0":{"441":1},"2":{"442":3,"445":1}}],["tpp",{"2":{"218":1}}],["tplugin",{"0":{"440":1},"2":{"445":1}}],["tpl",{"2":{"54":1}}],["tlen",{"2":{"311":1}}],["tl4",{"2":{"270":2,"275":2}}],["tls",{"2":{"204":1}}],["tlk",{"2":{"191":2,"192":1}}],["tlb",{"2":{"163":2}}],["txsummary",{"2":{"170":1}}],["txt",{"2":{"36":1,"44":1,"353":1,"371":1}}],["tbd",{"2":{"108":1,"314":1,"315":1,"429":1}}],["tbb",{"2":{"7":1,"8":1,"11":1,"13":1}}],["t",{"2":{"50":1,"53":3,"58":3,"62":1,"64":1,"65":3,"67":1,"68":1,"69":2,"70":4,"74":1,"83":2,"84":1,"85":2,"88":1,"100":1,"103":3,"104":1,"106":4,"108":2,"112":1,"128":3,"145":1,"159":1,"160":2,"161":1,"162":1,"192":3,"194":1,"195":1,"196":1,"217":2,"218":1,"238":1,"266":1,"269":2,"275":7,"299":1,"311":1,"313":1,"318":1,"319":2,"320":1,"326":2,"327":3,"340":2,"342":1,"353":1,"357":2,"358":5,"359":1,"366":1,"376":2,"377":2,"378":2,"418":6,"422":2,"426":2,"428":2,"431":1,"440":3,"441":4,"443":4,"446":1,"458":1,"474":1,"479":1,"480":2}}],["tally",{"2":{"319":1}}],["talking",{"2":{"307":1}}],["tail",{"2":{"340":1}}],["tailor",{"2":{"81":1,"269":1,"280":1}}],["taiquantilearg",{"2":{"311":1}}],["taskqueue",{"2":{"366":1}}],["tasks",{"2":{"170":1,"336":1}}],["task",{"2":{"167":1,"170":2,"278":1,"308":1,"337":1,"339":2,"365":1,"366":1}}],["tab",{"2":{"222":1,"238":1,"239":1,"243":1,"304":1,"451":1}}],["tabs",{"2":{"108":1,"349":1}}],["table",{"2":{"62":4,"64":2,"66":3,"73":1,"106":1,"277":1,"308":1,"310":1,"321":1,"327":1,"337":2,"412":1,"421":3}}],["tables",{"2":{"62":1,"85":1,"452":1}}],["tagging",{"0":{"493":1,"496":1},"1":{"494":1,"495":1,"496":1,"497":1,"498":1,"499":1},"2":{"495":1,"496":1}}],["tagged",{"2":{"242":1,"245":2}}],["tag",{"2":{"106":3,"242":7,"243":3,"274":1,"335":2,"439":1}}],["tags",{"0":{"106":1},"2":{"104":1,"106":1,"238":2,"242":2,"335":1,"492":1}}],["taking",{"0":{"497":1},"2":{"320":1}}],["takindexread",{"2":{"270":1}}],["takquantile",{"2":{"311":1,"313":1}}],["takworkunitwrite",{"2":{"270":1}}],["taksplit",{"2":{"88":1}}],["taksort",{"2":{"88":1}}],["takes",{"2":{"76":1,"163":1,"183":1,"188":1,"247":1,"249":1,"251":1,"252":1}}],["taken",{"2":{"58":1,"59":1,"135":1,"137":1,"170":1,"210":1,"217":2,"335":1}}],["take",{"2":{"47":1,"53":1,"65":1,"88":1,"106":1,"108":1,"114":1,"133":1,"163":1,"166":1,"170":2,"172":1,"182":2,"196":1,"221":2,"234":1,"253":1,"269":1,"308":1,"309":1,"320":2,"405":1,"448":1,"474":1}}],["tarball",{"2":{"124":1}}],["target>",{"2":{"340":1}}],["targetactivity",{"2":{"269":1}}],["targetclustertype>",{"2":{"259":1,"274":1}}],["targetclustertype>hthor",{"2":{"259":1,"274":1}}],["target=",{"2":{"256":1,"269":4,"274":1,"486":8,"487":8,"488":8}}],["targeted",{"2":{"197":1,"288":1,"331":1}}],["targetted",{"2":{"110":1}}],["targets",{"2":{"42":1,"91":1,"196":1,"242":1,"311":1}}],["target",{"0":{"110":1,"458":1},"2":{"21":1,"34":1,"42":1,"44":4,"53":1,"90":3,"91":5,"92":3,"105":1,"108":1,"129":1,"196":2,"202":1,"245":1,"264":3,"269":5,"278":1,"280":1,"308":1,"327":1,"329":1,"340":3,"432":1,"460":2,"471":2}}],["tar",{"2":{"5":1,"16":6,"345":1}}],["t3",{"2":{"37":2}}],["t2",{"2":{"37":2}}],["t1",{"2":{"37":2}}],["tightly",{"2":{"402":1,"435":1}}],["tile",{"2":{"335":4}}],["tid",{"2":{"328":2}}],["timings",{"0":{"262":1},"2":{"262":2}}],["time64",{"2":{"466":1}}],["time32",{"2":{"466":1}}],["timeouts",{"2":{"327":1}}],["timeoutms",{"2":{"327":2}}],["timeout",{"0":{"362":1,"375":1},"2":{"169":1,"204":2,"327":3,"362":2,"375":3}}],["timely",{"2":{"103":1,"327":2}}],["timestamp",{"2":{"466":1,"480":1}}],["times",{"2":{"76":1,"83":1,"103":1,"170":1,"267":1,"268":1,"287":1,"324":1,"328":1,"329":1}}],["time",{"0":{"430":1},"1":{"431":1,"432":1},"2":{"12":1,"18":1,"36":1,"43":1,"50":1,"53":1,"62":1,"64":1,"69":1,"70":1,"83":1,"99":1,"103":1,"106":3,"114":1,"128":1,"131":1,"144":1,"149":1,"158":1,"162":1,"163":1,"170":5,"185":4,"189":1,"211":1,"216":1,"217":2,"247":1,"268":1,"270":3,"274":1,"286":1,"299":1,"317":1,"320":2,"322":1,"324":1,"326":2,"327":1,"328":2,"330":2,"336":1,"337":1,"399":2,"428":1,"458":1,"460":1,"466":1}}],["tip",{"2":{"113":1,"116":1,"197":1,"277":1}}],["tips",{"0":{"56":1,"280":1,"336":1},"1":{"337":1,"338":1,"339":1},"2":{"278":1,"333":1,"339":1}}],["tick",{"2":{"108":1}}],["tickinterval",{"2":{"26":1}}],["tied",{"2":{"407":1}}],["ties",{"0":{"97":1}}],["tie",{"2":{"51":1,"249":1}}],["title",{"2":{"26":1,"105":1,"221":1}}],["telephone",{"2":{"437":1}}],["tell",{"2":{"299":2,"451":1}}],["tech",{"2":{"245":1}}],["techniques",{"0":{"40":1},"1":{"41":1,"42":1,"43":1,"44":1},"2":{"278":1}}],["technical",{"2":{"26":1,"279":2,"280":1,"310":1}}],["technology",{"2":{"24":1}}],["teams",{"2":{"277":1}}],["team",{"2":{"106":1,"281":1,"308":1}}],["text>",{"2":{"486":2,"487":2,"488":2}}],["text2",{"2":{"371":1}}],["text1",{"2":{"371":1}}],["texts",{"2":{"336":1}}],["text",{"0":{"274":1},"2":{"87":1,"96":1,"108":1,"116":1,"175":1,"261":1,"288":1,"308":1,"310":2,"312":1,"326":1,"328":4,"337":2,"339":1,"348":1,"351":3,"353":6,"355":1,"357":1,"358":2,"369":2,"371":2,"372":2,"377":1,"386":2,"389":1,"399":1,"400":3,"403":1,"425":1,"427":1,"432":9,"439":1,"474":2,"482":1,"486":2,"487":2,"488":2}}],["temporarily",{"2":{"330":1}}],["temporaries",{"2":{"234":1}}],["temporary",{"2":{"87":3,"91":3,"192":1}}],["tempted",{"2":{"70":1,"310":1}}],["template>",{"2":{"486":1,"487":1,"488":1}}],["templatename",{"2":{"204":1}}],["templated",{"2":{"180":1,"444":1}}],["templates",{"2":{"87":1,"187":1,"219":1}}],["templatepath",{"2":{"54":1}}],["template",{"0":{"291":1},"1":{"292":1,"293":1,"294":1,"295":1,"296":1,"297":1},"2":{"36":1,"72":1,"87":1,"96":1,"187":4,"204":1,"218":1,"233":1,"275":1,"291":1,"337":2,"440":1,"441":1,"442":1,"443":1,"444":1,"471":2,"473":1,"486":1,"487":1,"488":1,"492":3}}],["tend",{"2":{"161":2,"163":1,"219":1,"312":1,"313":1}}],["tends",{"2":{"51":1,"56":1,"77":1,"148":1,"155":3,"268":1,"321":1}}],["tense",{"2":{"185":1,"280":1,"299":1}}],["tens",{"2":{"48":1}}],["terminating",{"2":{"327":1,"439":1}}],["termination",{"2":{"308":1}}],["terminated",{"2":{"157":1}}],["terminal",{"0":{"458":1},"2":{"241":1}}],["terms",{"0":{"300":1,"302":1,"303":1},"1":{"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1},"2":{"170":1,"217":1,"300":1,"303":1,"337":2}}],["term",{"0":{"79":1},"2":{"50":1,"63":1,"66":1,"70":1,"135":1,"204":1,"249":4,"299":1}}],["test=",{"2":{"486":2,"487":2,"488":2}}],["tested",{"2":{"63":1}}],["test",{"0":{"34":1,"309":1,"459":1,"460":1,"462":1,"463":1,"470":1},"1":{"460":1,"461":1,"463":1,"464":1,"465":1,"466":1},"2":{"34":1,"37":2,"44":4,"72":1,"124":1,"125":1,"128":2,"181":1,"194":1,"238":1,"308":1,"309":10,"319":1,"325":1,"349":2,"431":2,"456":2,"458":5,"460":25,"461":4,"462":3,"465":1,"470":2,"490":2,"491":1}}],["testing",{"0":{"125":1,"325":1,"464":1,"467":1},"1":{"126":1,"127":1,"128":1,"465":1,"466":1,"468":1},"2":{"18":1,"21":1,"31":1,"34":1,"53":1,"128":1,"181":1,"238":1,"309":2,"313":1,"314":1,"326":1,"460":1,"470":1}}],["tests",{"0":{"126":1,"127":1,"128":1,"286":1,"465":1,"469":1},"1":{"466":1},"2":{"18":1,"53":3,"126":2,"128":4,"217":1,"286":2,"309":7,"460":3,"461":1,"462":4,"467":1,"468":2,"470":1}}],["tr",{"2":{"482":1}}],["troubleshooting",{"0":{"297":1},"2":{"288":1,"332":1}}],["troubleshoot",{"2":{"278":1,"288":1,"332":1}}],["tricky",{"2":{"318":1}}],["tricks",{"2":{"278":1,"333":1}}],["trips",{"2":{"211":1}}],["tries",{"2":{"163":1,"242":1}}],["tried",{"2":{"100":1}}],["trigger",{"2":{"160":1,"167":1,"310":1,"474":1}}],["triggered",{"2":{"158":1,"254":1,"264":1,"266":1}}],["trivial",{"2":{"103":1,"106":1,"108":2,"158":1,"314":1,"319":1,"326":1,"497":1}}],["treat",{"2":{"328":1,"349":1}}],["treats",{"2":{"269":1}}],["treated",{"2":{"65":1,"100":1,"189":1,"194":1,"273":1,"440":1,"474":1}}],["trees",{"2":{"58":1}}],["tree",{"0":{"236":1},"2":{"37":1,"56":2,"58":1,"64":1,"66":1,"70":3,"72":2,"75":1,"87":2,"100":2,"308":1,"310":2,"346":2,"406":1,"440":1,"477":1}}],["traffic",{"2":{"325":1}}],["trained",{"2":{"321":1}}],["training",{"2":{"102":1,"339":1}}],["trailing",{"2":{"310":1,"483":1,"484":1}}],["trademark",{"2":{"301":1}}],["traditionally",{"2":{"321":1,"330":1}}],["traditional",{"2":{"217":1}}],["traversing",{"2":{"66":1,"149":1,"425":1,"427":1}}],["traceroxiepackets=1",{"2":{"325":1}}],["tracelevel=1",{"2":{"325":1}}],["trace",{"2":{"56":1,"328":1,"397":1,"402":2,"431":2,"441":1,"480":1,"485":1}}],["tracing",{"2":{"56":2,"108":2,"318":1,"328":2,"485":1}}],["tracked",{"2":{"106":1,"309":1,"326":1}}],["tracks",{"2":{"77":1,"174":1,"324":1}}],["tracking",{"2":{"56":1,"61":1,"169":1,"170":1,"319":2,"326":1}}],["track",{"2":{"48":1,"49":1,"56":1,"87":1,"90":1,"103":2,"155":1,"166":1,"169":3,"172":1,"186":1,"308":3,"310":2,"319":1,"320":1,"326":2}}],["transaction",{"2":{"486":2,"487":2,"488":2,"496":1}}],["transactionid",{"2":{"486":2,"487":2,"488":2}}],["transactional",{"2":{"402":1}}],["transactions",{"2":{"266":1}}],["transient",{"2":{"264":1}}],["transition",{"2":{"217":2,"324":1,"333":1}}],["transfer",{"0":{"359":1},"2":{"221":1}}],["transferring",{"2":{"196":1}}],["transform>",{"2":{"474":1,"487":1,"488":1}}],["transformer",{"2":{"74":1,"76":2,"77":1}}],["transformers",{"2":{"70":1}}],["transformed",{"2":{"63":1,"66":2,"70":1,"481":1}}],["transformsource=",{"2":{"474":1,"487":1,"488":1}}],["transforms",{"2":{"61":1,"66":1,"67":1,"70":1,"73":1,"84":2,"310":2,"312":1,"484":1}}],["transform",{"0":{"484":1},"2":{"49":1,"62":1,"66":1,"70":1,"75":2,"92":1,"249":1,"269":2,"270":1,"272":1,"275":1,"308":3,"309":1,"310":13,"311":1,"312":1,"313":3,"471":4,"474":3,"477":1,"482":1,"484":4,"486":1,"487":2,"488":2}}],["transformations",{"0":{"70":1},"2":{"50":1,"56":1,"70":2,"72":1,"73":1,"76":1}}],["transformation",{"2":{"28":1,"56":1,"64":1}}],["transforming",{"0":{"66":1},"2":{"28":1,"70":2,"73":1,"440":1}}],["translating",{"2":{"90":1}}],["translated",{"2":{"90":3,"91":1}}],["translate",{"2":{"51":1,"189":1,"327":1}}],["transparent",{"2":{"30":1,"163":1}}],["trusts",{"2":{"407":1,"441":1}}],["trusted",{"2":{"196":1,"446":1}}],["trunk",{"2":{"225":1}}],["trunk0trusty",{"2":{"22":1}}],["trueresult",{"2":{"267":1}}],["true",{"2":{"43":1,"62":1,"64":1,"72":1,"75":2,"155":1,"163":1,"191":1,"269":1,"275":1,"308":1,"310":1,"313":1,"327":1,"357":3,"358":2,"359":1,"371":1,"376":3,"377":1,"378":1,"382":1,"383":1,"385":1,"389":1,"419":1,"420":1,"424":1,"428":1,"439":1,"449":1,"451":1,"479":1,"486":1,"487":1,"488":1}}],["trying",{"2":{"56":1,"103":1,"149":1,"160":1,"310":1,"326":1,"327":1,"342":1}}],["try",{"2":{"15":1,"50":1,"70":1,"103":1,"106":1,"159":1,"233":1,"299":1}}],["thus",{"2":{"187":1}}],["thrown",{"2":{"221":1,"242":1,"319":1}}],["throughput",{"2":{"324":1}}],["throughs",{"2":{"308":1}}],["throughout",{"2":{"109":1,"187":2,"249":1,"300":1}}],["through",{"2":{"58":1,"59":1,"67":1,"69":1,"70":1,"75":1,"77":1,"91":1,"92":1,"93":2,"120":1,"141":1,"143":1,"155":1,"161":2,"169":1,"194":1,"196":1,"199":1,"202":1,"204":2,"211":1,"249":1,"250":1,"253":1,"258":1,"264":1,"290":1,"308":2,"310":2,"313":1,"318":1,"319":1,"403":1,"404":1,"474":1}}],["three",{"2":{"64":1,"140":1,"172":1,"187":1,"242":1,"400":3,"428":1,"432":1,"437":1,"451":1,"452":1,"477":1}}],["threadpool",{"2":{"365":1}}],["thread",{"0":{"365":1,"366":1},"2":{"83":1,"84":4,"105":2,"154":2,"155":1,"160":2,"234":1,"319":1,"324":2,"327":3,"328":1,"353":1,"365":4,"366":1}}],["threaded",{"2":{"28":1,"29":1,"317":1}}],["threads",{"2":{"20":1,"81":1,"83":1,"153":1,"158":1,"269":1,"270":2,"318":1,"324":1,"327":1,"328":2,"329":1}}],["thanks",{"0":{"396":1}}],["than",{"2":{"50":1,"54":1,"56":1,"58":2,"64":1,"65":1,"69":2,"84":1,"92":1,"100":1,"102":1,"103":1,"104":1,"105":1,"108":2,"150":1,"153":1,"155":2,"159":1,"160":1,"162":1,"170":1,"181":1,"192":1,"196":2,"221":1,"223":1,"234":1,"270":1,"287":1,"288":1,"299":1,"308":2,"309":1,"318":1,"320":1,"321":1,"323":1,"326":3,"327":5,"329":1,"330":1,"331":1,"342":1,"401":1,"414":1,"425":1,"432":1}}],["that",{"0":{"284":1,"285":1},"2":{"6":1,"13":1,"16":1,"18":1,"22":1,"23":1,"28":1,"30":1,"36":3,"37":1,"44":1,"47":1,"48":3,"49":1,"50":4,"51":1,"53":4,"54":1,"56":6,"58":3,"59":2,"61":2,"62":4,"63":4,"64":2,"65":3,"66":3,"67":3,"68":2,"69":2,"70":5,"72":5,"73":3,"74":3,"75":2,"76":1,"77":1,"79":1,"81":3,"82":2,"84":2,"85":3,"87":3,"88":5,"89":2,"90":3,"91":2,"93":1,"95":2,"96":1,"99":1,"101":1,"102":1,"105":1,"106":10,"108":7,"109":6,"110":1,"112":2,"113":2,"114":1,"122":1,"123":2,"128":3,"129":1,"131":2,"133":2,"135":1,"139":1,"142":1,"143":1,"144":1,"145":5,"148":1,"149":2,"150":1,"153":1,"154":1,"155":2,"156":1,"157":1,"159":1,"160":1,"161":1,"162":2,"163":2,"164":3,"166":2,"167":2,"169":1,"170":4,"172":6,"174":3,"175":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":2,"183":2,"184":1,"186":2,"187":7,"190":2,"191":4,"192":4,"194":1,"195":2,"196":5,"202":1,"203":1,"204":1,"209":1,"210":1,"211":2,"212":2,"213":1,"216":2,"217":4,"218":2,"219":1,"221":2,"222":1,"223":1,"233":1,"234":3,"238":1,"242":1,"245":8,"246":7,"247":1,"249":12,"250":3,"251":2,"252":2,"253":1,"254":2,"255":3,"256":1,"257":3,"258":2,"259":1,"260":1,"261":1,"264":1,"265":1,"266":11,"267":4,"268":2,"269":11,"270":11,"271":1,"272":6,"276":2,"278":1,"279":2,"280":1,"281":1,"282":1,"284":1,"285":1,"287":1,"291":1,"297":1,"299":3,"301":1,"307":2,"308":19,"309":17,"310":25,"311":4,"312":13,"313":10,"317":2,"318":5,"319":4,"320":4,"321":5,"322":1,"324":3,"326":12,"327":9,"328":7,"329":2,"330":9,"335":2,"337":1,"338":1,"339":3,"340":2,"345":1,"346":1,"347":4,"348":1,"397":3,"399":2,"402":2,"403":1,"405":1,"407":2,"410":2,"411":2,"414":3,"415":2,"416":1,"419":7,"420":2,"421":2,"423":1,"424":2,"427":1,"428":2,"431":3,"432":2,"435":2,"436":1,"438":4,"439":1,"441":1,"442":2,"443":2,"446":5,"448":11,"449":2,"450":2,"455":1,"462":1,"465":1,"473":2,"474":2,"476":1,"477":2,"478":1,"479":1,"480":1,"481":1,"482":2,"483":3,"484":2,"494":2,"496":2,"497":1}}],["thousand",{"2":{"280":1}}],["thousands",{"2":{"28":1,"29":1,"48":1,"99":1}}],["though",{"2":{"268":1,"310":1,"321":1,"324":1,"326":1,"328":1,"329":1,"330":1,"448":1,"451":1,"474":1}}],["thought",{"2":{"102":1,"103":1,"106":2,"108":1,"221":1,"480":1}}],["thoroughly",{"2":{"461":1}}],["thorcommon",{"2":{"312":2}}],["thorhelper",{"2":{"311":1}}],["thor200b",{"2":{"193":2}}],["thor200a",{"2":{"193":1}}],["thor200s",{"2":{"193":1}}],["thor400group",{"2":{"193":8}}],["thor400",{"2":{"193":9}}],["thortpl",{"2":{"87":1}}],["thoractivitykind",{"2":{"81":1,"269":1,"272":1,"311":1}}],["thor",{"0":{"28":1,"315":1},"2":{"27":3,"28":1,"33":1,"34":1,"148":2,"155":1,"157":2,"162":3,"164":1,"185":1,"190":2,"193":8,"196":2,"217":1,"249":2,"251":1,"264":1,"265":3,"266":2,"268":2,"273":1,"275":1,"288":2,"301":1,"308":1,"311":1,"317":2,"327":1,"332":2,"349":1}}],["those",{"2":{"12":1,"62":1,"77":1,"81":1,"87":1,"112":1,"149":1,"159":1,"161":1,"172":1,"192":1,"196":1,"217":2,"224":1,"245":1,"246":1,"250":1,"251":1,"254":1,"264":1,"266":2,"268":1,"269":2,"270":1,"282":1,"287":1,"290":1,"308":2,"310":2,"311":1,"312":1,"313":1,"318":1,"427":1,"435":1,"441":1,"448":1,"449":1,"450":1,"451":1,"470":1}}],["thing",{"2":{"128":1,"196":1,"302":1,"326":1}}],["things",{"2":{"70":2,"72":1,"108":1,"192":1,"299":1,"333":1,"446":1}}],["thinks",{"2":{"441":1}}],["thinking",{"2":{"310":1}}],["think",{"2":{"66":1,"192":1,"196":4,"308":1,"326":1,"338":1}}],["thirdly",{"2":{"326":1}}],["third",{"0":{"14":1},"1":{"15":1},"2":{"12":1,"122":1,"172":1,"192":1,"214":1,"250":1,"408":1,"416":1,"421":1,"446":2,"449":1,"450":1}}],["this",{"0":{"79":3},"2":{"5":1,"18":1,"20":1,"44":2,"45":1,"49":1,"50":1,"53":4,"54":1,"56":2,"58":3,"59":4,"60":1,"61":1,"62":2,"64":3,"65":2,"66":3,"67":2,"69":3,"70":1,"72":2,"75":3,"76":1,"77":3,"79":2,"84":1,"85":2,"87":2,"89":1,"90":3,"91":1,"92":1,"95":1,"99":3,"101":1,"102":1,"103":6,"104":1,"105":2,"106":1,"108":2,"109":1,"112":1,"113":1,"114":4,"123":3,"124":1,"128":5,"131":2,"132":3,"133":1,"135":1,"136":1,"140":1,"143":1,"145":1,"146":1,"148":2,"149":4,"152":2,"153":1,"155":1,"156":2,"157":1,"159":1,"161":2,"162":1,"163":5,"164":1,"166":1,"168":1,"170":4,"172":1,"181":1,"183":1,"184":1,"186":1,"190":4,"191":2,"192":6,"196":8,"197":1,"201":1,"204":1,"209":4,"210":2,"214":1,"219":1,"221":1,"222":1,"233":4,"234":4,"235":1,"238":3,"240":1,"241":2,"243":1,"246":1,"247":1,"249":5,"250":2,"251":2,"252":1,"253":2,"254":3,"256":1,"261":1,"264":1,"266":5,"267":7,"268":1,"269":5,"270":5,"272":5,"276":2,"278":3,"279":2,"282":1,"283":1,"284":2,"285":1,"288":5,"289":2,"291":1,"292":1,"298":1,"299":6,"308":17,"309":1,"310":18,"311":9,"312":4,"313":8,"318":3,"320":3,"321":1,"324":2,"325":1,"326":8,"327":5,"328":4,"329":2,"330":6,"331":2,"335":1,"338":1,"339":1,"346":1,"347":1,"384":1,"393":1,"396":1,"397":1,"399":1,"400":3,"402":1,"406":1,"407":2,"408":3,"410":2,"411":1,"412":2,"414":3,"415":1,"416":3,"419":2,"420":1,"421":2,"424":1,"428":3,"431":2,"432":1,"433":2,"437":1,"438":1,"439":4,"440":1,"441":12,"443":1,"444":1,"446":4,"448":3,"449":2,"450":1,"451":1,"452":2,"460":1,"461":1,"473":1,"474":1,"476":1,"479":2,"480":1,"482":2,"485":1,"486":1,"490":3,"492":2,"498":1}}],["theory",{"0":{"448":1},"2":{"441":1}}],["their",{"2":{"51":1,"67":1,"76":1,"109":2,"126":1,"131":1,"167":1,"170":1,"174":2,"216":1,"221":2,"238":1,"260":1,"279":1,"308":1,"309":1,"311":1,"313":1,"318":1,"320":2,"400":1,"410":1,"414":1,"424":1,"443":1,"455":1,"479":1,"490":1}}],["themselves",{"2":{"222":1,"450":1}}],["them",{"2":{"51":3,"58":1,"74":1,"84":1,"103":3,"106":1,"108":1,"126":1,"131":1,"157":2,"161":1,"163":2,"164":1,"174":1,"221":2,"251":1,"263":1,"266":1,"268":1,"270":1,"299":2,"308":2,"310":2,"313":1,"318":1,"319":1,"320":1,"321":1,"322":1,"327":1,"349":1,"400":1,"424":1,"441":1,"449":1,"473":1,"479":1}}],["therefore",{"2":{"149":1,"160":1,"175":1,"196":1,"282":1,"308":1,"311":1,"312":1,"318":1,"319":1,"320":1}}],["there",{"2":{"48":1,"49":2,"51":1,"53":1,"54":1,"56":3,"58":7,"62":2,"63":2,"64":3,"69":6,"73":2,"76":1,"79":1,"81":1,"83":1,"84":2,"85":2,"90":1,"91":4,"92":1,"99":1,"103":2,"105":1,"106":3,"108":2,"112":1,"120":1,"128":2,"130":1,"131":1,"140":1,"144":1,"153":1,"156":1,"158":3,"160":1,"162":1,"163":1,"169":1,"170":2,"179":1,"183":1,"187":2,"191":1,"193":1,"195":1,"196":3,"214":1,"216":1,"217":1,"218":1,"219":1,"220":1,"221":1,"238":2,"243":1,"245":1,"246":1,"249":2,"254":1,"255":1,"261":1,"264":1,"266":2,"269":2,"270":1,"272":1,"277":1,"281":1,"284":1,"287":2,"300":1,"308":7,"310":6,"311":3,"312":1,"313":1,"318":2,"319":1,"320":2,"321":1,"322":1,"323":1,"324":1,"326":5,"327":11,"328":1,"329":1,"330":2,"340":2,"342":2,"358":1,"400":1,"423":1,"427":1,"477":1,"496":1}}],["then",{"2":{"37":1,"50":1,"53":1,"56":3,"58":1,"60":1,"68":1,"70":1,"72":2,"74":2,"77":2,"84":1,"85":1,"91":2,"92":4,"100":1,"103":1,"104":4,"106":1,"108":3,"112":4,"115":1,"123":1,"124":1,"129":1,"131":2,"140":1,"148":1,"153":1,"154":1,"157":1,"160":1,"161":2,"163":1,"167":1,"170":2,"172":2,"174":1,"191":1,"195":1,"196":1,"221":1,"239":1,"241":1,"242":3,"245":1,"246":1,"249":1,"250":1,"251":1,"254":1,"264":3,"266":2,"267":1,"268":1,"270":3,"272":1,"285":2,"308":5,"310":2,"312":1,"313":2,"318":1,"320":4,"326":2,"329":1,"330":2,"340":2,"347":1,"447":1,"449":1,"451":2,"474":1,"482":2,"483":1,"495":1,"497":1}}],["they",{"2":{"31":1,"49":2,"58":5,"63":2,"69":4,"73":2,"74":2,"76":1,"82":1,"84":2,"93":1,"94":1,"100":1,"103":4,"104":1,"106":4,"108":1,"109":2,"110":1,"128":1,"150":1,"157":1,"161":1,"163":2,"190":1,"191":1,"192":2,"194":2,"195":1,"196":1,"218":1,"221":1,"233":2,"245":3,"247":1,"249":1,"251":1,"256":1,"261":1,"264":1,"266":2,"269":1,"270":2,"271":1,"308":1,"309":7,"310":2,"312":1,"313":1,"318":2,"319":1,"320":1,"321":1,"324":3,"326":1,"329":2,"330":7,"338":1,"403":1,"404":1,"474":1,"477":1,"479":2,"480":3,"495":2,"496":1}}],["these",{"2":{"15":1,"18":1,"23":1,"26":1,"27":1,"48":1,"58":1,"64":2,"65":1,"69":1,"70":1,"73":1,"81":1,"82":1,"85":1,"88":1,"92":1,"102":1,"106":1,"131":2,"140":1,"146":1,"150":1,"157":1,"160":2,"163":1,"169":2,"170":2,"172":1,"175":2,"190":1,"191":1,"193":1,"196":1,"197":1,"201":1,"202":2,"204":2,"214":1,"216":1,"230":1,"239":1,"242":1,"249":3,"256":1,"260":1,"265":1,"267":1,"269":7,"273":1,"276":1,"280":1,"299":2,"303":1,"310":3,"311":3,"312":1,"319":1,"322":1,"326":2,"327":1,"328":1,"330":2,"336":3,"338":1,"339":1,"345":1,"348":1,"353":1,"396":1,"412":1,"420":1,"428":2,"441":1,"460":2,"470":1,"475":2,"480":4,"486":1}}],["the",{"0":{"22":1,"52":1,"53":1,"54":1,"79":1,"97":1,"100":1,"116":1,"117":1,"120":1,"121":1,"123":1,"125":1,"129":1,"141":1,"147":1,"199":1,"236":1,"240":1,"251":1,"269":1,"270":1,"274":1,"275":1,"276":1,"282":1,"285":1,"286":2,"310":1,"311":1,"312":1,"319":1,"322":1,"341":1,"366":1,"458":1,"459":1,"460":1,"466":1},"1":{"53":1,"54":1,"55":1,"56":1,"122":1,"123":1,"126":1,"127":1,"128":1,"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"342":1,"460":1,"461":1},"2":{"5":2,"6":1,"12":5,"13":3,"15":1,"16":3,"18":16,"19":1,"20":1,"21":5,"22":5,"23":6,"24":1,"26":3,"27":4,"28":2,"29":2,"30":2,"31":1,"33":12,"36":4,"37":3,"44":6,"45":3,"47":3,"48":5,"49":11,"50":9,"51":11,"53":26,"54":6,"55":6,"56":34,"58":31,"59":19,"60":6,"61":8,"62":2,"63":6,"64":31,"65":18,"66":13,"67":8,"68":12,"69":17,"70":25,"72":17,"73":10,"74":2,"75":9,"76":6,"77":6,"79":12,"80":9,"81":28,"82":10,"83":15,"84":12,"85":12,"86":10,"87":15,"88":7,"89":7,"90":13,"91":10,"92":12,"93":7,"94":6,"95":7,"96":6,"99":7,"100":12,"101":2,"102":6,"103":33,"104":10,"105":12,"106":49,"107":4,"108":42,"109":10,"110":4,"112":10,"113":1,"114":17,"115":6,"116":9,"117":4,"119":3,"120":6,"122":6,"123":15,"124":8,"125":3,"126":3,"128":30,"129":6,"130":4,"131":16,"132":7,"133":9,"134":2,"135":7,"136":2,"137":2,"138":2,"139":6,"140":10,"141":9,"142":1,"143":20,"144":7,"145":11,"146":5,"147":5,"148":8,"149":20,"150":3,"152":5,"153":6,"154":4,"155":10,"156":11,"157":4,"158":3,"159":6,"160":10,"161":8,"162":5,"163":19,"164":7,"166":7,"167":8,"168":1,"169":11,"170":18,"171":1,"172":56,"173":3,"174":17,"175":11,"176":9,"177":1,"178":6,"179":15,"180":11,"181":10,"182":7,"183":17,"184":4,"185":3,"186":11,"187":43,"188":8,"189":8,"190":20,"191":19,"192":28,"193":12,"194":6,"195":14,"196":43,"197":2,"198":1,"199":4,"200":2,"201":4,"202":5,"203":3,"204":21,"205":2,"206":1,"209":9,"210":3,"211":23,"212":10,"213":10,"214":11,"216":4,"217":16,"218":6,"219":7,"220":3,"221":14,"222":3,"223":2,"224":3,"225":1,"229":1,"230":1,"232":1,"233":11,"234":14,"238":12,"239":7,"240":7,"241":8,"242":22,"243":9,"245":8,"246":13,"247":10,"249":43,"250":14,"251":13,"252":9,"253":3,"254":14,"255":12,"256":8,"257":6,"258":6,"259":5,"260":5,"261":8,"262":7,"263":1,"264":40,"265":3,"266":41,"267":41,"268":7,"269":77,"270":88,"271":14,"272":20,"273":5,"274":9,"276":5,"277":11,"278":12,"279":5,"280":6,"281":2,"282":6,"283":8,"285":3,"286":4,"287":2,"288":2,"289":5,"290":5,"291":1,"292":1,"293":2,"294":1,"295":2,"296":1,"297":1,"298":2,"299":13,"300":2,"301":10,"302":8,"303":1,"306":2,"307":1,"308":109,"309":51,"310":111,"311":66,"312":33,"313":71,"314":1,"315":2,"317":8,"318":20,"319":10,"320":20,"321":25,"322":5,"323":8,"324":16,"326":47,"327":35,"328":13,"329":12,"330":21,"331":2,"333":3,"335":29,"336":2,"337":10,"338":3,"339":21,"340":33,"342":4,"345":2,"346":10,"347":6,"348":6,"349":8,"353":3,"358":2,"359":1,"360":2,"365":2,"376":2,"377":1,"378":1,"386":2,"393":1,"397":12,"399":6,"400":9,"401":5,"402":6,"403":10,"404":7,"405":5,"406":7,"407":15,"408":5,"410":5,"411":10,"412":13,"414":7,"415":8,"416":16,"419":22,"420":20,"421":21,"423":8,"424":8,"425":10,"427":15,"428":20,"431":6,"432":22,"433":2,"435":4,"436":10,"437":14,"438":5,"439":5,"440":7,"441":37,"442":1,"443":18,"444":8,"445":1,"446":16,"447":3,"448":31,"449":16,"450":3,"451":24,"452":11,"455":7,"456":3,"460":9,"461":10,"462":1,"465":2,"470":2,"471":3,"472":3,"473":14,"474":22,"475":2,"476":4,"477":8,"478":7,"479":8,"480":18,"481":4,"482":13,"483":5,"484":3,"485":9,"486":7,"487":1,"488":1,"490":3,"491":7,"492":7,"494":6,"495":4,"496":6,"497":3,"498":1,"499":1}}],["toy",{"2":{"396":1}}],["touching",{"2":{"326":1}}],["towards",{"2":{"309":1,"317":1,"321":1}}],["toward",{"2":{"288":1}}],["toxml",{"2":{"275":1}}],["tony",{"2":{"263":1}}],["today",{"2":{"217":1,"446":1}}],["todo",{"2":{"128":1}}],["totally",{"2":{"396":1}}],["total",{"2":{"169":1,"172":1,"178":1,"191":2,"378":3,"421":1}}],["totals",{"2":{"169":1}}],["tokenerrorresponse",{"2":{"449":1}}],["tokenresponse",{"2":{"449":1}}],["tokens",{"2":{"132":1,"192":1,"310":4,"439":3,"446":1,"448":5,"450":2}}],["token",{"2":{"85":1,"131":2,"132":1,"133":1,"134":1,"239":2,"310":1,"379":3,"380":2,"425":4,"439":3,"446":1,"448":10,"449":4,"450":1,"451":1}}],["together",{"0":{"97":1,"318":1},"2":{"58":1,"81":1,"172":1,"176":1,"249":2,"253":1,"254":1,"256":1,"269":1,"273":1,"318":1}}],["topology",{"0":{"322":1},"2":{"320":1,"322":3,"326":1,"330":5}}],["topic",{"2":{"209":1,"308":1,"337":2}}],["topn",{"2":{"76":1}}],["top",{"2":{"44":3,"149":1,"335":5,"446":1}}],["took",{"2":{"308":1}}],["tool",{"0":{"283":1,"489":1},"1":{"490":1,"491":1,"492":1},"2":{"31":1,"104":1,"123":1,"131":1,"204":1,"238":1,"283":1,"302":1,"333":1,"471":1,"473":3,"474":4,"475":1,"476":1,"477":1,"478":1,"481":1,"482":2,"490":1,"491":2,"492":1}}],["tools",{"0":{"23":1,"341":1},"1":{"342":1},"2":{"18":1,"27":1,"85":1,"122":1,"131":1,"240":1,"301":1,"333":1,"342":1,"494":1,"495":1}}],["too",{"2":{"23":1,"100":2,"103":1,"105":2,"106":1,"145":1,"195":1,"196":1,"234":1,"308":1,"310":1,"320":1,"321":1,"324":1,"326":1,"327":2,"339":1}}],["to",{"0":{"5":1,"23":1,"50":1,"99":1,"116":1,"276":1,"285":1,"316":1,"320":1,"339":1,"341":1,"352":1,"396":1},"1":{"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1,"342":1},"2":{"0":1,"5":1,"12":4,"13":6,"15":3,"16":1,"17":1,"18":15,"19":2,"20":2,"21":4,"23":8,"24":2,"27":2,"28":1,"29":1,"31":5,"32":3,"33":1,"36":2,"37":3,"38":4,"39":2,"44":2,"45":2,"47":1,"48":9,"49":3,"50":6,"51":3,"53":8,"54":3,"55":1,"56":15,"58":13,"59":7,"60":2,"61":5,"63":3,"64":11,"65":14,"66":4,"67":4,"68":2,"69":11,"70":15,"72":7,"73":10,"74":3,"75":5,"76":4,"77":4,"79":2,"80":2,"81":5,"82":1,"83":6,"84":7,"85":7,"86":1,"87":6,"88":1,"89":3,"90":9,"91":6,"92":3,"93":4,"95":4,"96":2,"99":5,"100":3,"102":1,"103":19,"104":4,"105":1,"106":33,"107":1,"108":21,"109":5,"110":1,"111":1,"112":6,"113":4,"114":9,"115":6,"116":8,"120":3,"122":1,"123":4,"124":6,"126":1,"128":14,"129":4,"130":2,"131":10,"132":5,"133":7,"135":4,"136":1,"139":2,"140":1,"141":3,"142":3,"143":10,"144":1,"145":2,"146":1,"147":2,"148":5,"149":13,"150":2,"152":2,"153":1,"154":2,"155":5,"156":2,"157":11,"158":3,"159":5,"160":8,"161":5,"162":5,"163":12,"164":5,"166":8,"168":1,"169":7,"170":12,"171":1,"172":11,"174":13,"175":4,"176":2,"178":1,"179":1,"180":3,"181":3,"182":3,"183":3,"184":2,"186":7,"187":17,"188":1,"189":3,"190":6,"191":6,"192":12,"193":1,"194":6,"195":5,"196":62,"198":2,"201":1,"203":2,"204":10,"205":3,"206":1,"207":1,"208":1,"209":2,"211":11,"214":6,"216":2,"217":10,"218":2,"219":3,"220":2,"221":12,"222":2,"223":1,"232":1,"233":8,"234":6,"238":5,"239":4,"240":5,"241":5,"242":11,"243":1,"245":9,"246":4,"247":4,"249":18,"250":4,"251":5,"252":1,"254":7,"255":2,"256":1,"259":2,"260":1,"263":2,"264":16,"265":7,"266":11,"267":9,"268":3,"269":17,"270":22,"271":5,"272":10,"273":4,"274":1,"276":7,"277":6,"278":4,"279":5,"283":2,"285":1,"287":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"296":1,"298":1,"299":10,"300":3,"301":4,"302":1,"306":1,"307":8,"308":51,"309":15,"310":46,"311":20,"312":17,"313":25,"315":1,"317":4,"318":10,"319":6,"320":10,"321":17,"322":6,"323":6,"324":8,"325":3,"326":27,"327":32,"328":10,"329":9,"330":20,"331":4,"332":1,"333":4,"335":12,"336":1,"337":12,"338":6,"339":13,"340":15,"342":3,"345":1,"346":5,"347":11,"348":1,"349":2,"350":1,"352":1,"353":5,"358":2,"359":1,"365":1,"376":2,"377":1,"378":1,"386":1,"396":2,"397":5,"399":6,"400":4,"401":3,"402":7,"403":5,"404":3,"405":4,"406":1,"407":5,"408":4,"410":6,"411":5,"412":5,"414":4,"415":5,"416":7,"419":10,"420":10,"421":3,"423":6,"424":4,"425":3,"427":7,"428":10,"431":9,"432":7,"433":2,"435":2,"436":3,"437":5,"438":6,"439":8,"440":2,"441":19,"443":8,"444":2,"446":6,"447":1,"448":11,"449":4,"450":5,"451":16,"452":2,"453":6,"454":6,"455":2,"458":1,"460":5,"461":3,"468":1,"470":1,"471":2,"473":5,"474":10,"476":6,"477":2,"478":4,"480":4,"481":2,"482":7,"483":1,"484":1,"485":1,"486":2,"487":1,"488":1,"490":3,"491":1,"492":6,"494":3,"495":1,"496":3,"497":1}}],["oauth",{"2":{"446":2}}],["oic",{"0":{"449":1},"2":{"446":3,"448":4,"449":2,"450":2}}],["oitems",{"2":{"41":2}}],["omisssion",{"2":{"441":1}}],["omission",{"2":{"441":4}}],["omit",{"2":{"431":1,"438":1,"477":1,"486":1,"487":1,"488":1}}],["omitting",{"2":{"128":1,"431":1}}],["omitted",{"2":{"64":1,"290":1,"425":1,"432":1,"437":1,"439":1,"449":1}}],["octet",{"2":{"371":1}}],["occasion",{"2":{"308":1}}],["occasional",{"2":{"245":1}}],["occasionally",{"2":{"26":1,"110":1,"161":1,"245":1,"311":1}}],["occupies",{"2":{"270":1}}],["occurrence",{"2":{"405":1,"425":1,"439":1}}],["occurrences",{"2":{"178":1,"403":2,"423":2}}],["occurred",{"2":{"75":1}}],["occurs",{"2":{"72":1,"95":1,"404":1}}],["occur",{"2":{"51":1,"72":1,"88":1,"247":1,"251":1,"254":1,"339":1}}],["o",{"2":{"240":2,"432":8}}],["owenership",{"2":{"234":1}}],["ownedx",{"2":{"221":1}}],["owned",{"2":{"172":1,"221":3,"234":1,"269":1,"275":1,"327":5}}],["ownership",{"2":{"59":1,"170":3,"221":6,"234":1}}],["owns",{"2":{"149":1}}],["own",{"2":{"106":1,"108":1,"126":1,"136":1,"205":1,"216":1,"238":1,"246":1,"308":1,"419":1}}],["o3",{"2":{"221":1}}],["o2",{"2":{"221":1}}],["odd",{"2":{"217":1,"330":1}}],["obfuscastes",{"2":{"427":1}}],["obfuscacation",{"2":{"410":1}}],["obfuscated",{"2":{"402":1,"404":1,"405":1,"410":1,"419":3,"421":9,"425":1,"431":1}}],["obfuscates",{"2":{"400":3,"427":2}}],["obfuscate",{"2":{"397":1,"419":2,"427":2,"428":1}}],["obfuscation",{"2":{"397":3,"399":3,"400":7,"402":1,"403":2,"404":2,"407":4,"408":1,"410":2,"419":9,"420":3,"421":5,"423":1,"424":2,"427":6,"428":7,"431":1}}],["obtain",{"2":{"187":1,"270":1,"272":1,"313":1,"407":3,"420":2}}],["obtained",{"2":{"61":1,"62":1,"67":1,"167":1}}],["observation",{"2":{"106":1}}],["obvious",{"2":{"103":2,"106":1,"278":1,"450":1}}],["objective",{"2":{"461":1}}],["object",{"2":{"58":1,"79":3,"149":1,"167":1,"183":1,"187":1,"211":2,"221":1,"234":2,"266":1,"270":2,"312":1,"313":1,"327":3,"329":1}}],["objects",{"0":{"234":1},"2":{"58":2,"123":1,"172":1,"187":1,"205":1,"221":2,"234":1,"249":1,"268":1,"273":1,"312":1,"323":2,"330":2,"420":1}}],["older",{"2":{"110":1,"217":1,"245":1,"311":1}}],["old",{"2":{"49":1,"70":1,"160":1,"190":1,"217":2,"310":1,"324":1,"340":1}}],["ouput",{"2":{"270":1,"473":1}}],["ou",{"2":{"240":1}}],["ou=aaddc",{"2":{"204":2}}],["ou=modules",{"2":{"204":1}}],["ou=groups",{"2":{"204":1}}],["ou=ecl",{"2":{"204":3}}],["ou=files",{"2":{"204":1}}],["our",{"2":{"45":1,"217":1,"219":1,"221":1,"226":1,"238":1,"242":2,"254":1,"256":1,"260":1,"261":1,"269":2,"270":1,"276":2,"308":2,"331":1}}],["outfile",{"2":{"485":1}}],["outright",{"2":{"397":1}}],["outcome",{"2":{"411":2,"450":1,"482":1}}],["outcomes",{"2":{"348":1,"470":1}}],["outcode",{"2":{"105":1}}],["outbound",{"2":{"329":1}}],["outstanding",{"2":{"326":1}}],["outside",{"2":{"122":1,"181":1,"473":1,"482":1}}],["outer",{"2":{"77":1}}],["outputtype",{"2":{"476":3,"485":1}}],["outputtype=",{"2":{"474":1}}],["outputting",{"2":{"313":1,"326":1}}],["outputs",{"2":{"242":1,"252":1,"270":1,"278":1,"319":1,"400":1,"474":1}}],["output",{"0":{"87":1,"243":1,"458":1,"486":1},"1":{"487":1,"488":1},"2":{"48":1,"56":4,"62":2,"68":3,"73":1,"128":3,"238":1,"240":2,"241":3,"252":2,"256":2,"261":1,"269":5,"270":3,"274":3,"275":1,"308":2,"310":3,"311":2,"313":2,"318":1,"319":2,"346":1,"348":1,"397":1,"400":2,"402":3,"406":1,"431":1,"436":1,"441":1,"471":1,"472":1,"473":4,"474":5,"475":1,"476":9,"477":2,"478":4,"479":1,"481":1,"482":5,"483":2,"484":1,"485":11,"486":5,"487":3,"488":3}}],["outlines",{"2":{"253":1}}],["outlined",{"2":{"184":1,"270":1}}],["outline",{"2":{"33":1,"251":1,"270":1,"279":1,"311":1}}],["out",{"2":{"22":1,"23":1,"37":1,"51":1,"56":2,"59":1,"73":1,"76":1,"103":1,"106":3,"108":1,"127":1,"128":2,"217":1,"218":1,"240":5,"269":1,"275":2,"308":2,"310":1,"319":1,"323":1,"324":1,"325":2,"329":1,"330":2,"336":1,"391":1,"492":1,"495":1}}],["overwritten",{"2":{"480":1}}],["overwrite",{"2":{"460":2,"462":2}}],["overridedefault",{"2":{"436":1}}],["override",{"0":{"366":1},"2":{"224":1,"233":4,"366":2,"402":1,"408":2,"414":1,"474":1,"479":1}}],["overrides",{"2":{"204":1,"485":1}}],["overloads",{"2":{"214":1}}],["overload",{"2":{"170":1}}],["overlap",{"2":{"51":1,"311":1}}],["overused",{"2":{"135":1}}],["overnight",{"2":{"128":1}}],["overflow",{"2":{"105":1,"108":1}}],["overall",{"2":{"104":1,"170":2,"172":1,"278":1}}],["overhead",{"2":{"92":1,"153":1,"268":1,"270":2,"327":1}}],["over",{"0":{"42":1},"2":{"59":1,"62":1,"75":1,"76":1,"161":1,"192":1,"194":2,"196":1,"204":1,"299":1,"310":1,"320":3,"322":1,"399":1,"402":1}}],["overview",{"0":{"284":1,"292":1,"410":1,"414":1,"419":1,"423":1,"427":1,"431":1,"463":1,"490":1},"2":{"33":1,"249":1,"278":1,"283":1,"333":1}}],["oss",{"2":{"128":2}}],["os",{"2":{"23":1,"36":4,"146":1,"163":1,"249":1,"273":1}}],["originated",{"2":{"425":1}}],["originally",{"2":{"310":1,"312":1,"327":1,"448":1}}],["original",{"2":{"70":1,"81":2,"90":1,"95":1,"103":1,"106":1,"145":1,"269":1,"288":1,"308":1,"320":1,"324":1,"328":1,"425":1,"428":1}}],["oriented",{"2":{"30":1,"278":1}}],["or",{"0":{"283":2},"2":{"20":1,"21":1,"23":2,"26":1,"27":1,"36":2,"37":1,"43":1,"44":1,"50":1,"53":2,"54":1,"55":1,"58":1,"61":1,"62":1,"63":2,"64":3,"65":1,"72":1,"75":1,"76":1,"87":1,"88":1,"90":2,"91":2,"93":1,"102":2,"103":7,"104":1,"105":4,"106":18,"108":10,"109":3,"112":1,"120":1,"123":2,"124":2,"128":3,"129":2,"136":1,"139":1,"145":1,"149":1,"162":1,"169":1,"170":3,"172":2,"174":1,"175":1,"178":1,"179":2,"181":1,"183":1,"184":2,"185":1,"186":1,"187":1,"190":3,"191":1,"192":4,"195":2,"196":4,"201":1,"202":1,"204":1,"210":1,"211":2,"214":3,"217":4,"218":1,"221":2,"222":1,"233":2,"234":2,"242":1,"243":2,"245":4,"246":1,"250":1,"254":2,"258":2,"259":1,"263":1,"264":1,"265":2,"266":2,"267":1,"269":3,"271":1,"272":1,"274":1,"277":1,"278":4,"279":2,"280":2,"282":3,"283":1,"284":1,"285":3,"287":1,"289":2,"291":1,"293":1,"295":1,"296":2,"297":2,"299":2,"300":1,"301":1,"302":1,"307":1,"308":5,"309":1,"310":4,"311":1,"312":1,"318":2,"320":1,"321":1,"322":1,"324":1,"326":1,"327":3,"328":2,"329":2,"330":1,"335":1,"336":1,"337":5,"338":1,"339":8,"340":13,"345":1,"348":1,"368":1,"370":1,"384":1,"394":1,"397":1,"400":4,"401":2,"403":3,"405":1,"406":1,"407":1,"408":1,"411":1,"412":2,"415":1,"416":1,"419":3,"423":1,"424":1,"427":2,"428":1,"431":2,"432":14,"436":1,"437":5,"438":1,"439":3,"441":6,"448":3,"449":1,"450":1,"451":5,"452":1,"460":1,"471":1,"474":2,"475":1,"476":2,"477":1,"479":2,"480":1,"482":5,"485":1,"490":1,"492":1}}],["ordering",{"2":{"308":2,"320":1,"474":1}}],["ordered",{"2":{"267":2,"314":1}}],["orders",{"2":{"51":1,"310":2}}],["order",{"2":{"12":1,"51":1,"58":1,"69":1,"70":1,"81":2,"122":1,"129":1,"142":1,"157":1,"166":1,"169":1,"187":1,"188":1,"192":2,"196":1,"205":1,"214":1,"239":1,"249":1,"254":1,"267":3,"269":1,"308":6,"309":4,"310":6,"311":1,"323":1,"327":1,"346":1,"347":1,"353":2,"427":1,"474":5,"486":1}}],["organized",{"2":{"433":1}}],["organizaion",{"2":{"301":1}}],["organised",{"2":{"84":1}}],["org",{"2":{"5":1,"13":1,"16":3,"18":1,"274":1,"381":1,"447":1,"482":1,"486":5,"487":5,"488":5}}],["otherwise",{"2":{"59":2,"70":2,"204":1,"220":1,"221":1,"242":1,"245":1,"310":1,"319":1,"320":1,"329":1,"480":1}}],["others",{"2":{"48":1,"70":1,"308":1,"309":2,"338":1,"400":1}}],["other",{"0":{"9":1,"14":1,"200":1,"226":1,"228":1,"258":1,"288":1,"302":1},"1":{"10":1,"11":1,"12":1,"13":1,"15":1,"229":1,"230":1},"2":{"13":1,"22":1,"37":2,"44":1,"56":2,"58":1,"64":2,"69":2,"70":1,"73":2,"75":1,"77":1,"79":1,"85":2,"95":1,"100":1,"102":1,"103":2,"105":1,"106":4,"113":1,"123":2,"141":1,"147":1,"153":1,"157":1,"163":1,"172":2,"186":1,"187":1,"188":1,"196":2,"203":1,"210":1,"211":1,"238":1,"242":1,"249":1,"250":2,"256":1,"258":2,"262":1,"263":1,"266":1,"267":2,"269":1,"270":6,"285":1,"300":1,"307":1,"308":3,"309":1,"310":6,"312":2,"317":1,"320":1,"321":1,"326":2,"327":2,"328":1,"330":1,"335":1,"342":1,"401":1,"410":2,"414":1,"425":1,"437":1,"441":1,"442":1,"451":1,"461":1,"469":1,"473":1,"476":1,"479":1,"494":1,"496":1}}],["often",{"2":{"48":1,"56":1,"64":2,"66":1,"70":1,"72":1,"75":1,"77":1,"79":1,"92":1,"95":1,"103":1,"108":1,"169":1,"217":1,"226":1,"234":1,"254":1,"266":1,"269":1,"308":3,"309":1,"311":3,"312":1,"313":1,"318":1,"319":1,"320":1}}],["offsite",{"2":{"308":1}}],["offsets",{"2":{"314":1}}],["offset+count",{"2":{"190":1}}],["offset",{"2":{"190":2,"192":2,"193":4,"358":3,"359":1,"377":2,"418":3,"419":1,"422":1,"423":1,"426":1,"428":3}}],["offered",{"2":{"423":1,"427":1}}],["offer",{"2":{"296":1}}],["offers",{"2":{"24":3,"279":1,"423":1}}],["officially",{"2":{"301":1}}],["official",{"2":{"217":1,"238":1,"331":1}}],["offline",{"2":{"106":1}}],["off",{"2":{"18":1,"37":1,"242":1,"449":1}}],["of",{"0":{"36":1,"51":1,"79":1,"199":1,"236":1,"250":1,"264":1,"269":1,"274":1,"275":1,"282":1,"448":1,"450":1,"455":1},"1":{"37":1,"38":1,"39":1,"265":1,"266":1,"456":1,"457":1,"458":1},"2":{"6":2,"12":1,"18":4,"19":1,"22":1,"23":3,"24":2,"26":1,"27":2,"28":3,"29":2,"30":1,"33":6,"37":1,"45":1,"47":2,"48":5,"49":3,"50":2,"51":2,"53":7,"54":1,"56":11,"58":11,"59":8,"60":1,"61":4,"63":2,"64":8,"65":5,"66":5,"67":2,"68":1,"69":11,"70":8,"72":3,"73":2,"75":1,"76":4,"77":1,"79":1,"80":2,"81":9,"82":4,"83":7,"84":3,"85":5,"86":1,"87":7,"88":7,"89":2,"90":5,"91":1,"92":5,"93":4,"94":3,"95":1,"96":1,"99":4,"100":1,"101":1,"102":2,"103":10,"104":6,"105":3,"106":11,"107":2,"108":13,"109":3,"110":2,"112":1,"114":1,"117":1,"119":1,"120":3,"122":3,"123":2,"124":1,"127":1,"128":1,"129":1,"130":2,"131":2,"132":1,"136":1,"139":2,"140":3,"141":1,"143":2,"144":2,"145":4,"146":1,"147":1,"148":4,"149":9,"150":1,"152":2,"153":2,"154":1,"155":3,"156":1,"157":1,"159":1,"160":4,"161":7,"162":3,"163":6,"164":2,"166":5,"167":6,"168":1,"169":9,"170":20,"171":1,"172":17,"173":1,"174":4,"175":6,"176":2,"178":3,"179":5,"180":2,"181":5,"183":5,"184":3,"185":2,"186":5,"187":6,"188":3,"189":2,"190":7,"191":12,"192":10,"193":3,"195":2,"196":13,"197":1,"199":5,"204":4,"209":6,"213":2,"214":3,"216":3,"217":8,"218":1,"219":2,"220":1,"221":6,"224":1,"232":1,"233":4,"234":4,"238":1,"239":1,"240":1,"241":1,"242":2,"243":2,"245":8,"246":4,"247":2,"249":13,"250":7,"251":4,"252":2,"253":2,"254":5,"255":2,"256":5,"257":3,"259":2,"260":1,"261":4,"262":1,"264":2,"266":10,"267":8,"268":3,"269":22,"270":17,"272":2,"273":3,"276":3,"277":3,"278":3,"279":1,"280":2,"282":2,"283":1,"284":1,"285":2,"288":2,"289":1,"290":1,"301":1,"302":1,"307":2,"308":34,"309":14,"310":28,"311":19,"312":9,"313":11,"314":1,"317":3,"318":5,"319":7,"320":4,"321":14,"322":1,"323":1,"324":3,"325":1,"326":8,"327":11,"328":6,"329":1,"330":12,"331":1,"333":1,"335":5,"336":2,"337":2,"338":4,"339":10,"340":1,"345":1,"346":1,"347":5,"348":4,"349":4,"397":5,"399":5,"400":8,"401":5,"402":4,"403":5,"404":2,"405":3,"406":3,"407":6,"408":2,"410":3,"411":5,"412":7,"414":6,"415":3,"416":4,"419":5,"420":11,"421":6,"423":3,"424":6,"425":3,"427":8,"428":12,"431":3,"432":21,"433":1,"435":4,"436":4,"437":9,"438":2,"439":4,"440":6,"441":19,"442":1,"443":10,"444":3,"445":2,"446":7,"448":4,"449":4,"450":1,"451":2,"452":3,"455":2,"456":1,"460":2,"461":1,"462":3,"471":1,"472":1,"473":6,"474":12,"475":1,"476":4,"477":8,"478":1,"479":1,"480":6,"481":1,"482":6,"485":2,"486":4,"487":2,"488":2,"490":4,"491":1,"492":3,"494":2,"496":2,"497":1}}],["opposed",{"2":{"474":1}}],["opportunity",{"2":{"102":1}}],["opportunities",{"2":{"75":1}}],["opaque",{"2":{"133":1,"220":1}}],["opinion",{"2":{"106":1}}],["operate",{"2":{"172":1}}],["operates",{"2":{"170":1,"172":1,"326":2,"328":1,"446":1}}],["operating",{"2":{"123":1,"124":2,"160":1,"163":3,"175":1,"326":1}}],["operational",{"2":{"169":1,"170":2}}],["operation",{"2":{"62":2,"75":1,"92":1,"155":1,"170":1,"179":1,"183":1,"209":1,"254":2,"270":1,"308":3,"312":1,"323":1,"329":1,"400":5,"402":3,"411":1,"420":1,"423":1,"424":1,"428":1,"432":4,"482":1}}],["operations",{"0":{"417":1,"448":1,"468":1},"1":{"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1},"2":{"51":1,"64":1,"76":1,"92":3,"136":1,"140":1,"142":1,"144":1,"154":1,"155":1,"156":1,"169":2,"196":5,"204":1,"249":2,"256":1,"270":3,"285":1,"308":1,"309":1,"326":1,"328":1,"400":2,"401":1,"402":1,"432":2,"441":1,"445":1,"461":1,"462":4}}],["operators",{"2":{"58":1,"59":2,"63":1,"64":2,"85":2,"91":1,"310":1}}],["operator",{"2":{"58":1,"59":2,"62":1,"63":1,"64":1,"91":1,"310":5,"485":1}}],["openid",{"2":{"446":4,"449":8}}],["opening",{"2":{"349":1}}],["open",{"2":{"24":1,"53":1,"103":1,"241":1,"323":1,"458":1,"479":1}}],["openjdk",{"2":{"7":1}}],["openldap=true",{"2":{"23":1}}],["openldap",{"2":{"7":1,"8":1,"10":1,"11":1,"12":1,"23":4,"204":1}}],["openssl",{"0":{"385":1},"2":{"3":1,"4":1,"5":2,"7":1,"8":1,"10":1,"11":1,"12":1,"240":5,"379":1,"380":1,"385":2}}],["optimal",{"2":{"91":1,"195":1}}],["optimizing",{"2":{"70":1,"72":1,"192":1}}],["optimizes",{"2":{"269":1,"313":1}}],["optimizehqlexpression",{"0":{"76":1}}],["optimizer",{"0":{"76":1}}],["optimize",{"2":{"51":1,"192":4,"310":1,"313":1,"319":1,"428":1}}],["optimized",{"2":{"28":1,"29":1,"30":1,"268":1,"423":1}}],["optimizations",{"2":{"50":1,"51":1,"58":1,"75":1,"76":1,"77":1,"310":2,"311":1,"315":1,"329":1}}],["optimization",{"2":{"48":1,"66":1,"87":1,"196":1,"308":1,"313":2,"329":1}}],["optiondefaults",{"2":{"36":1}}],["option",{"2":{"20":1,"53":2,"54":1,"56":2,"75":1,"128":1,"131":1,"154":1,"156":1,"163":2,"170":1,"196":2,"238":1,"259":2,"264":1,"271":1,"274":1,"279":1,"310":1,"327":3,"328":1,"330":2,"340":1,"342":1,"346":2,"476":3,"485":1,"497":1}}],["optional=",{"2":{"487":1}}],["optionals",{"2":{"479":1}}],["optionally",{"2":{"308":2,"326":1,"328":1,"340":2,"423":1}}],["optional",{"0":{"277":1,"279":1},"1":{"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"12":2,"17":1,"37":1,"85":1,"133":1,"148":1,"191":1,"277":1,"280":1,"290":1,"301":1,"308":1,"310":3,"416":1,"425":1,"431":1,"432":6,"436":4,"437":1,"438":2,"439":2,"440":1,"441":9,"443":5,"455":1,"479":1,"482":1,"483":1,"484":1,"486":1}}],["options",{"0":{"259":1,"328":1,"374":1},"2":{"5":1,"22":1,"36":1,"53":3,"54":4,"79":1,"81":1,"129":1,"131":2,"132":1,"190":1,"191":2,"192":5,"195":3,"241":1,"246":1,"259":2,"269":1,"277":1,"279":2,"287":1,"294":1,"308":2,"309":1,"310":1,"311":3,"321":2,"327":2,"351":1,"374":2,"436":1,"437":1,"442":1,"467":1,"472":1,"485":2}}],["opt",{"2":{"23":8,"34":1,"134":2,"191":1,"451":1}}],["onfail",{"2":{"327":1}}],["onstart",{"2":{"312":1}}],["oncreate",{"2":{"269":2,"275":1,"312":1}}],["once",{"2":{"49":1,"50":2,"56":1,"58":1,"59":1,"70":1,"81":1,"82":1,"83":2,"84":1,"87":1,"114":2,"115":1,"126":1,"128":1,"141":1,"143":2,"163":1,"178":1,"179":1,"187":1,"192":2,"211":1,"242":1,"245":1,"246":1,"264":1,"266":2,"267":2,"269":1,"270":1,"308":2,"311":1,"324":1,"326":1,"329":1,"342":1,"407":1,"425":1,"451":1,"485":1}}],["onto",{"2":{"266":1,"497":1}}],["onward",{"2":{"163":1}}],["ones",{"2":{"217":2,"291":1,"320":1,"330":2,"335":1}}],["one",{"2":{"47":1,"49":1,"50":2,"65":2,"66":1,"70":4,"77":1,"88":1,"90":1,"93":1,"99":1,"103":1,"105":1,"108":2,"124":1,"128":1,"129":1,"143":1,"145":2,"147":1,"150":1,"155":1,"157":1,"160":2,"182":1,"186":1,"188":1,"214":2,"217":1,"218":2,"219":2,"233":1,"246":1,"249":3,"250":1,"254":1,"265":1,"267":1,"268":1,"269":3,"270":1,"271":1,"272":1,"282":1,"287":1,"299":1,"308":2,"310":6,"311":1,"312":1,"313":1,"318":1,"319":1,"320":1,"321":3,"322":1,"326":2,"327":3,"328":2,"329":2,"330":2,"340":1,"349":1,"399":1,"401":4,"402":1,"403":1,"405":1,"406":1,"407":2,"410":2,"411":1,"414":3,"415":1,"416":1,"424":1,"425":1,"431":1,"432":11,"435":1,"436":1,"442":1,"443":1,"444":1,"446":1,"450":2,"452":1}}],["online",{"2":{"29":2,"317":1,"326":1}}],["only=true",{"2":{"23":1}}],["only=1",{"2":{"18":1}}],["only",{"0":{"285":1},"2":{"18":1,"27":1,"44":1,"45":1,"50":2,"56":1,"77":1,"92":1,"103":3,"104":1,"108":1,"128":3,"145":1,"154":1,"155":1,"161":1,"170":1,"174":1,"186":1,"187":1,"195":2,"196":2,"204":1,"221":1,"233":3,"245":4,"249":2,"266":1,"267":2,"269":1,"270":1,"272":1,"279":1,"285":1,"299":1,"308":1,"310":2,"311":1,"312":4,"318":1,"324":2,"327":6,"335":1,"348":1,"350":1,"385":1,"400":1,"403":1,"408":1,"410":1,"412":1,"416":3,"419":1,"428":3,"431":1,"436":2,"437":1,"438":1,"440":1,"441":1,"443":3,"448":1,"451":1,"452":1,"456":1,"474":2,"477":1,"480":1,"496":1}}],["on",{"0":{"5":1,"52":1,"329":1,"330":1,"389":1,"390":1},"1":{"53":1,"54":1,"55":1,"56":1},"2":{"6":1,"13":2,"15":2,"16":1,"18":1,"19":1,"21":1,"22":1,"23":1,"39":1,"41":1,"43":1,"48":1,"50":1,"53":1,"55":1,"61":1,"65":2,"69":1,"70":1,"72":2,"73":1,"74":1,"75":2,"83":1,"85":1,"90":1,"91":1,"93":1,"100":1,"102":1,"103":2,"104":1,"106":9,"108":3,"109":3,"114":1,"116":1,"119":1,"120":1,"123":3,"124":3,"125":1,"128":3,"129":3,"131":3,"140":1,"145":2,"149":1,"150":1,"154":2,"156":3,"162":1,"167":1,"169":1,"170":2,"179":2,"183":1,"188":1,"189":1,"190":1,"191":2,"192":2,"193":5,"195":1,"196":6,"202":2,"212":1,"217":1,"219":4,"221":2,"222":1,"224":2,"233":3,"234":1,"238":3,"239":3,"240":2,"242":1,"243":1,"245":1,"246":1,"249":3,"254":1,"256":1,"264":2,"265":3,"266":3,"267":1,"270":4,"271":1,"274":1,"277":2,"278":2,"280":2,"288":1,"289":1,"294":1,"296":1,"299":1,"308":4,"309":6,"310":2,"311":4,"312":3,"313":2,"317":1,"318":2,"319":1,"320":8,"321":4,"324":3,"325":1,"326":4,"327":1,"328":3,"329":1,"330":1,"335":15,"337":2,"339":2,"340":5,"348":1,"349":2,"384":1,"394":1,"399":1,"400":4,"401":3,"403":1,"405":1,"410":1,"420":1,"421":2,"423":1,"424":1,"425":1,"427":5,"428":2,"431":2,"435":1,"438":1,"439":2,"440":1,"441":2,"443":2,"446":1,"447":2,"449":2,"455":1,"460":2,"461":1,"462":1,"470":1,"471":3,"472":2,"473":1,"474":1,"477":1,"478":2,"479":2,"480":4,"482":3,"483":1,"487":1,"495":1}}],["08",{"2":{"460":1}}],["09",{"2":{"458":1}}],["094327",{"2":{"458":3}}],["094325",{"2":{"458":5}}],["094324",{"2":{"458":3}}],["094322",{"2":{"458":3}}],["05",{"2":{"458":1}}],["06534d8e9962637fe9a5188d1cc4ab32c3925010",{"2":{"311":1}}],["00",{"2":{"458":2,"460":2}}],["007",{"2":{"275":1}}],["001ascii",{"2":{"275":4}}],["001x",{"2":{"275":1}}],["004p",{"2":{"275":1}}],["004",{"2":{"275":2}}],["003",{"2":{"275":2}}],["002",{"2":{"275":2}}],["000address",{"2":{"275":1}}],["000name",{"2":{"275":1}}],["000",{"2":{"275":36,"378":1}}],["000r",{"2":{"275":1}}],["00archive",{"2":{"16":2}}],["0xd",{"2":{"270":1,"275":1}}],["0x4",{"2":{"270":2,"275":2}}],["0u",{"2":{"269":2,"275":2}}],["01",{"2":{"26":1,"193":1,"340":3}}],["03",{"2":{"26":1,"193":1}}],["07",{"2":{"26":3}}],["02",{"2":{"26":2,"193":1}}],["0",{"0":{"5":1},"2":{"5":1,"7":1,"16":6,"22":2,"26":1,"37":1,"64":3,"68":1,"75":1,"90":1,"190":2,"194":1,"240":2,"241":1,"256":2,"261":3,"266":2,"269":5,"270":1,"274":6,"275":6,"308":2,"309":1,"328":1,"330":3,"352":4,"362":3,"375":3,"378":1,"381":3,"407":1,"428":1,"441":6,"446":3,"449":4,"458":11,"460":31,"474":2,"486":1,"487":3,"488":3,"491":2,"496":2}}],["04",{"0":{"3":2,"4":1,"5":1},"1":{"5":1},"2":{"26":5,"246":1}}],["76",{"2":{"497":1}}],["789",{"2":{"359":1}}],["73",{"2":{"274":1}}],["77",{"2":{"269":2,"270":1,"274":1}}],["777",{"2":{"42":1}}],["755",{"2":{"42":2}}],["7",{"0":{"5":1,"7":1,"314":1},"2":{"13":1,"196":2,"337":1,"460":5,"497":2}}],["v",{"2":{"485":1}}],["volumes",{"2":{"326":1}}],["volunteers",{"2":{"312":1}}],["voice",{"2":{"280":1,"299":7}}],["void",{"2":{"220":1,"221":2,"233":8,"269":4,"275":10,"351":1,"366":3,"367":1}}],["vulnerabilities",{"2":{"303":1}}],["v4",{"2":{"269":5,"275":5}}],["ve",{"2":{"321":1}}],["vehicle",{"2":{"204":1}}],["vector",{"2":{"188":4,"358":1}}],["ver",{"2":{"340":4}}],["versus",{"2":{"323":1}}],["version=",{"2":{"486":1,"487":3,"488":1}}],["versioned",{"2":{"401":1}}],["versioning",{"2":{"347":1}}],["version1",{"2":{"130":1}}],["version",{"2":{"6":1,"13":1,"15":1,"18":2,"23":1,"26":1,"36":3,"53":1,"88":1,"105":1,"110":2,"123":2,"126":1,"128":2,"129":1,"130":1,"163":1,"192":1,"200":1,"204":1,"244":1,"245":5,"246":1,"247":2,"261":1,"275":1,"308":1,"310":1,"311":1,"313":1,"320":1,"324":1,"326":1,"337":1,"342":1,"347":5,"385":1,"399":1,"401":4,"402":1,"407":6,"408":1,"432":2,"435":1,"436":4,"441":17,"443":8,"460":12,"479":2,"486":2,"498":1}}],["versions",{"0":{"5":1,"244":1,"245":1,"247":1,"493":1,"496":1},"1":{"245":1,"246":1,"247":1,"494":1,"495":1,"496":1,"497":1,"498":1,"499":1},"2":{"53":1,"105":1,"108":1,"110":3,"132":1,"192":1,"245":3,"247":1,"324":1,"347":2,"348":1,"393":1,"401":1,"402":1,"441":1,"443":1,"496":2,"497":1}}],["verifiable",{"2":{"446":1}}],["verification",{"2":{"385":1,"446":1,"448":2}}],["verified",{"2":{"211":2}}],["verifying",{"2":{"209":1,"212":1}}],["verify",{"2":{"141":1,"214":1,"448":1}}],["verbatim",{"2":{"474":1}}],["verbose",{"2":{"204":1,"279":1,"485":3}}],["verbosely",{"2":{"128":1}}],["verb",{"2":{"185":2,"306":1}}],["very",{"2":{"48":1,"56":2,"58":1,"69":2,"70":1,"73":1,"84":1,"88":1,"106":1,"195":1,"196":1,"217":1,"245":1,"252":1,"268":1,"270":1,"271":1,"277":1,"309":2,"310":1,"312":2,"313":1,"319":1,"321":1,"324":1,"328":1}}],["vs",{"0":{"306":1,"307":2},"2":{"175":3,"184":1,"277":1,"301":2}}],["v1",{"2":{"133":1,"269":4,"275":4}}],["v2",{"2":{"131":1,"269":4,"275":4}}],["vital",{"2":{"310":1}}],["vitepress",{"0":{"114":1},"2":{"111":1,"112":2,"113":3,"114":2,"116":2,"117":1,"197":2,"198":1}}],["video",{"2":{"279":2,"337":1}}],["videos",{"2":{"279":1}}],["virtually",{"2":{"123":1}}],["virtual",{"2":{"95":1,"146":2,"149":2,"163":2,"191":1,"192":1,"219":2,"224":3,"233":10,"234":2,"255":1,"269":8,"270":1,"275":14,"340":1,"366":2,"428":1}}],["via",{"2":{"68":1,"69":2,"109":2,"122":1,"164":1,"196":2,"203":1,"204":1,"259":1,"264":1,"320":1,"324":1,"326":2,"327":1,"328":1,"330":3,"349":1,"446":1,"447":2}}],["view`",{"2":{"335":3}}],["viewed",{"2":{"103":1,"242":1,"274":1,"401":1,"425":1}}],["view",{"2":{"51":1,"95":1,"103":2,"114":1,"115":2,"170":1,"335":2,"452":1,"453":2,"454":2}}],["visual",{"2":{"20":1,"113":1,"123":3,"129":1,"278":1,"279":1,"280":1}}],["visit",{"2":{"0":1,"17":1}}],["vague",{"2":{"339":1}}],["vault",{"2":{"133":1,"140":1,"204":1,"451":1}}],["val",{"2":{"351":2}}],["valuable",{"2":{"311":1}}],["valuetype",{"2":{"402":1,"410":1,"411":2,"412":12,"416":1,"418":3,"419":2,"421":6,"432":3,"441":4,"443":1}}],["value=",{"2":{"256":1,"262":1,"269":20,"270":1,"274":13,"486":4,"487":4,"488":4}}],["value2",{"2":{"183":1,"188":1}}],["value1",{"2":{"183":1,"188":1}}],["values",{"2":{"59":1,"73":3,"82":1,"94":1,"95":1,"96":1,"133":1,"140":3,"142":1,"166":1,"167":4,"169":2,"170":1,"172":6,"181":1,"187":1,"189":2,"201":2,"202":2,"204":5,"213":1,"221":1,"261":1,"269":2,"270":1,"290":2,"308":6,"309":1,"310":3,"311":1,"327":1,"397":2,"400":3,"405":1,"419":1,"421":5,"427":6,"428":1,"431":1,"432":1,"436":1,"437":4,"439":3,"441":3,"449":1,"451":1,"479":1,"480":2,"485":1}}],["value",{"0":{"282":1,"403":1,"409":1},"1":{"410":1,"411":1,"412":1},"2":{"37":1,"44":1,"59":1,"66":1,"68":2,"73":1,"75":2,"87":3,"90":1,"91":3,"92":1,"133":1,"139":1,"140":1,"167":2,"170":1,"172":1,"174":2,"178":1,"179":7,"180":4,"181":1,"183":1,"184":3,"186":2,"187":3,"188":1,"204":1,"221":1,"254":2,"266":2,"269":4,"278":1,"290":1,"308":1,"311":1,"312":1,"313":1,"318":1,"321":1,"326":2,"335":1,"351":2,"356":1,"365":1,"397":2,"400":6,"402":1,"403":7,"404":6,"405":3,"408":1,"409":1,"410":3,"412":4,"413":1,"414":2,"416":1,"419":4,"420":1,"421":15,"424":1,"425":1,"427":10,"428":8,"431":2,"432":6,"436":4,"437":7,"438":4,"439":1,"441":16,"443":5,"445":3,"448":1,"452":2,"453":1,"454":1,"476":2,"478":2,"479":1,"480":4,"481":1,"482":1,"485":2,"486":5,"487":5,"488":5}}],["validating",{"2":{"211":1,"448":1}}],["validates",{"2":{"462":2}}],["validate",{"2":{"211":1,"213":1,"450":1}}],["validated",{"2":{"105":1,"448":1}}],["valid",{"2":{"75":1,"143":3,"149":1,"204":1,"266":1,"310":1,"446":1,"474":1,"476":1,"482":1}}],["validity",{"2":{"74":1}}],["vary",{"2":{"170":1,"313":2,"321":1,"399":1}}],["varying",{"2":{"170":1,"309":1}}],["varation",{"2":{"156":1}}],["vars",{"2":{"45":1}}],["var",{"2":{"44":2,"193":7}}],["varieties",{"2":{"91":1}}],["variations",{"2":{"88":1,"141":1}}],["variable>",{"2":{"260":1,"274":1}}],["variable",{"2":{"83":3,"87":1,"90":1,"91":3,"133":1,"134":1,"187":2,"220":1,"240":2,"254":1,"260":1,"274":1,"312":1,"400":1,"432":1,"486":2,"487":2,"488":2,"491":1,"494":1,"496":1}}],["variables>",{"2":{"260":2,"274":2}}],["variables",{"0":{"239":1},"1":{"240":1,"241":1},"2":{"36":2,"39":1,"45":1,"54":1,"105":1,"220":1,"221":2,"234":1,"239":2,"313":2,"479":1,"494":1}}],["variants",{"2":{"308":1,"313":1}}],["variant",{"2":{"20":1,"21":1,"191":1}}],["various",{"2":{"13":1,"51":1,"58":1,"72":2,"73":1,"79":1,"130":1,"131":2,"250":1,"258":1,"266":1,"269":1,"310":1,"339":1,"432":1,"461":2,"462":1,"464":1,"490":1,"494":1}}],["vast",{"2":{"28":1,"308":1}}],["v9",{"2":{"26":6}}],["vcpkg",{"2":{"13":17}}],["v8embed",{"2":{"18":1,"53":1}}],["v8",{"2":{"7":1,"26":1}}],["rst",{"2":{"271":1,"311":1}}],["rsa",{"2":{"240":1,"241":2}}],["rf2",{"2":{"270":2,"275":2}}],["rf1",{"2":{"270":2,"275":2}}],["rc",{"0":{"498":1},"2":{"497":1}}],["rc1",{"2":{"242":2}}],["rcpp",{"2":{"3":1,"7":1,"8":1,"16":3}}],["r1",{"2":{"193":3}}],["rhs",{"2":{"164":1}}],["rtlsleep",{"2":{"325":1}}],["rtlstrtodatax",{"2":{"275":1}}],["rtlstrtostr",{"2":{"269":1,"275":2}}],["rtlstringtypeinfo",{"2":{"270":2,"275":2}}],["rtlkey",{"2":{"275":1}}],["rtlreleaserow",{"2":{"272":1}}],["rtlrecordtypeinfo",{"2":{"270":1,"275":1}}],["rtlfieldinfo",{"2":{"270":1,"275":1}}],["rtlfieldstrinfo",{"2":{"270":2,"275":2}}],["rtltypeinfo",{"2":{"270":1,"275":1}}],["rtlcomparestrstr",{"2":{"269":1,"275":2}}],["rtldataattr",{"2":{"269":1,"275":1}}],["rtl",{"2":{"96":2,"257":1,"269":2,"275":2,"311":1,"325":2}}],["rudimentary",{"2":{"328":1,"397":1}}],["rule",{"0":{"405":1,"413":1},"1":{"414":1,"415":1,"416":1},"2":{"219":1,"233":1,"270":1,"402":1,"405":1,"414":1,"415":3,"416":12,"423":1,"424":1,"425":2,"431":1,"432":5,"438":5,"441":1,"442":1,"443":3}}],["rules",{"0":{"159":1},"2":{"53":1,"159":1,"190":1,"349":1,"402":1,"405":6,"414":4,"415":1,"416":4,"423":5,"424":2,"425":3,"428":1,"432":1,"438":6,"441":3,"443":3,"445":1}}],["runclass",{"2":{"460":4}}],["runtime",{"0":{"411":1,"415":1,"420":1,"424":1,"428":1},"2":{"83":1,"258":1,"262":1,"309":1,"310":1,"431":2,"479":1,"480":1}}],["runable",{"2":{"42":1}}],["runs",{"2":{"38":2,"128":2,"196":1,"328":1}}],["running",{"0":{"54":1,"460":1},"2":{"25":1,"47":1,"53":3,"54":1,"162":1,"262":1,"288":2,"310":1,"313":1,"322":1,"332":2,"460":1,"473":1,"480":2,"494":1}}],["run",{"0":{"455":1},"1":{"456":1,"457":1,"458":1},"2":{"18":1,"23":2,"38":2,"39":1,"53":4,"54":1,"82":2,"114":1,"123":1,"126":3,"128":6,"129":2,"130":1,"164":2,"239":1,"241":2,"242":1,"249":2,"264":2,"266":1,"268":1,"313":1,"317":1,"325":1,"326":1,"342":1,"346":1,"455":2,"456":2,"460":5,"480":1}}],["rocking",{"2":{"324":1}}],["role",{"2":{"339":1}}],["roles",{"2":{"278":1}}],["rollint",{"2":{"204":1}}],["robustness",{"2":{"461":1}}],["robust",{"2":{"166":1}}],["robining",{"2":{"330":1}}],["robins",{"2":{"330":1}}],["robin",{"2":{"144":1}}],["rounds",{"2":{"152":1}}],["rounded",{"2":{"148":1,"152":1}}],["round",{"2":{"144":1,"330":2}}],["routine",{"2":{"48":1,"160":2,"458":1}}],["rotated",{"2":{"132":1}}],["row>",{"2":{"486":2,"487":2,"488":2}}],["rowbuilder",{"2":{"311":2}}],["rowlimit=",{"2":{"261":1,"274":1}}],["row",{"2":{"65":2,"76":1,"87":1,"89":3,"94":2,"145":1,"149":6,"152":1,"153":3,"154":1,"155":5,"156":3,"161":2,"164":2,"269":3,"270":8,"275":1,"308":13,"309":2,"311":2,"312":2,"313":12,"318":2,"319":4,"321":1,"329":1,"486":1,"487":1,"488":1}}],["rowsets",{"2":{"272":1}}],["rows",{"2":{"64":2,"65":1,"69":1,"74":1,"81":1,"92":1,"93":4,"145":3,"149":1,"154":2,"155":4,"157":2,"158":2,"159":1,"160":1,"161":7,"191":2,"269":1,"270":5,"272":3,"308":12,"309":1,"310":2,"311":2,"313":5,"318":2,"321":4,"329":2}}],["rootgraph",{"2":{"256":1,"269":1,"270":1,"274":1}}],["rootca",{"2":{"240":4}}],["root",{"2":{"36":1,"114":1,"117":1,"190":1,"195":1,"240":1,"270":3,"458":1,"474":1,"476":1,"482":1}}],["roxiefaq",{"2":{"333":1}}],["roxielocalqueuemanager",{"2":{"329":1}}],["roxieudpsocketqueuemanager",{"2":{"329":1}}],["roxiemulticastenabled=0",{"2":{"325":1}}],["roxiemem",{"2":{"199":1,"326":1,"330":1}}],["roxie100",{"2":{"193":3}}],["roxie",{"0":{"29":1,"169":1,"313":1,"314":1,"316":1,"325":1,"340":1},"1":{"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1},"2":{"27":2,"29":2,"33":1,"126":1,"145":3,"148":2,"155":1,"157":2,"169":4,"170":1,"190":2,"193":5,"199":2,"217":1,"249":2,"251":2,"264":2,"265":2,"266":4,"268":4,"269":1,"270":1,"275":1,"288":2,"301":1,"313":2,"317":2,"318":1,"323":1,"324":1,"325":1,"326":7,"327":4,"328":5,"332":2,"333":1,"340":3,"460":5}}],["random",{"2":{"309":1}}],["ranges",{"2":{"170":1,"308":1,"309":4}}],["range",{"0":{"381":1},"2":{"59":1,"190":1,"233":1,"308":3,"311":1,"313":3,"314":1,"381":9,"419":1}}],["rarely",{"2":{"209":1,"311":1}}],["race",{"2":{"105":1,"245":1,"326":1}}],["rawsize",{"2":{"191":2}}],["raw",{"2":{"94":1,"349":2}}],["raton",{"2":{"240":2}}],["rates",{"2":{"171":1}}],["rationalised",{"2":{"84":1,"91":1}}],["rationalized",{"2":{"73":1,"190":1}}],["rationale",{"0":{"24":1},"2":{"410":1,"414":1}}],["rather",{"2":{"50":1,"54":1,"56":1,"58":1,"64":1,"65":1,"69":2,"103":1,"104":1,"108":1,"153":1,"155":1,"162":1,"168":1,"196":2,"201":1,"223":1,"234":1,"288":1,"318":1,"320":1,"326":1,"327":1,"329":2,"330":1,"450":1}}],["rapid",{"2":{"29":1,"327":1}}],["rights",{"2":{"109":1,"453":6,"454":6}}],["right",{"2":{"64":4,"66":1,"74":1,"105":1,"108":1,"303":1,"310":1,"335":1}}],["risk",{"2":{"24":1,"240":1,"321":1}}],["rinside",{"2":{"3":1,"7":1,"8":1,"16":3}}],["rpm",{"2":{"7":1,"21":1,"36":2,"124":1}}],["rejection",{"2":{"435":1}}],["rejects",{"2":{"435":1}}],["rejected",{"2":{"143":1,"144":1}}],["reevaluate",{"2":{"428":1}}],["revealing",{"2":{"397":1}}],["reveal",{"2":{"335":1}}],["revealed",{"2":{"310":1}}],["reverse",{"2":{"309":1}}],["revisions",{"2":{"310":1}}],["revisited",{"2":{"309":1,"310":1}}],["revisit",{"2":{"308":1}}],["reviewing",{"2":{"105":1,"198":1,"276":1}}],["reviewed",{"2":{"103":1,"109":2}}],["reviewer",{"2":{"102":1,"103":9,"106":12,"108":4,"109":2}}],["reviewers",{"0":{"109":1},"2":{"101":1,"103":4,"106":1,"109":3}}],["reviews",{"2":{"102":1,"103":2,"104":2,"106":1,"108":1,"109":1}}],["review",{"0":{"101":1,"102":1},"1":{"102":1,"103":1,"104":1,"105":1,"106":1},"2":{"103":6,"104":1,"106":4,"108":6,"109":7,"194":1,"198":1,"236":1,"238":1,"280":1,"310":1}}],["reinstall",{"2":{"347":1}}],["reinitialised",{"2":{"270":1}}],["reimplement",{"2":{"194":1}}],["reqd",{"2":{"266":3,"274":1}}],["req",{"2":{"240":7,"351":10,"354":2,"355":1,"356":4,"357":2,"358":2,"359":1,"360":2}}],["requisites",{"0":{"495":1}}],["requiring",{"2":{"170":1,"175":1,"236":1,"326":1,"330":1,"428":1,"432":1}}],["requirement",{"2":{"169":1,"174":1,"195":2,"310":1,"399":2,"401":1,"441":1}}],["requirements",{"0":{"122":1},"2":{"106":1,"130":1,"141":1,"157":2,"166":1,"168":1,"172":1,"174":1,"175":4,"184":1,"187":1,"188":1,"189":1,"219":1,"233":2,"278":1,"399":7,"400":1,"401":2,"402":1,"403":2,"419":2,"430":1,"431":1,"432":7,"486":1}}],["require",{"2":{"66":1,"109":1,"134":1,"150":1,"181":1,"192":1,"238":1,"246":1,"282":1,"291":1,"310":5,"311":1,"326":1,"329":1,"347":2,"400":5,"410":2,"427":4,"431":1,"432":1,"441":1,"460":1}}],["requires",{"0":{"284":1},"2":{"54":1,"65":1,"88":1,"108":1,"122":1,"131":1,"155":1,"162":1,"214":1,"233":1,"239":1,"249":1,"269":1,"311":1,"312":1,"347":2,"403":1,"410":1,"414":1,"420":1,"428":1,"448":1}}],["required",{"0":{"14":1,"277":1,"278":1},"1":{"15":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"12":3,"18":2,"44":1,"49":1,"50":1,"58":1,"69":1,"70":1,"77":1,"81":1,"87":2,"91":1,"106":2,"122":2,"142":1,"154":1,"157":2,"159":1,"163":2,"172":1,"182":1,"187":2,"190":4,"192":4,"204":2,"211":1,"214":1,"217":1,"249":1,"256":1,"257":1,"266":3,"277":1,"280":1,"293":1,"301":1,"308":6,"311":2,"312":1,"318":1,"340":2,"346":1,"379":1,"380":1,"404":1,"406":1,"410":2,"416":1,"419":3,"427":6,"428":4,"431":1,"432":9,"436":1,"437":1,"438":1,"439":1,"440":1,"441":3,"443":2,"449":2,"451":2,"474":3,"476":2,"477":1,"478":1,"479":1,"480":1,"481":2,"482":2,"486":1,"495":2}}],["requestname",{"2":{"486":1,"487":1}}],["requestoption",{"2":{"486":1,"487":1}}],["requestoption=1",{"2":{"486":1,"487":1,"488":1}}],["requestoption>1",{"2":{"486":1,"487":1,"488":1}}],["requestvalue",{"2":{"486":1,"487":1,"488":1}}],["requesttosend",{"2":{"330":1}}],["requesting",{"2":{"279":1,"318":3}}],["requested",{"2":{"109":1,"148":1,"152":1,"308":1,"313":1,"318":2,"400":1,"404":1,"407":5,"412":2,"416":1,"428":1,"438":2,"441":1}}],["request",{"0":{"363":1,"389":1},"2":{"108":1,"109":1,"128":1,"170":2,"172":1,"184":1,"185":2,"186":1,"211":1,"240":1,"264":1,"289":3,"290":1,"309":1,"310":2,"313":1,"318":1,"319":1,"320":1,"351":4,"357":1,"358":2,"359":1,"360":2,"376":2,"377":1,"378":1,"382":1,"402":1,"411":1,"412":2,"416":2,"420":1,"424":1,"427":3,"428":1,"441":1,"443":1,"449":3,"474":1,"479":1,"486":7,"487":8,"488":6,"490":1,"492":1}}],["requests",{"0":{"108":1,"289":1},"2":{"29":1,"104":1,"107":1,"109":2,"120":1,"153":1,"169":1,"170":3,"172":1,"174":1,"178":1,"179":1,"184":1,"185":4,"186":7,"188":2,"196":2,"213":1,"270":1,"317":1,"319":1,"320":1,"322":1,"330":1,"402":2,"405":3,"407":1,"419":1,"423":2,"438":2,"441":1,"443":1,"450":1,"451":1,"490":1,"492":2}}],["reusing",{"2":{"192":1,"317":1}}],["reused",{"2":{"163":1,"192":2,"194":1}}],["reuse",{"2":{"163":1,"310":1,"318":1,"474":1}}],["reusable",{"2":{"30":1,"172":1}}],["rewriting",{"2":{"127":1,"347":1}}],["rewritten",{"2":{"103":1}}],["renewable",{"2":{"339":1}}],["rename",{"2":{"195":1}}],["renaming",{"2":{"108":1}}],["rendered",{"2":{"277":1}}],["render",{"2":{"114":1,"328":1}}],["rendering",{"0":{"114":1}}],["reordered",{"2":{"309":1}}],["reorder",{"2":{"76":1}}],["ret",{"2":{"353":7,"356":1}}],["retained",{"2":{"221":1,"234":1}}],["retain",{"2":{"221":1}}],["retry",{"2":{"192":1}}],["retrived",{"2":{"192":1}}],["retries+1",{"2":{"327":1}}],["retries",{"2":{"327":1}}],["retrieval",{"2":{"174":1}}],["retrieving",{"2":{"167":1,"172":2,"188":1,"192":1}}],["retrieve",{"0":{"141":1},"2":{"141":1,"172":2,"174":1}}],["retrieved",{"2":{"69":1,"82":1,"140":2,"141":2,"192":1,"204":1,"330":1,"480":1}}],["retried",{"2":{"143":1}}],["rethink",{"2":{"133":1}}],["returning",{"2":{"308":2,"311":2,"313":1,"431":1}}],["returned",{"2":{"143":1,"149":2,"150":1,"154":1,"179":1,"191":1,"211":1,"221":2,"270":2,"308":5,"313":1,"321":1,"328":1,"440":1,"444":1}}],["return",{"2":{"45":2,"56":1,"59":1,"68":1,"70":1,"89":1,"92":1,"154":1,"163":1,"191":1,"195":1,"221":3,"255":2,"257":2,"269":7,"270":1,"275":15,"308":2,"312":1,"313":3,"349":2,"357":3,"358":4,"359":2,"360":2,"366":1,"376":5,"377":2,"378":2,"427":1}}],["returnschemalocationonok",{"2":{"479":1}}],["returns",{"2":{"36":1,"59":3,"62":1,"64":1,"187":1,"192":1,"195":1,"251":1,"252":1,"266":1,"270":1,"310":1,"311":2,"313":3,"349":1,"445":1,"448":1}}],["remedy",{"2":{"267":1}}],["remember",{"2":{"100":1,"280":1}}],["remain",{"2":{"169":1,"196":1,"202":1,"312":1,"421":1}}],["remaining",{"2":{"161":1}}],["remains",{"2":{"66":1,"160":1,"174":1,"211":1}}],["reminders",{"2":{"108":1}}],["remind",{"2":{"108":1,"318":1}}],["removeproperty",{"2":{"408":1}}],["remove",{"2":{"77":1,"85":1,"103":1,"194":1,"270":1,"353":4,"471":3}}],["removed",{"2":{"73":1,"190":1,"310":2,"312":1,"324":1,"330":1,"345":1}}],["removes",{"2":{"73":1,"77":1,"92":1,"264":2,"327":1}}],["removing",{"2":{"58":1,"70":1,"75":1,"76":1,"77":1,"108":1,"497":1}}],["remotely",{"2":{"196":1}}],["remotefilecollection",{"2":{"192":2}}],["remote",{"2":{"72":1,"172":1,"192":2,"196":5,"323":4,"340":4}}],["rembed",{"2":{"18":1}}],["real",{"2":{"221":3,"234":1,"241":1,"278":1,"302":1}}],["reality",{"2":{"216":1}}],["reallocated",{"2":{"156":1}}],["really",{"0":{"79":1},"2":{"84":1,"85":1,"100":1,"108":1,"191":1,"234":1,"310":1,"312":1,"326":1,"327":2,"446":1}}],["reach",{"2":{"340":1}}],["reached",{"2":{"143":3}}],["react",{"2":{"166":1,"432":1}}],["reasonable",{"2":{"201":1}}],["reasonably",{"2":{"48":1,"399":1}}],["reason",{"2":{"70":1,"105":1,"106":4,"189":1,"203":1,"321":1,"324":1,"327":1,"340":1}}],["reasons",{"2":{"61":1,"103":1,"169":1,"196":1,"309":1,"324":1,"326":1,"339":1}}],["readme",{"0":{"489":1},"1":{"490":1,"491":1,"492":1},"2":{"286":1}}],["readmes",{"2":{"277":1}}],["reader",{"2":{"219":1,"299":1,"301":1,"357":3}}],["reads",{"2":{"169":1,"179":1,"180":1,"192":1,"196":1,"269":1,"318":1,"326":2,"329":1,"486":1}}],["reading",{"0":{"195":1,"363":1},"2":{"106":1,"179":2,"192":4,"194":5,"195":3,"196":1,"270":1,"313":1,"323":1,"326":1,"329":1,"360":1}}],["readable",{"2":{"83":1,"102":1}}],["read",{"0":{"468":1},"2":{"56":1,"61":1,"76":1,"104":1,"106":4,"113":1,"139":1,"161":1,"192":1,"193":1,"195":1,"196":6,"204":2,"213":1,"249":1,"256":2,"269":3,"270":4,"274":1,"306":1,"313":1,"318":2,"323":2,"326":4,"329":1,"362":1,"375":1,"448":1,"452":2,"468":1,"469":1,"474":1,"477":1,"480":1}}],["ready",{"2":{"18":1,"24":1,"109":3,"251":1,"264":2,"266":3,"313":1,"319":1,"330":1}}],["rebuilds",{"2":{"36":1}}],["reproducible",{"2":{"308":1}}],["reproduce",{"2":{"306":1}}],["representing",{"2":{"90":1,"96":1,"167":1,"170":1,"179":1,"185":1,"318":2,"428":1}}],["represented",{"2":{"63":1,"64":2,"93":1,"94":1,"186":1,"190":1,"196":1,"256":1,"310":2,"348":1,"399":1,"400":1,"403":1,"404":1,"405":1,"406":1,"407":1,"448":1}}],["represent",{"2":{"58":1,"59":1,"64":1,"84":1,"172":1,"174":1,"182":1,"310":1,"312":2,"399":1,"444":1}}],["represents",{"2":{"58":1,"90":3,"149":1,"167":1,"170":1,"270":1,"312":1,"348":3,"411":1,"415":1,"421":2,"431":1}}],["representation",{"0":{"58":1},"1":{"59":1,"60":1,"61":1,"62":1,"63":1},"2":{"30":1,"49":1,"56":2,"58":1,"66":1,"69":1,"73":2,"91":1,"93":1,"96":1,"189":1,"190":1,"310":1,"321":1,"399":1,"400":1,"403":1}}],["repurposed",{"2":{"284":1}}],["replies",{"2":{"449":1,"450":1}}],["replicas",{"2":{"196":1}}],["replica",{"2":{"193":1}}],["replicate",{"2":{"196":1}}],["replicated",{"2":{"190":1}}],["replicating",{"2":{"193":1,"402":1}}],["replication",{"2":{"190":3,"193":2}}],["reply",{"2":{"449":2}}],["replypending",{"2":{"329":1}}],["replacing",{"2":{"486":1}}],["replaces",{"2":{"190":1}}],["replaced",{"2":{"66":1,"73":2,"95":1,"187":1,"196":1,"217":1,"312":1,"329":1,"397":1,"414":1,"479":1,"483":1}}],["replace",{"2":{"39":2,"217":1,"436":1,"474":1}}],["replay",{"2":{"326":1}}],["repack",{"2":{"159":1}}],["reported",{"2":{"176":1,"327":1}}],["reporter",{"0":{"176":1},"2":{"175":1,"176":1,"187":9}}],["reports",{"2":{"172":1,"175":1,"184":1,"322":1}}],["report",{"2":{"170":2,"175":1,"319":1,"322":1,"411":1,"415":1}}],["reporting",{"2":{"70":1,"167":2,"172":4,"174":2,"175":1,"176":1,"179":1,"186":1,"187":1,"310":1}}],["repo",{"2":{"128":1,"239":1,"285":1,"473":1}}],["repositories",{"2":{"31":1,"123":1,"130":3,"133":1,"238":1,"242":1,"495":1,"496":2}}],["repository",{"2":{"6":1,"61":1,"72":1,"107":1,"112":1,"114":1,"115":1,"120":1,"130":1,"238":3,"239":3,"242":4,"243":4,"277":1,"308":1,"309":1,"311":1}}],["repeatable",{"2":{"483":1,"484":1}}],["repeated",{"2":{"103":2,"204":1,"211":1,"441":1,"443":1}}],["repeat",{"2":{"70":1,"192":1}}],["repeating",{"2":{"61":1,"438":1,"440":1,"441":3,"443":3}}],["res",{"2":{"351":7,"354":2,"355":3,"356":1,"357":2,"358":4,"359":2,"360":3,"367":4,"368":2,"369":1,"370":2,"371":1,"374":1,"376":2,"377":1,"378":1,"381":3,"383":4,"389":1,"390":2}}],["reset",{"2":{"313":1,"318":1,"319":1,"327":1}}],["reserves",{"2":{"408":1}}],["reserve",{"2":{"146":1,"272":1,"411":1,"415":1}}],["reservedwords",{"2":{"310":1}}],["reserved",{"2":{"63":1,"85":2,"349":1,"408":1,"410":1,"414":1,"420":2,"421":4,"441":1,"495":1}}],["respectively",{"2":{"234":1}}],["respect",{"2":{"170":2}}],["responsiveness",{"2":{"170":1}}],["responsibilities",{"2":{"281":1}}],["responsibility",{"2":{"109":1,"172":1,"180":1,"181":1,"281":2,"441":1}}],["responsible",{"2":{"28":1,"109":1,"172":1,"174":1,"175":1,"176":1,"180":1,"186":1,"187":1,"192":2,"266":1,"330":1,"348":1}}],["responses",{"2":{"169":2,"339":5,"492":1}}],["response",{"0":{"390":1},"2":{"103":1,"106":5,"109":1,"170":4,"327":1,"330":1,"339":2,"351":4,"357":1,"358":2,"359":1,"360":4,"376":3,"420":1,"424":1,"428":1,"449":1,"458":1,"479":1,"487":2,"490":1,"492":3}}],["responds",{"2":{"322":1}}],["respond",{"2":{"103":2,"166":1,"327":1}}],["resizing",{"0":{"160":1},"2":{"158":1,"159":1}}],["resize",{"2":{"158":2,"159":1,"160":3,"335":1}}],["resizerow",{"2":{"150":1}}],["resides",{"2":{"53":1}}],["resolvechildquery",{"2":{"272":1}}],["resolved",{"2":{"73":1,"103":1}}],["resolve",{"0":{"341":1},"1":{"342":1},"2":{"61":1,"72":1,"103":1,"106":1,"270":1,"333":1}}],["resourcesbasedn",{"2":{"204":2}}],["resources",{"2":{"131":1,"135":1,"142":1,"204":1,"214":1,"234":1,"250":1,"263":1,"279":1}}],["resource",{"2":{"51":2,"79":1,"166":1,"204":1,"209":1,"212":1,"214":4,"250":1,"323":2,"335":2,"372":1,"373":1,"374":1,"389":1,"390":1}}],["restarting",{"2":{"448":1}}],["restarts",{"2":{"322":1}}],["restarted",{"2":{"103":1}}],["restore",{"2":{"313":1}}],["restrict",{"2":{"326":1}}],["restrictions",{"2":{"131":1,"278":1,"312":1}}],["restructuring",{"2":{"321":1}}],["restructured",{"2":{"58":1}}],["rest",{"2":{"32":1,"83":1,"103":1,"145":1,"252":1,"310":1}}],["resulting",{"2":{"264":1,"309":1,"313":1,"326":1}}],["result>",{"2":{"261":2,"274":2,"486":2,"487":2,"488":2}}],["results>",{"2":{"261":2,"274":2}}],["results",{"0":{"82":1,"261":1},"2":{"48":1,"53":1,"77":1,"79":1,"82":2,"88":1,"106":1,"169":1,"192":1,"196":1,"204":1,"217":1,"245":1,"249":2,"250":1,"251":1,"252":2,"254":1,"261":2,"264":1,"266":1,"269":2,"270":1,"274":1,"278":1,"308":6,"309":1,"318":2,"328":1,"330":1,"339":1,"406":1,"412":3,"420":2,"424":1,"428":1,"446":1,"470":1}}],["result",{"0":{"457":1},"1":{"458":1},"2":{"23":1,"82":1,"90":1,"252":1,"256":2,"261":6,"266":1,"269":3,"274":6,"275":4,"308":1,"318":3,"319":1,"320":1,"324":1,"330":1,"419":1,"420":2,"424":1,"428":1,"458":1,"460":1,"471":1,"473":1}}],["refrain",{"2":{"483":1,"484":1}}],["refreshes",{"2":{"450":1}}],["refreshed",{"2":{"109":1}}],["refreshtokens",{"2":{"449":1}}],["refresh",{"2":{"109":1,"448":5,"449":1,"451":1}}],["refine",{"2":{"339":2}}],["refinement",{"2":{"339":1}}],["refinery",{"2":{"28":1}}],["refstr",{"2":{"269":1,"275":1}}],["ref",{"2":{"221":1}}],["refactor",{"2":{"194":4}}],["refactoring",{"0":{"195":1},"2":{"84":1,"108":1,"245":2,"329":1}}],["refactored",{"2":{"61":1,"103":1,"196":2,"310":1,"312":1}}],["reflects",{"2":{"326":1}}],["reflection",{"2":{"310":1}}],["reflect",{"2":{"58":1,"104":1,"106":1,"108":1}}],["referred",{"2":{"172":1}}],["referring",{"2":{"64":1,"300":1,"301":1,"302":1}}],["refers",{"2":{"63":2,"65":1}}],["referencenumber",{"2":{"338":1}}],["referenced",{"2":{"64":1,"72":1,"267":1,"399":1,"477":1}}],["reference",{"0":{"234":1,"295":1},"2":{"53":3,"58":1,"64":2,"65":1,"69":1,"108":1,"131":1,"145":1,"180":2,"187":5,"200":1,"221":1,"234":1,"267":1,"272":1,"276":1,"279":2,"308":1,"448":1,"477":1,"480":1}}],["references",{"0":{"64":1},"1":{"65":1,"66":1},"2":{"51":1,"58":2,"65":1,"69":1,"70":2,"74":1,"83":1,"85":2,"87":1,"264":1,"295":1,"339":1,"411":1,"415":1,"420":1,"441":1}}],["refer",{"2":{"15":1,"90":1,"113":1,"135":1,"221":1,"249":1,"299":2,"301":1,"339":1,"401":1,"402":1,"444":1}}],["re",{"2":{"18":1,"49":1,"103":1,"109":1,"128":3,"159":1,"194":1,"217":1,"241":1,"267":1,"270":1,"280":1,"336":1,"349":2,"428":1,"448":2,"477":1}}],["redirects",{"2":{"347":1}}],["redirect",{"0":{"383":1},"2":{"347":4}}],["redis",{"2":{"18":1}}],["red",{"2":{"282":1,"285":1,"355":1}}],["redefinition",{"2":{"441":2,"443":2}}],["redefines",{"2":{"170":1}}],["redesigning",{"2":{"310":1}}],["redesign",{"2":{"106":1}}],["redhat",{"2":{"124":1}}],["redundancy",{"2":{"322":1,"337":1}}],["redundant",{"2":{"70":1,"76":1,"412":1,"432":2}}],["reductions",{"2":{"85":1}}],["reducing",{"2":{"70":1,"145":1,"163":1}}],["reduced",{"2":{"324":1}}],["reduces",{"2":{"152":1,"153":2,"156":1,"247":2,"269":1}}],["reduce",{"2":{"12":1,"58":1,"64":1,"66":1,"72":1,"83":1,"147":1,"148":1,"154":1,"310":4,"311":1,"318":2,"320":1,"337":1,"339":1}}],["rec>",{"2":{"486":2,"487":2,"488":2}}],["rec",{"2":{"482":1}}],["recipe",{"2":{"327":1}}],["recipient",{"2":{"219":1}}],["recap",{"2":{"269":1}}],["recalculating",{"2":{"87":1}}],["receiving",{"2":{"330":1}}],["receiver",{"0":{"357":1,"376":1},"2":{"329":2,"330":3,"357":1}}],["receive",{"0":{"357":1,"376":1},"2":{"265":1}}],["receives",{"2":{"172":1}}],["received",{"2":{"170":1,"172":1,"174":1,"178":1,"184":1,"186":4,"318":1,"320":1}}],["recereate",{"2":{"192":1}}],["recently",{"2":{"326":1,"461":1}}],["recent",{"2":{"18":1,"110":1,"326":1,"339":1}}],["reconnect",{"2":{"327":1}}],["recover",{"2":{"326":1,"327":1}}],["recovery",{"2":{"145":1,"267":4}}],["recognition",{"2":{"474":3,"477":2}}],["recognizes",{"2":{"411":1,"415":1}}],["recognized",{"2":{"124":1,"441":1,"473":1,"474":1,"481":1,"483":1,"484":1}}],["recognise",{"2":{"310":1}}],["recomputed",{"2":{"69":1}}],["recommend",{"2":{"53":1,"163":1,"319":1}}],["recommended",{"2":{"15":1,"21":1,"106":1,"113":1,"131":1,"170":1,"247":2,"474":1,"477":2,"480":1,"481":1}}],["recorded",{"2":{"181":1}}],["recording",{"2":{"181":1,"318":1}}],["record",{"2":{"59":1,"60":2,"62":3,"63":1,"64":2,"69":1,"73":1,"81":2,"158":1,"191":1,"195":1,"249":1,"261":1,"269":3,"311":2,"338":2,"349":4}}],["records>",{"2":{"486":2,"487":2,"488":2}}],["recordsizeentry=",{"2":{"261":1,"274":1}}],["recordsize",{"2":{"191":1,"269":3,"274":2}}],["records",{"2":{"58":2,"60":2,"66":1,"73":3,"181":2,"252":1,"269":1,"308":1,"313":1,"327":1,"338":3}}],["recursively",{"2":{"72":1}}],["recursive",{"2":{"23":1,"73":1}}],["recurse",{"2":{"17":1}}],["reliance",{"2":{"431":1}}],["reliable",{"2":{"339":1}}],["reliability",{"2":{"167":1,"339":1}}],["relies",{"2":{"400":1,"449":1}}],["relied",{"2":{"317":1}}],["relevant",{"2":{"167":1,"202":1,"245":1,"279":1,"294":1,"326":2,"474":1}}],["releasing",{"2":{"149":1}}],["releaseemptypages",{"2":{"149":1}}],["releases",{"2":{"26":6,"234":1,"243":2,"246":2}}],["released",{"2":{"26":1,"53":1,"56":1,"105":1,"145":1,"149":4,"150":1,"156":1,"238":1,"243":1,"331":1}}],["releasemode",{"2":{"20":2,"23":2}}],["release",{"0":{"26":1,"156":1,"498":1},"2":{"6":1,"18":1,"26":2,"53":1,"110":1,"129":3,"234":2,"245":2,"246":2,"247":4,"272":1,"282":1,"286":1,"337":1,"347":1,"496":1,"497":1}}],["reload",{"2":{"114":1}}],["rely",{"2":{"108":1,"128":1,"156":1,"405":1,"470":1}}],["relax",{"2":{"99":1}}],["relates",{"2":{"269":1}}],["relate",{"2":{"191":1}}],["related",{"2":{"15":1,"36":1,"63":1,"100":1,"187":1,"254":1,"272":1,"279":1,"288":1,"326":1,"328":1,"332":1,"410":1,"414":1,"420":1,"462":1}}],["relationship",{"2":{"405":1}}],["relating",{"2":{"191":1,"249":1}}],["relatively",{"2":{"48":1,"108":1,"310":1,"312":1,"313":2,"448":1}}],["relative",{"2":{"38":2,"308":2,"427":1}}],["rel",{"2":{"38":2}}],["registration",{"2":{"301":1}}],["register",{"2":{"157":1,"408":1}}],["registered",{"2":{"131":2,"301":1}}],["regularly",{"2":{"280":1}}],["regenerate",{"2":{"56":2}}],["regenerated",{"2":{"56":1}}],["regenerating",{"2":{"56":1}}],["regex",{"2":{"3":1,"4":1,"18":1,"23":2,"393":1}}],["regressout",{"2":{"274":1}}],["regressing",{"2":{"128":1}}],["regressiontest>",{"2":{"274":1}}],["regressiontest>1",{"2":{"274":1}}],["regressions",{"2":{"103":1,"128":4,"245":3}}],["regression",{"0":{"34":1,"53":1,"127":1,"286":1,"455":1},"1":{"456":1,"457":1,"458":1},"2":{"53":7,"126":1,"286":1,"309":2,"455":1,"458":1,"460":2}}],["regress",{"2":{"34":1,"53":9,"56":1,"128":3,"309":2,"314":1,"460":1,"470":1}}],["regardless",{"2":{"6":1,"212":1,"277":1,"329":1,"410":1,"414":1}}],["r",{"0":{"16":1},"1":{"19":1},"2":{"3":4,"7":4,"8":4,"16":8,"18":1,"23":1,"149":1,"275":1,"351":1,"432":8}}],["d+",{"2":{"351":1}}],["ds",{"2":{"310":1,"349":2}}],["dsign",{"2":{"18":3}}],["d75e6b40e3503f851265670a27889d8adc73f645",{"2":{"309":1}}],["dll",{"2":{"249":3,"250":4,"251":3,"255":1,"258":2,"262":1,"263":1,"264":6,"266":2,"270":1,"273":2}}],["dlibarchive",{"2":{"23":2}}],["dfa",{"2":{"321":2}}],["dfuserver",{"2":{"196":1,"265":1}}],["dfu",{"0":{"196":1},"2":{"196":2,"265":1,"301":1,"302":2}}],["dfs",{"2":{"195":1}}],["dreaded",{"2":{"318":1}}],["dragons",{"0":{"319":1}}],["drag",{"2":{"303":2,"335":1}}],["draft",{"2":{"104":1}}],["dropzone",{"2":{"340":1}}],["drop",{"2":{"239":1,"303":2}}],["driven",{"2":{"339":1}}],["drive",{"2":{"172":1}}],["drives",{"2":{"166":1,"170":1}}],["drill",{"2":{"166":1}}],["dn",{"2":{"204":5,"240":2}}],["dnsname",{"2":{"340":1}}],["dns",{"2":{"141":1,"240":2,"340":1}}],["dnf",{"2":{"11":1}}],["day",{"2":{"466":1}}],["days",{"2":{"103":1,"240":2}}],["daily",{"2":{"336":1}}],["dag",{"2":{"318":1}}],["dangling",{"2":{"221":1}}],["dashboards",{"2":{"335":1}}],["dashboard",{"2":{"204":1,"263":1,"335":4}}],["dafilesrv",{"2":{"191":1,"192":1,"196":9,"301":1}}],["daregress",{"2":{"126":1}}],["dalistorage",{"2":{"340":1}}],["daliip>",{"2":{"340":1}}],["daliip=",{"2":{"340":1}}],["dali",{"0":{"171":1},"2":{"82":1,"126":1,"171":1,"185":2,"192":5,"194":5,"195":1,"250":1,"251":2,"258":1,"261":1,"264":5,"265":1,"301":1,"340":3,"451":2,"473":1,"480":3,"488":1}}],["datum",{"2":{"403":2,"405":1,"419":2}}],["data>",{"2":{"486":2,"487":2,"488":2}}],["datamaskingmarkupvalueinfo",{"2":{"428":1}}],["datamaskingshared",{"2":{"397":1}}],["datamaskingplugin",{"2":{"397":1}}],["datamaskingengine",{"2":{"397":1}}],["datamasking",{"0":{"434":1},"1":{"435":1,"436":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1},"2":{"397":1}}],["datasink",{"2":{"358":2,"359":1,"377":1}}],["dataset>",{"2":{"486":1,"487":1,"488":1}}],["datasetleft",{"2":{"310":1}}],["datasets",{"0":{"92":1},"2":{"51":1,"62":1,"64":1,"65":2,"69":3,"70":1,"73":1,"75":1,"87":1,"92":1,"93":2,"100":2,"196":1,"249":1,"269":1,"270":3,"468":2}}],["dataset",{"0":{"93":1},"2":{"50":1,"58":1,"62":1,"64":10,"65":12,"66":1,"69":1,"70":2,"73":4,"74":2,"75":2,"76":2,"83":1,"92":7,"93":3,"94":1,"195":1,"249":2,"256":1,"261":1,"270":3,"273":1,"308":14,"309":8,"310":6,"311":1,"313":1,"325":1,"338":3,"349":2,"486":1,"487":1,"488":1}}],["databuffer",{"2":{"329":1}}],["databuffersize",{"2":{"329":1}}],["databuffers",{"2":{"329":2,"330":1}}],["database",{"2":{"250":3,"251":1,"349":1}}],["datafiles",{"2":{"249":1}}],["dataflow",{"2":{"30":1,"31":1}}],["dataencipherment",{"2":{"240":1}}],["datadog",{"2":{"175":1}}],["data",{"0":{"186":1,"188":1,"356":2,"371":1,"397":1,"465":1},"1":{"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"431":1,"432":1,"466":1},"2":{"24":2,"27":1,"28":3,"29":3,"30":2,"48":1,"58":1,"77":2,"84":1,"87":1,"94":1,"105":1,"131":1,"139":1,"149":1,"164":1,"166":3,"183":1,"184":2,"186":8,"188":7,"189":1,"192":2,"194":1,"195":2,"196":2,"204":1,"233":1,"245":1,"249":5,"250":1,"272":2,"308":1,"309":1,"317":2,"318":4,"319":2,"320":1,"321":4,"327":1,"329":2,"330":5,"339":1,"340":4,"357":9,"358":15,"359":1,"376":9,"377":1,"390":1,"397":2,"399":3,"401":1,"402":1,"410":4,"423":2,"427":1,"439":1,"461":1,"462":2,"464":1,"469":1,"486":1,"487":1,"488":1}}],["date64",{"2":{"466":1}}],["date32",{"2":{"466":1}}],["dateformat",{"2":{"26":1}}],["date",{"2":{"0":1,"23":1,"108":1,"109":1,"119":1,"124":1,"127":1,"204":1,"241":1,"267":1}}],["dbglogir",{"2":{"56":1}}],["dbglogexpr",{"2":{"56":1}}],["dboost",{"2":{"23":2}}],["dd",{"2":{"26":1}}],["dmg",{"2":{"23":1}}],["dylib",{"2":{"23":3,"249":1,"273":1}}],["dynamically",{"2":{"61":1,"162":1,"166":1,"169":1,"249":1,"273":1,"320":1,"397":1}}],["dynamic",{"0":{"157":1},"1":{"158":1,"159":1,"160":1,"161":1},"2":{"18":1,"50":1,"166":1,"169":1,"192":1,"249":2,"273":2,"309":1,"311":1,"471":1}}],["due",{"2":{"242":1,"322":1,"339":1,"448":1}}],["durations",{"2":{"182":1}}],["during",{"0":{"214":1},"2":{"18":1,"135":1,"137":1,"141":2,"167":2,"179":1,"180":1,"181":1,"186":2,"187":1,"211":1,"213":1,"214":2,"309":1,"311":1,"324":1,"328":1,"402":1,"441":1,"443":2,"456":1,"479":1}}],["dummy",{"2":{"73":1}}],["dumped",{"2":{"56":1}}],["dump",{"2":{"56":4}}],["duplicates",{"2":{"308":1,"309":3}}],["duplicate",{"2":{"105":1,"270":1,"308":1}}],["duplicated",{"2":{"58":1,"192":1,"217":1,"308":1,"329":1}}],["duplication",{"2":{"76":1,"192":1}}],["duplicating",{"2":{"69":1,"76":1}}],["duse",{"2":{"18":1,"19":1,"23":1}}],["dpkg",{"2":{"18":1,"22":1}}],["dc=com",{"2":{"204":6}}],["dc=onmicrosoft",{"2":{"204":6}}],["dc=z0lpf",{"2":{"204":6}}],["dclienttools",{"2":{"18":1,"23":1}}],["dcmake",{"2":{"18":1,"129":1}}],["dtest",{"2":{"18":1}}],["d",{"2":{"18":1,"53":1,"75":2,"130":1,"194":1,"196":3,"269":1,"325":2,"355":1,"358":2,"378":1}}],["digit",{"2":{"403":1}}],["digits",{"2":{"397":1,"403":1,"431":1}}],["digitalsignature",{"2":{"240":1}}],["digitally",{"2":{"211":2}}],["digital",{"2":{"211":2}}],["digest",{"2":{"379":3,"380":3}}],["dive",{"2":{"336":1}}],["divided",{"2":{"80":1,"321":1,"452":1}}],["division",{"2":{"24":1,"310":1}}],["dictates",{"2":{"474":1}}],["dictionary",{"2":{"321":1}}],["dicu",{"2":{"23":2}}],["didn",{"2":{"318":1}}],["did",{"0":{"317":1},"2":{"306":1,"327":1,"340":1}}],["difficult",{"2":{"217":1,"219":1,"328":1}}],["differs",{"2":{"399":1}}],["differ",{"2":{"202":1,"478":1}}],["differencing",{"2":{"471":1}}],["difference",{"2":{"56":2,"106":1,"196":1,"416":1}}],["differences",{"2":{"53":1,"128":6,"175":1,"187":1,"202":1,"268":1,"337":2}}],["differently",{"2":{"267":1,"308":1,"474":1}}],["differentiate",{"2":{"65":1}}],["different",{"0":{"199":1},"2":{"33":1,"48":1,"51":2,"53":2,"54":1,"58":4,"61":1,"63":2,"64":2,"65":1,"66":1,"70":3,"72":1,"73":1,"77":1,"83":2,"87":1,"88":1,"91":3,"93":1,"102":1,"108":3,"110":2,"114":1,"122":1,"132":1,"148":2,"156":1,"158":2,"163":1,"164":1,"169":1,"170":5,"175":3,"184":2,"186":1,"201":1,"202":1,"249":4,"250":2,"251":1,"253":1,"254":2,"262":1,"264":1,"265":1,"267":1,"270":1,"272":1,"308":3,"309":2,"310":1,"311":3,"312":2,"318":1,"321":1,"327":2,"328":2,"347":1,"399":1,"405":1,"410":1,"414":2,"441":1,"443":1,"452":1,"461":2,"462":3,"467":1,"471":1,"476":1,"496":2}}],["diagnose",{"2":{"245":1,"247":1}}],["diagnosed",{"2":{"166":1}}],["diagnosing",{"2":{"166":1}}],["diagnosis",{"2":{"166":1}}],["diagrams",{"2":{"280":1}}],["diagram",{"2":{"27":1}}],["directed",{"2":{"176":1}}],["direct",{"2":{"170":1,"221":1,"269":1}}],["direction",{"2":{"99":1}}],["directly",{"0":{"54":1},"2":{"54":1,"55":1,"58":1,"130":1,"145":1,"150":1,"196":3,"210":1,"221":1,"233":1,"266":1,"299":1,"313":1,"329":4,"347":1,"348":1,"420":1,"446":1,"492":1}}],["directories",{"2":{"44":1,"53":2,"96":1,"128":1,"340":2,"353":1}}],["directory>",{"2":{"123":1,"129":1}}],["directory",{"0":{"36":1,"54":1,"332":1,"347":1},"1":{"37":1,"38":1,"39":1},"2":{"18":5,"21":1,"23":3,"36":3,"42":2,"53":5,"54":1,"55":2,"96":1,"123":4,"126":1,"128":2,"129":1,"135":1,"190":1,"197":1,"204":1,"211":1,"309":1,"331":1,"346":1,"347":2,"353":3,"460":1,"482":2,"491":2,"492":2,"495":1}}],["dir",{"2":{"45":1,"128":1}}],["dir=",{"2":{"23":4}}],["disposition",{"2":{"184":1}}],["display",{"2":{"114":1,"485":1}}],["displayed",{"2":{"82":1,"279":1}}],["displaymode",{"2":{"26":1}}],["discouraged",{"2":{"477":1}}],["discover",{"2":{"336":1}}],["discovering",{"2":{"318":1}}],["discern",{"2":{"424":1}}],["discretion",{"2":{"106":1}}],["discusses",{"2":{"172":1,"183":1}}],["discussed",{"2":{"110":1,"245":1,"309":1}}],["discuss",{"2":{"106":5,"177":1}}],["discussions",{"2":{"308":4}}],["discussion",{"2":{"106":2,"252":2,"308":6,"309":1,"310":1,"314":1}}],["disambiguation",{"2":{"310":1}}],["disambiguate",{"2":{"64":1,"100":1}}],["disambiguated",{"2":{"64":1}}],["disadvantage",{"2":{"84":1,"163":2}}],["disabled",{"2":{"12":1}}],["disable",{"2":{"12":1,"482":1,"485":1,"486":2,"487":2,"488":2}}],["distinguished",{"2":{"240":1}}],["distinct",{"2":{"128":1,"152":1,"196":1,"399":1}}],["distinction",{"2":{"62":1,"186":1,"419":1}}],["distribute",{"2":{"308":1}}],["distributes",{"2":{"76":1}}],["distributed",{"2":{"28":3,"29":2,"192":1,"301":1,"309":1,"310":1}}],["distribution",{"2":{"69":1,"170":2,"308":1,"309":1}}],["distributions",{"2":{"16":1,"122":1,"242":1}}],["distrocheck",{"2":{"36":1}}],["distro",{"2":{"21":1,"22":1,"123":1}}],["distros",{"2":{"16":1,"123":1}}],["disksize",{"2":{"191":2}}],["disk",{"2":{"59":1,"65":1,"76":1,"89":1,"93":1,"95":1,"157":1,"159":1,"163":2,"169":1,"191":1,"192":1,"194":2,"195":1,"196":1,"269":1,"274":1,"321":1,"329":1}}],["doxyfile",{"2":{"447":1}}],["doxygen",{"2":{"447":4}}],["dot",{"2":{"447":1}}],["dodgycharacters",{"2":{"349":1}}],["doperf",{"2":{"328":1}}],["dopenldap",{"2":{"23":2}}],["dopenssl",{"2":{"5":2}}],["doit",{"2":{"221":1}}],["doing",{"2":{"187":1,"219":1,"317":1,"327":1,"451":1}}],["domains",{"2":{"399":2,"407":3}}],["domain",{"0":{"399":1},"2":{"182":4,"204":2,"399":6,"400":2,"401":3,"403":2,"406":1,"407":7,"419":2,"423":1,"425":2,"432":4,"441":2}}],["double",{"2":{"303":1,"320":1,"439":1,"466":1}}],["doubly",{"2":{"149":2}}],["doubt",{"2":{"106":1}}],["dos",{"2":{"105":1,"327":1}}],["dobuildassignxxx",{"2":{"91":1}}],["dobuildexprxxx",{"2":{"91":1}}],["don",{"2":{"58":1,"62":1,"65":1,"70":1,"74":1,"88":1,"103":2,"104":1,"194":1,"196":1,"238":1,"319":2,"326":1,"327":1,"342":1,"480":1}}],["done",{"2":{"45":1,"49":1,"50":1,"70":1,"72":2,"84":1,"92":1,"100":1,"106":2,"149":1,"162":1,"194":1,"196":2,"211":1,"223":1,"252":2,"261":1,"266":2,"271":1,"275":1,"299":2,"313":1,"321":1,"322":1,"324":1,"326":6,"328":1,"358":1,"359":1,"428":1}}],["downstream",{"2":{"318":1,"319":2}}],["downside",{"2":{"156":1}}],["down",{"2":{"48":1,"56":1,"145":1,"166":1,"172":1,"239":1,"320":1,"327":1}}],["downloaded",{"2":{"342":1,"345":1}}],["download",{"2":{"18":1,"25":1,"120":2,"131":1,"447":1}}],["doesn",{"2":{"53":1,"58":1,"65":2,"67":1,"68":1,"69":1,"70":1,"83":1,"106":2,"108":1,"162":1,"192":1,"313":1,"320":1,"327":1,"340":1,"353":1}}],["does",{"0":{"320":1},"2":{"48":1,"58":1,"59":3,"69":2,"103":1,"105":5,"106":1,"124":1,"145":1,"149":1,"159":1,"163":1,"169":1,"172":1,"187":1,"191":1,"196":2,"201":2,"212":2,"221":1,"233":2,"241":1,"269":1,"270":2,"299":1,"307":1,"308":5,"309":1,"310":2,"311":1,"313":3,"320":1,"324":1,"326":2,"327":4,"329":2,"330":1,"399":1,"402":1,"414":1,"419":1,"427":2,"436":2,"439":1,"443":2,"446":2,"453":3,"454":3,"474":1}}],["docfeedback",{"2":{"307":1,"338":1}}],["docker",{"2":{"239":4,"340":1}}],["documenting",{"0":{"277":1},"1":{"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"223":1,"235":1,"276":1}}],["documents",{"2":{"112":1,"197":1,"202":1,"288":2,"299":1}}],["documented",{"2":{"110":1,"200":1,"245":1,"282":1,"452":2}}],["document",{"0":{"79":1,"115":1,"116":1},"2":{"99":1,"101":2,"108":1,"110":1,"112":2,"115":1,"116":3,"120":1,"135":1,"166":2,"201":1,"209":2,"249":3,"269":1,"276":1,"277":1,"284":1,"285":1,"287":2,"288":1,"295":1,"301":1,"337":1,"400":1,"427":2,"428":2}}],["documentation",{"0":{"33":1,"38":1,"111":1,"112":1,"113":1,"114":1,"197":1,"198":1,"200":1,"276":1,"290":1,"291":1,"303":1,"331":1,"333":1,"334":1,"343":1,"447":1},"1":{"34":1,"112":1,"113":1,"114":1,"115":1,"116":1,"117":1,"198":1,"199":1,"200":1,"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"292":1,"293":1,"294":1,"295":1,"296":1,"297":1,"332":1,"333":1,"334":1,"344":1,"345":1,"346":1,"347":1,"348":1},"2":{"25":1,"33":1,"36":1,"106":2,"108":4,"111":1,"112":5,"113":2,"114":5,"116":1,"122":1,"197":1,"198":2,"206":1,"236":1,"271":1,"276":3,"277":3,"279":3,"280":3,"281":1,"282":1,"283":2,"287":1,"288":1,"289":3,"290":1,"291":1,"298":1,"299":3,"311":1,"331":4,"334":1,"447":3,"461":1,"479":1}}],["doc",{"2":{"44":2,"289":1}}],["docs",{"2":{"44":2,"114":1,"131":2,"201":1,"288":4,"300":1,"331":1,"334":1,"447":1}}],["docbook",{"2":{"38":1,"122":1}}],["docmacros",{"2":{"36":1}}],["do",{"0":{"318":1},"2":{"15":1,"18":3,"48":1,"54":1,"58":1,"84":1,"103":1,"124":1,"128":1,"163":1,"189":1,"192":1,"216":1,"219":1,"221":1,"225":1,"233":1,"234":1,"240":1,"241":1,"245":1,"249":2,"263":1,"269":1,"299":6,"302":1,"308":2,"309":3,"310":1,"311":2,"313":1,"317":1,"318":1,"321":2,"324":1,"326":2,"327":1,"329":1,"330":3,"333":1,"335":2,"340":6,"399":2,"405":1,"437":2,"448":1,"470":1}}],["denyfilescopedelete",{"2":{"454":1}}],["denyfilescopemodify",{"2":{"454":1}}],["denyfilescopeview",{"2":{"454":1}}],["denyworkunitscopedelete",{"2":{"453":1}}],["denyworkunitscopemodify",{"2":{"453":1}}],["denyworkunitscopeview",{"2":{"453":1}}],["denoted",{"2":{"437":1}}],["denotes",{"2":{"17":1}}],["deem",{"2":{"435":1}}],["deep",{"2":{"166":1}}],["deeply",{"2":{"23":1}}],["deduced",{"2":{"269":1,"309":1}}],["deduping",{"2":{"249":1}}],["dedup",{"2":{"62":1,"92":1,"308":3,"309":1,"313":1,"326":1}}],["derive",{"2":{"308":1}}],["derived",{"0":{"69":1},"2":{"58":2,"63":1,"69":4,"88":2,"190":2,"192":2,"224":2,"234":1,"269":2,"272":1,"274":1,"310":3,"311":2,"348":1,"397":1}}],["derivatives",{"2":{"214":1}}],["deallocate",{"2":{"145":1}}],["deadlock",{"2":{"105":1,"158":1,"159":2,"160":1}}],["demo",{"2":{"130":1}}],["demands",{"2":{"166":1}}],["demand",{"2":{"50":1,"61":1,"69":1,"72":1,"83":1,"143":1,"166":1,"270":1,"401":1,"441":1}}],["decompress",{"2":{"321":2,"390":1}}],["decompressing",{"2":{"321":1}}],["decompression",{"2":{"321":1}}],["decompressed",{"2":{"321":2}}],["decouple",{"2":{"233":1}}],["decimal",{"2":{"466":1}}],["deciding",{"2":{"308":1,"326":1,"330":1}}],["decides",{"2":{"400":1}}],["decided",{"2":{"308":1,"310":1,"326":1}}],["decide",{"2":{"281":1,"427":2}}],["decisions",{"2":{"170":1,"217":3}}],["decrease",{"2":{"179":2}}],["decreased",{"2":{"58":1}}],["decrementing",{"2":{"156":1}}],["decremented",{"2":{"156":1}}],["declaration=",{"2":{"486":1,"487":1,"488":1}}],["declaration",{"2":{"224":1,"234":1,"483":1,"484":1}}],["declarative",{"0":{"50":1,"99":1},"2":{"49":1,"50":1,"68":1,"99":1}}],["declared",{"2":{"401":1,"412":1,"420":1,"432":1,"476":1,"483":1}}],["declares",{"2":{"219":1,"397":1,"401":1,"421":1}}],["declare",{"2":{"96":1,"441":1,"479":1}}],["deprecated",{"2":{"314":1}}],["deprecate",{"2":{"217":1}}],["deploy",{"2":{"247":1,"340":1}}],["deployment",{"2":{"202":2,"204":1}}],["deployments",{"2":{"202":2,"204":2,"247":2,"282":2,"285":1}}],["deployed",{"2":{"190":1,"317":1,"327":1}}],["deploying",{"2":{"54":1,"246":1,"247":1,"473":1}}],["depth",{"2":{"70":1,"279":1}}],["depending",{"2":{"90":1,"91":1,"123":1,"129":1,"170":1,"188":1,"318":1,"321":1,"449":1}}],["dependson",{"2":{"269":1}}],["depends",{"2":{"74":1,"264":1,"289":1,"308":1,"309":1,"312":1,"401":1,"420":1,"421":1,"424":1,"428":1,"435":1,"440":1,"441":1,"443":2,"472":1,"482":1}}],["depend",{"2":{"22":1,"267":1,"309":1,"312":1,"313":1,"427":1,"431":1}}],["dependency",{"2":{"18":1,"36":1,"43":1,"61":1,"131":1,"242":1,"254":2,"266":2,"267":5,"274":1}}],["dependencies",{"2":{"12":2,"13":1,"18":1,"22":1,"36":3,"37":2,"73":1,"80":1,"81":1,"83":3,"122":1,"130":1,"172":1,"179":1,"196":1,"246":1,"254":1,"256":1,"266":4,"267":3,"269":2,"270":4,"278":1,"293":1,"318":1,"438":1}}],["dependent",{"0":{"239":1},"1":{"240":1,"241":1},"2":{"13":1,"70":1,"83":1,"187":1,"188":1,"312":1,"428":1,"474":1}}],["desdl",{"2":{"471":1,"474":1,"480":1,"487":1,"488":1}}],["desktop",{"2":{"340":1}}],["destination",{"2":{"329":1}}],["destruction",{"2":{"169":1,"318":1}}],["destructors",{"2":{"233":1}}],["destructor",{"2":{"149":2,"458":1}}],["destroying",{"2":{"169":1}}],["destroyed",{"2":{"149":1,"233":1,"319":1}}],["destroys",{"2":{"103":1}}],["destroy",{"2":{"89":1}}],["deserves",{"2":{"277":1}}],["deserialize",{"2":{"89":1}}],["descendent",{"2":{"474":1,"477":1}}],["descendant",{"2":{"474":1}}],["desc=",{"2":{"274":1}}],["describing",{"2":{"106":1,"186":1,"406":1,"423":1,"438":1,"441":2,"443":2}}],["describes",{"2":{"79":2,"101":1,"107":1,"132":1,"166":1,"168":1,"184":1,"186":1,"249":1,"270":1,"404":1,"406":1,"432":3,"444":1}}],["describe",{"2":{"33":1,"103":1,"128":1,"173":1,"210":1,"223":1,"249":2,"270":1,"272":1,"278":1,"339":2,"428":1,"490":2}}],["described",{"2":{"27":1,"136":1,"166":1,"216":1,"217":1,"218":1,"330":1,"346":1,"408":1,"416":1,"432":1,"444":1,"477":1}}],["descriptions",{"2":{"444":1}}],["description",{"0":{"24":1,"461":1},"2":{"106":1,"108":1,"187":3,"188":2,"249":1,"261":1,"308":1,"312":1,"313":1,"337":4,"397":1,"444":1,"476":1}}],["despray",{"2":{"196":2}}],["desire",{"2":{"318":2}}],["desires",{"2":{"169":1}}],["desired",{"2":{"53":1,"308":2,"329":1,"348":1}}],["designing",{"2":{"308":1,"311":1}}],["designed",{"2":{"145":1,"172":1,"174":1,"217":2,"242":1,"278":1,"313":1,"330":2,"336":1,"345":1,"448":1,"490":1}}],["design",{"0":{"165":1,"172":1,"231":1,"232":1,"346":1},"1":{"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"172":1,"173":1,"174":1,"175":1,"176":1,"177":1,"178":1,"179":1,"180":1,"181":1,"182":1,"183":1,"184":1,"185":1,"186":1,"187":1,"188":1,"189":1,"232":1,"233":2,"234":2,"235":2},"2":{"58":2,"102":1,"103":1,"105":1,"106":3,"145":1,"146":1,"166":2,"170":1,"172":2,"183":1,"199":2,"217":1,"232":1,"233":1,"249":1,"284":1,"308":6,"309":1,"310":1,"311":1,"321":1}}],["delimited",{"2":{"479":1,"485":1}}],["deliverables",{"2":{"461":1}}],["deliver",{"2":{"277":1}}],["delivery",{"2":{"29":1}}],["deliberately",{"2":{"233":1}}],["delta",{"2":{"190":1}}],["delays",{"2":{"156":1}}],["delay",{"0":{"156":1},"2":{"143":1,"313":1,"324":2,"325":1,"327":3}}],["delayed",{"2":{"50":1,"72":1,"318":1,"320":1}}],["deletion",{"2":{"455":1}}],["delete",{"0":{"373":1},"2":{"233":1,"291":1,"351":1,"358":1,"373":1,"453":2,"454":2,"455":1,"458":1,"471":2}}],["deleted",{"2":{"108":1,"143":1,"234":1,"271":1,"456":3,"458":3}}],["delegates",{"2":{"67":1}}],["deferred",{"2":{"474":1,"486":1,"487":1}}],["defender",{"2":{"342":1}}],["deflate",{"2":{"368":2,"390":1}}],["deficiency",{"2":{"308":1}}],["definition>",{"2":{"487":1,"488":1}}],["definition",{"2":{"64":1,"72":1,"73":1,"74":2,"77":2,"80":1,"81":1,"95":2,"193":1,"196":1,"213":1,"258":1,"269":5,"274":2,"338":2,"420":1,"424":1,"428":1,"441":3,"471":11,"477":1,"478":3,"479":2,"481":1,"487":1,"488":1}}],["definitions>",{"2":{"487":2}}],["definitions",{"0":{"167":1},"2":{"61":1,"72":1,"73":3,"77":1,"82":1,"85":1,"167":1,"260":1,"471":2,"481":3}}],["defining",{"2":{"56":1,"186":1,"272":1,"310":1,"394":1,"440":1,"461":1}}],["definescolumnlist",{"2":{"62":1}}],["defines",{"2":{"62":2,"175":1,"181":1,"185":1,"188":2,"269":1,"311":1,"397":3,"399":1,"401":2,"403":2,"408":1,"425":1,"438":1}}],["defined",{"2":{"45":2,"53":1,"58":1,"60":1,"81":1,"85":1,"90":1,"141":2,"146":1,"167":1,"181":1,"183":1,"187":3,"189":2,"190":1,"210":1,"245":1,"250":2,"257":1,"263":1,"266":1,"267":3,"269":1,"311":3,"353":1,"397":1,"400":2,"402":3,"403":1,"404":4,"405":1,"408":1,"410":1,"412":1,"414":1,"416":1,"419":1,"420":3,"421":4,"428":1,"436":3,"438":3,"439":1,"441":4,"443":7,"448":1,"451":1,"477":1}}],["define",{"2":{"44":2,"45":3,"64":1,"73":1,"130":1,"134":1,"188":1,"309":1,"310":1,"311":3,"330":1,"339":1,"385":1,"394":1,"399":1,"402":2,"404":2,"405":1,"408":1,"419":3,"421":1,"423":1,"478":1}}],["deftype",{"2":{"96":1}}],["defaultversion",{"2":{"441":1}}],["defaults",{"2":{"56":1,"271":1,"308":3,"451":5,"491":2}}],["default",{"0":{"282":1,"365":1,"366":1},"2":{"3":2,"4":1,"5":1,"12":1,"19":1,"63":1,"88":1,"91":1,"105":1,"114":1,"129":1,"152":1,"153":1,"155":2,"181":1,"188":1,"190":2,"194":1,"196":2,"204":4,"240":2,"241":1,"254":1,"266":1,"269":1,"290":1,"308":1,"311":2,"318":1,"360":1,"361":1,"365":2,"368":1,"371":1,"401":2,"402":2,"403":1,"404":1,"407":7,"408":6,"410":1,"412":4,"414":2,"416":2,"419":1,"425":1,"428":1,"431":1,"432":2,"436":1,"437":3,"438":1,"439":1,"441":2,"451":4,"456":1,"474":1,"476":2,"479":1,"480":2,"485":2,"486":1,"487":1,"495":2}}],["determination",{"2":{"427":1,"428":1}}],["determining",{"2":{"193":1,"266":1}}],["determined",{"2":{"269":1,"320":1,"448":2}}],["determine",{"2":{"56":2,"149":2,"183":1,"196":1,"267":1,"270":1,"320":2,"346":1,"411":1,"415":1}}],["determines",{"2":{"36":1,"269":1,"419":1,"428":1}}],["detectable",{"2":{"420":1}}],["detect",{"2":{"169":1,"209":1,"420":1,"421":1,"431":1}}],["detected",{"2":{"21":1,"166":1,"420":1}}],["details",{"0":{"84":1,"199":1,"269":1},"1":{"85":1},"2":{"33":1,"56":4,"70":1,"72":1,"73":1,"80":1,"81":1,"82":1,"94":2,"100":1,"104":1,"105":1,"108":2,"110":1,"113":1,"119":1,"122":1,"131":3,"196":1,"197":1,"199":3,"250":1,"251":1,"256":2,"257":1,"258":1,"260":1,"261":1,"262":1,"264":1,"268":1,"270":1,"271":1,"278":1,"290":2,"308":5,"309":1,"310":2,"311":1,"339":1,"477":1,"482":1}}],["detail",{"2":{"27":1,"33":1,"56":2,"63":1,"103":2,"209":1,"278":1,"280":1,"388":1,"405":1,"471":1}}],["detailed",{"2":{"15":1,"249":1,"253":1,"258":1,"279":2,"294":1,"336":1}}],["deblacklisting",{"2":{"327":1}}],["deblacklister",{"2":{"327":6}}],["debian",{"2":{"124":1}}],["debugger",{"2":{"313":1}}],["debugging",{"0":{"129":1},"2":{"31":1,"56":2,"310":1,"325":1}}],["debugnlp>",{"2":{"274":1}}],["debugnlp>1",{"2":{"274":1}}],["debug>",{"2":{"259":2,"274":2}}],["debug",{"2":{"18":1,"31":1,"55":1,"56":2,"126":4,"128":3,"129":3,"204":1,"247":1,"259":1,"313":1,"325":1,"340":1,"431":1,"485":1}}],["deb",{"2":{"15":1,"21":1,"22":1,"36":3,"124":1}}],["devalue",{"2":{"431":1}}],["deviations",{"0":{"449":1,"450":1}}],["deviation",{"2":{"308":1}}],["device",{"2":{"190":3,"193":2}}],["devices",{"2":{"190":1,"196":2}}],["devops",{"2":{"166":1}}],["devdoc",{"0":{"332":1},"2":{"112":4,"115":2,"116":3,"288":8}}],["developed",{"2":{"217":1}}],["developers",{"2":{"31":1,"101":1,"197":1,"217":1,"238":2,"239":1,"242":1,"276":1,"277":1,"279":1,"280":1,"285":1,"288":1}}],["developer",{"0":{"33":1,"111":1,"197":1,"237":1,"489":1},"1":{"34":1,"112":1,"113":1,"114":1,"115":1,"116":1,"117":1,"198":1,"199":1,"200":1,"238":1,"239":1,"240":1,"241":1,"242":1,"243":1,"490":1,"491":1,"492":1},"2":{"25":1,"102":1,"104":1,"106":12,"112":1,"131":1,"198":1,"277":1,"281":1,"283":2,"288":1,"485":1}}],["developing",{"2":{"31":1,"309":1,"325":1}}],["developmental",{"2":{"170":1}}],["development",{"0":{"118":1,"131":1},"1":{"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"129":1},"2":{"18":1,"31":1,"114":2,"128":2,"131":1,"198":2,"217":2,"240":1,"425":1,"461":1,"474":1}}],["devel",{"2":{"7":25,"8":18,"10":10,"11":13,"16":1}}],["dev",{"2":{"3":28,"4":25,"8":1,"16":1,"23":1,"53":4,"114":1,"133":1,"485":1}}],["fn",{"2":{"366":2}}],["fmt",{"2":{"355":2}}],["fs",{"2":{"326":2}}],["fsavecpptempfiles",{"2":{"271":1}}],["fspanmultiplecpp=0",{"2":{"271":1}}],["ftslave",{"2":{"196":5}}],["ftraceir",{"2":{"56":2,"310":1}}],["f+g",{"2":{"196":1}}],["fqdn",{"2":{"139":1}}],["flushed",{"2":{"326":1}}],["flushes",{"2":{"326":1}}],["flight",{"2":{"320":1,"329":1}}],["float",{"2":{"466":2}}],["florida",{"2":{"240":1}}],["flowcharts",{"2":{"280":1}}],["flows",{"2":{"270":1}}],["flowing",{"2":{"70":1}}],["flow",{"0":{"51":1},"2":{"77":1,"91":1,"103":1,"266":1}}],["flame",{"2":{"328":4}}],["flat",{"2":{"191":1}}],["flavors",{"2":{"123":1}}],["flags",{"2":{"63":1,"69":1,"308":2,"311":1,"485":2}}],["flag",{"2":{"54":1,"149":2,"156":1,"157":1,"192":1,"196":1,"308":3,"309":1,"311":1,"313":1,"324":2,"436":1,"439":1,"441":1}}],["flexible",{"2":{"190":1,"474":1}}],["flex",{"2":{"3":1,"4":1,"7":1,"8":1,"10":1,"11":1,"12":1,"13":1,"85":1}}],["fabricated",{"2":{"339":1}}],["familyname",{"2":{"338":1}}],["fashion",{"2":{"327":2,"330":1}}],["fast",{"2":{"145":1,"211":1,"321":1,"327":1,"330":1}}],["faster",{"2":{"114":1,"155":1,"289":1}}],["fall",{"2":{"309":1,"441":1}}],["falls",{"2":{"308":1,"410":1}}],["falseresult",{"2":{"267":1}}],["false",{"2":{"37":1,"72":1,"75":1,"220":1,"240":1,"266":1,"269":2,"274":1,"275":2,"358":2,"359":1,"376":2,"377":1,"378":1,"382":1,"390":1,"419":1,"439":1,"451":1,"479":1}}],["fatuous",{"2":{"308":1}}],["faqs",{"0":{"335":1,"340":1},"2":{"279":1}}],["favour",{"2":{"217":1,"218":1}}],["faults",{"2":{"221":1,"326":1}}],["fault",{"2":{"166":2,"192":1,"479":1}}],["far",{"2":{"84":1,"162":1,"196":1,"309":1,"324":1}}],["failover",{"2":{"327":1}}],["failed",{"2":{"185":1,"192":1,"267":1,"320":1,"327":4,"360":1,"456":1,"458":2}}],["failures",{"2":{"169":2,"327":1}}],["failure",{"2":{"144":1,"169":1,"267":3,"327":1,"458":2,"460":1}}],["fail",{"2":{"105":1,"160":1,"184":1,"192":1,"242":1,"327":1}}],["fails",{"2":{"43":1,"103":1,"143":1,"149":1,"267":1,"309":1,"329":1,"460":3}}],["fairly",{"2":{"75":1,"84":1,"106":1,"155":1,"192":1,"266":1}}],["fact",{"2":{"308":1}}],["factories",{"2":{"313":1,"318":1}}],["factor",{"2":{"182":1}}],["factory",{"2":{"59":1,"80":1,"81":1,"88":1,"250":1,"255":1,"257":1,"268":1,"269":1,"270":1,"313":2,"318":1,"327":2}}],["fac3",{"2":{"257":1,"275":1}}],["fac2",{"2":{"257":1,"270":1,"275":1}}],["fac",{"2":{"81":1,"269":1}}],["facilitates",{"2":{"474":1}}],["facilitate",{"2":{"27":1}}],["fakeroot",{"2":{"36":1}}],["friendly",{"2":{"217":1}}],["fragmented",{"2":{"159":1}}],["fragmentation",{"2":{"147":1}}],["framework",{"0":{"165":1,"172":1,"173":1,"397":1},"1":{"166":1,"167":1,"168":1,"169":1,"170":1,"171":1,"172":1,"173":1,"174":2,"175":2,"176":2,"177":2,"178":2,"179":2,"180":2,"181":2,"182":2,"183":1,"184":1,"185":1,"186":1,"187":1,"188":1,"189":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1,"408":1,"409":1,"410":1,"411":1,"412":1,"413":1,"414":1,"415":1,"416":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"431":1,"432":1},"2":{"23":1,"166":2,"167":4,"168":1,"169":1,"170":1,"172":12,"173":2,"174":5,"175":2,"176":1,"187":4,"189":1,"199":1,"397":4,"399":1,"400":2,"403":1,"404":1,"405":1,"406":1,"407":1,"408":2,"419":1}}],["frameworks",{"2":{"23":1}}],["front",{"2":{"106":1,"169":1}}],["from",{"0":{"50":1,"54":1,"99":1,"121":1,"141":1,"272":1,"449":1},"1":{"122":1,"123":1},"2":{"5":1,"13":1,"27":1,"28":2,"29":1,"37":1,"38":2,"44":3,"49":1,"53":1,"54":2,"55":1,"56":1,"61":1,"63":1,"64":2,"65":3,"69":3,"72":2,"73":1,"76":2,"79":1,"81":2,"83":2,"85":1,"87":1,"88":1,"89":1,"93":1,"99":1,"103":1,"109":1,"112":1,"122":1,"123":3,"126":1,"130":1,"133":1,"138":1,"139":1,"140":2,"141":1,"143":1,"145":2,"147":4,"149":5,"154":2,"159":1,"160":1,"161":1,"163":1,"166":2,"170":1,"171":1,"174":1,"175":1,"182":1,"187":1,"189":1,"190":1,"191":1,"192":3,"193":2,"194":3,"195":3,"196":15,"197":1,"204":4,"210":1,"221":1,"223":1,"233":1,"234":1,"240":1,"243":1,"245":1,"249":2,"250":1,"251":1,"252":3,"255":1,"262":1,"264":2,"266":2,"267":2,"269":5,"270":8,"272":2,"274":1,"308":6,"309":2,"310":4,"311":3,"312":2,"313":3,"314":1,"318":3,"319":3,"320":2,"321":1,"323":3,"324":1,"326":8,"327":2,"330":7,"340":3,"345":2,"346":1,"348":1,"365":1,"396":1,"397":3,"400":1,"407":1,"420":1,"427":1,"432":1,"441":1,"442":2,"448":1,"449":1,"452":1,"468":1,"471":8,"472":1,"480":1,"483":2,"484":2,"491":2,"492":1,"499":1}}],["frequency",{"2":{"308":1}}],["frequently",{"2":{"88":1,"196":1,"279":1,"286":1,"400":1,"403":1,"438":1}}],["freeing",{"2":{"149":1,"160":1,"234":1}}],["freebase",{"2":{"149":1}}],["freebsd",{"2":{"123":1}}],["free",{"2":{"103":2,"143":3,"149":1,"150":1,"155":5,"156":1,"157":2,"161":1,"189":1,"191":1,"242":1,"307":1}}],["freed",{"2":{"58":1,"145":2,"155":2,"156":1,"234":1,"313":2}}],["frustrating",{"2":{"48":1}}],["federated",{"2":{"327":1}}],["fedora389",{"2":{"204":1}}],["fedora",{"0":{"10":1,"11":1},"2":{"10":1,"11":1}}],["feature=",{"2":{"474":1,"487":2}}],["feature",{"0":{"277":1,"284":1,"285":1,"291":1},"1":{"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"292":1,"293":1,"294":1,"295":1,"296":1,"297":1},"2":{"106":1,"163":1,"204":1,"209":1,"276":1,"277":2,"278":7,"279":2,"280":2,"291":2,"292":1,"293":1,"294":1,"295":1,"296":1,"297":1,"299":3,"308":1,"309":3,"328":1,"384":1,"452":3,"479":1,"486":1,"492":2}}],["features",{"2":{"26":1,"103":1,"113":1,"245":3,"276":1,"279":2,"299":1,"309":2,"325":1,"451":1}}],["feedback",{"2":{"103":1,"280":1,"338":1}}],["feels",{"2":{"327":1}}],["feeling",{"2":{"170":1}}],["feel",{"2":{"45":1,"103":1,"307":1,"327":1}}],["fewer",{"2":{"148":1,"308":1}}],["few",{"2":{"59":1,"62":1,"75":1,"83":1,"84":1,"102":1,"103":1,"108":1,"155":1,"161":2,"195":1,"217":2,"221":2,"266":1,"287":1,"299":1,"308":2,"310":1,"337":1}}],["fetchentire=",{"2":{"274":1}}],["fetching",{"2":{"195":1}}],["fetch",{"2":{"23":1,"326":1}}],["f",{"2":{"22":1,"68":1,"133":1,"194":1,"196":2,"259":1,"311":1,"340":1}}],["five",{"2":{"403":1,"412":1,"431":1}}],["fifthly",{"2":{"326":1}}],["fixversion",{"2":{"495":1}}],["fixing",{"2":{"461":1}}],["fixes",{"2":{"108":1,"110":1,"194":1,"245":13,"246":3}}],["fixed",{"2":{"22":1,"152":1,"153":1,"169":1,"191":1,"270":1,"466":1}}],["fix",{"2":{"106":7,"498":1}}],["finished",{"2":{"313":2}}],["finishes",{"2":{"145":1,"270":1,"310":1}}],["fine",{"2":{"190":1,"192":1,"269":1,"270":1}}],["finaliserowclear",{"2":{"313":1}}],["finally",{"2":{"308":1,"310":1,"480":1}}],["final",{"2":{"68":1,"309":1,"310":1,"360":1,"427":1}}],["finds",{"2":{"270":1}}],["findapr",{"2":{"45":1}}],["finding",{"2":{"45":1,"212":1}}],["find",{"2":{"36":1,"45":6,"69":1,"84":1,"95":1,"116":1,"216":1,"249":2,"266":1,"270":1,"308":2,"340":1,"439":1}}],["findxxxxx",{"0":{"45":1},"2":{"36":1,"39":1}}],["fits",{"2":{"106":1,"249":1}}],["fit",{"2":{"68":1,"196":1,"310":1}}],["filling",{"2":{"492":1}}],["fill",{"2":{"108":1,"195":2}}],["filled",{"2":{"90":1}}],["filter`",{"2":{"335":1}}],["filtering",{"2":{"92":1,"270":1}}],["filtered",{"2":{"92":2,"158":1,"328":2}}],["filters",{"2":{"75":1,"76":5,"92":1,"128":1}}],["filter",{"2":{"62":1,"76":1,"81":1,"92":1,"109":1,"128":1,"161":1,"192":2,"269":4,"270":1,"274":1,"312":1,"335":3,"349":1}}],["file=",{"2":{"474":5}}],["file3",{"2":{"371":1}}],["file2",{"2":{"371":1}}],["file1",{"2":{"371":1}}],["file>",{"2":{"340":1}}],["filepartition",{"2":{"192":1}}],["fileposition",{"2":{"95":1,"192":1,"269":1,"274":1}}],["filepos",{"0":{"95":1}}],["filecollection",{"2":{"192":4}}],["fileview",{"2":{"192":1,"194":1}}],["filename=",{"2":{"274":1}}],["filenames",{"2":{"274":1}}],["filename",{"2":{"191":1,"192":4,"269":1,"270":1,"274":1,"356":1,"485":1}}],["filehooks",{"2":{"54":2}}],["file",{"0":{"195":1,"275":1,"323":1,"353":1,"454":1,"473":1},"2":{"21":1,"28":2,"29":1,"36":4,"38":2,"43":1,"44":4,"45":1,"53":1,"54":2,"55":2,"56":4,"81":1,"85":1,"95":2,"104":2,"112":2,"115":5,"116":2,"123":2,"129":1,"133":2,"140":1,"190":1,"191":16,"192":12,"193":1,"194":2,"195":2,"196":20,"212":3,"218":4,"219":1,"233":2,"240":2,"265":1,"269":4,"270":1,"271":1,"273":2,"274":1,"283":1,"284":1,"285":1,"299":3,"301":1,"310":4,"311":1,"321":1,"323":5,"326":8,"328":2,"329":1,"330":1,"338":2,"340":3,"342":2,"345":2,"346":1,"347":3,"350":2,"353":5,"356":6,"357":2,"397":1,"448":4,"451":2,"452":3,"454":6,"460":2,"461":2,"462":1,"467":1,"469":1,"471":1,"472":3,"473":5,"474":5,"481":2,"482":7,"483":1,"484":1,"485":2,"486":1,"494":4,"497":1}}],["filesbasedn",{"2":{"204":1}}],["filescopes",{"2":{"204":2}}],["fileservices",{"2":{"196":1}}],["fileslice",{"2":{"192":1}}],["filesystem",{"2":{"27":1}}],["files",{"0":{"35":1,"36":1,"40":1,"43":1,"44":1,"191":1,"218":1,"462":1},"1":{"36":1,"37":2,"38":2,"39":2,"40":1,"41":2,"42":2,"43":2,"44":2,"45":1,"463":1,"464":1,"465":1,"466":1},"2":{"18":1,"21":1,"33":1,"36":3,"39":1,"42":1,"43":2,"53":3,"54":1,"65":1,"72":2,"83":1,"87":1,"95":1,"96":1,"108":2,"114":1,"123":1,"128":4,"130":1,"131":1,"190":2,"191":1,"192":4,"193":3,"195":2,"196":7,"201":1,"218":5,"230":1,"254":1,"271":7,"273":1,"277":2,"286":1,"323":3,"326":2,"328":3,"329":1,"345":1,"346":1,"347":2,"356":1,"357":3,"455":1,"460":2,"462":3,"468":2,"469":1,"470":3,"473":1,"474":3,"476":1,"482":1,"483":2,"484":2,"485":1,"486":1,"491":2,"492":1,"495":1}}],["field",{"0":{"64":1,"94":1},"1":{"65":1,"66":1},"2":{"58":2,"64":4,"66":1,"74":1,"75":2,"76":1,"95":1,"191":1,"310":1,"312":1,"321":2,"349":2,"449":1}}],["fields",{"2":{"51":1,"58":1,"60":2,"61":1,"64":2,"65":1,"69":1,"70":2,"73":2,"74":1,"75":1,"77":3,"81":1,"89":1,"90":1,"95":1,"191":1,"270":1,"308":8,"309":1,"310":3,"321":6,"335":1,"338":3,"346":1,"449":2}}],["firstly",{"2":{"326":2}}],["firstname",{"2":{"308":1,"349":1}}],["first",{"2":{"16":1,"56":2,"64":1,"65":2,"72":2,"84":1,"92":1,"99":1,"114":1,"124":1,"129":1,"143":1,"145":2,"157":1,"184":1,"190":2,"192":2,"193":1,"204":1,"214":2,"254":2,"266":2,"269":2,"270":1,"276":1,"301":3,"308":8,"309":1,"310":3,"311":1,"313":4,"319":2,"320":1,"321":1,"325":1,"327":1,"330":1,"403":1,"420":1,"431":1,"437":2,"451":1,"478":1}}],["fusion",{"2":{"326":1}}],["furher",{"2":{"186":1}}],["further",{"2":{"75":1,"106":2,"156":1,"184":1,"186":2,"188":1,"211":1,"257":1,"269":1,"270":1,"310":1,"407":1}}],["fulfilling",{"2":{"320":1}}],["fulfilled",{"2":{"147":2}}],["fully",{"2":{"58":1,"72":2,"85":1,"149":1,"446":1,"448":1,"476":1}}],["full",{"0":{"274":1,"275":1},"2":{"17":1,"44":2,"53":1,"161":1,"162":1,"180":1,"194":1,"234":1,"241":1,"269":1,"270":1,"336":1,"451":8,"482":1}}],["futher",{"2":{"130":1}}],["future",{"2":{"64":1,"69":1,"106":2,"192":1,"308":2,"310":1,"347":1,"479":1}}],["fundamentally",{"2":{"103":1}}],["functional",{"2":{"83":1}}],["functionality",{"2":{"12":1,"58":2,"102":1,"105":1,"106":5,"166":1,"196":1,"217":1,"233":2,"245":3,"278":2,"294":1,"308":1,"309":1,"327":1,"461":1,"462":1}}],["function",{"0":{"283":1,"285":1},"2":{"51":1,"56":3,"64":2,"68":1,"69":3,"70":1,"72":3,"74":2,"80":1,"84":1,"88":1,"90":2,"91":3,"92":1,"100":1,"106":2,"108":1,"159":1,"161":1,"166":1,"167":1,"172":1,"187":3,"192":1,"195":1,"220":1,"221":3,"234":1,"242":1,"249":1,"255":2,"257":1,"266":2,"267":1,"270":2,"272":1,"277":1,"283":2,"291":1,"310":2,"311":3,"312":3,"313":4,"328":2,"349":1,"366":1,"406":3,"440":1,"444":1}}],["functions",{"0":{"192":1,"444":1},"1":{"445":1},"2":{"28":1,"32":1,"37":1,"56":2,"59":4,"61":1,"63":1,"65":1,"69":8,"72":2,"79":1,"83":1,"84":2,"88":1,"89":1,"91":1,"99":1,"106":2,"149":1,"150":1,"175":2,"176":1,"195":1,"196":2,"205":1,"221":2,"224":2,"233":1,"234":1,"249":1,"266":1,"269":1,"270":3,"272":3,"309":2,"310":3,"311":4,"312":5,"313":1,"349":1,"444":2,"447":1}}],["fourth",{"2":{"416":1}}],["fourthly",{"2":{"326":1}}],["four",{"2":{"397":1,"403":1,"412":1,"416":1,"431":1,"437":2,"449":1,"490":1}}],["foundaddress",{"2":{"486":1,"487":1}}],["foundcount",{"2":{"486":1,"487":1}}],["found",{"2":{"12":2,"18":1,"37":1,"45":4,"54":1,"119":1,"131":2,"143":2,"155":2,"191":1,"202":1,"204":1,"271":1,"309":1,"310":1,"311":1,"312":1,"318":1,"321":1,"325":1,"403":1,"425":1,"427":1,"431":1,"446":1,"451":1}}],["foobar",{"2":{"486":1,"487":1,"488":1}}],["foobarsearchresponse",{"2":{"486":2,"487":2}}],["foobarsearchrequest",{"2":{"486":2,"487":2}}],["foobarsearch",{"2":{"474":2,"486":2,"487":2,"488":1}}],["foo=",{"2":{"483":1}}],["foo>",{"2":{"234":2}}],["foo",{"2":{"221":4,"233":4,"234":1,"290":2,"372":1,"373":1,"374":1,"389":1,"390":1,"411":2,"415":2,"416":2,"421":6,"441":4,"443":4}}],["focuses",{"2":{"461":1,"462":1}}],["focused",{"2":{"338":1}}],["focus",{"2":{"194":1,"202":1}}],["folks",{"2":{"396":1}}],["folders",{"0":{"288":1},"2":{"112":2}}],["folder",{"2":{"112":4,"114":1,"115":1,"116":1,"117":1,"287":1,"288":2,"310":1,"342":1}}],["folded",{"2":{"62":1,"72":1,"328":4}}],["foldhqlexpression",{"0":{"75":1}}],["folding",{"0":{"75":1},"2":{"70":1,"72":1,"75":1}}],["follow",{"2":{"159":1,"177":1,"184":1,"187":1,"190":1,"210":1,"266":1,"299":1,"308":1,"309":1,"310":2,"311":2,"313":1,"340":1,"349":1,"383":1,"439":1}}],["followed",{"2":{"81":2,"95":1,"108":1,"185":1,"220":1,"249":1,"253":1,"269":2,"270":1,"310":1,"449":1,"496":1}}],["follows",{"2":{"51":1,"56":1,"185":1,"270":1,"312":1,"328":1,"448":1,"449":1}}],["followings",{"2":{"353":1}}],["following",{"2":{"5":1,"16":1,"27":1,"33":1,"45":1,"51":1,"54":1,"56":2,"64":1,"68":1,"81":1,"85":1,"96":1,"102":1,"107":1,"114":1,"116":1,"122":1,"131":1,"132":1,"133":1,"137":1,"138":1,"143":1,"163":1,"166":1,"172":2,"173":1,"175":1,"176":1,"183":1,"185":1,"187":5,"192":2,"196":1,"225":1,"233":1,"239":1,"240":1,"245":1,"247":1,"251":1,"252":2,"254":1,"264":1,"266":2,"269":4,"270":1,"280":1,"300":1,"303":1,"308":3,"309":1,"310":2,"311":2,"313":2,"338":2,"339":2,"349":1,"386":1,"495":3,"496":1}}],["fop",{"2":{"36":1,"38":2,"122":1}}],["forever",{"2":{"327":1}}],["foreach",{"2":{"41":2}}],["forthcoming",{"2":{"479":1}}],["forth",{"2":{"249":1,"270":1}}],["forked",{"2":{"238":1,"239":1,"317":1}}],["fork",{"2":{"120":1}}],["forcing",{"2":{"92":1}}],["forced",{"2":{"419":1}}],["forcegenerate>",{"2":{"274":1}}],["forcegenerate>1",{"2":{"274":1}}],["force",{"2":{"13":1,"44":1,"157":1,"441":1}}],["formal",{"2":{"277":1,"285":1,"288":1,"299":1,"331":1}}],["format>",{"2":{"486":1,"487":1,"488":1}}],["format>special",{"2":{"486":1,"487":1,"488":1}}],["formatted",{"2":{"223":1,"251":1,"449":1,"473":1,"486":1}}],["formatting",{"2":{"106":1,"216":1,"230":1}}],["formatoptions",{"2":{"191":1}}],["formats",{"2":{"59":1,"113":1,"124":1,"192":1,"249":1,"321":1,"403":2,"461":2,"462":1,"490":1}}],["format",{"0":{"45":1,"113":1},"2":{"36":1,"45":1,"51":1,"56":2,"89":1,"92":1,"93":1,"95":1,"105":1,"106":1,"108":1,"113":1,"167":1,"175":1,"191":7,"192":3,"195":4,"196":1,"249":1,"256":1,"261":1,"262":1,"269":3,"277":1,"278":1,"310":1,"321":1,"329":1,"337":2,"338":1,"339":1,"419":2,"423":4,"438":4,"449":1,"473":1,"474":2}}],["formed",{"2":{"185":1}}],["forms",{"2":{"88":1,"269":1}}],["form",{"0":{"356":1,"371":1},"2":{"64":1,"65":1,"66":1,"73":2,"107":1,"131":1,"183":1,"188":1,"191":1,"194":1,"196":1,"264":4,"277":3,"285":1,"310":1,"321":3,"357":1,"369":1,"446":1,"480":1,"490":1}}],["forward",{"2":{"58":1,"324":1}}],["for",{"0":{"5":1,"23":1,"43":1,"131":1,"199":1,"237":1,"241":1,"322":1,"363":1,"458":1,"459":1,"489":1,"492":1,"498":1},"1":{"238":1,"239":1,"240":1,"241":1,"242":1,"243":1,"460":1,"461":1,"490":1,"491":1,"492":1},"2":{"0":1,"12":3,"13":2,"16":4,"17":1,"18":4,"19":1,"20":1,"21":2,"22":1,"23":1,"24":2,"26":2,"28":2,"29":1,"30":1,"31":1,"36":9,"37":2,"44":2,"45":7,"47":1,"48":1,"49":1,"50":1,"51":1,"53":5,"56":6,"58":4,"59":4,"60":1,"61":3,"63":2,"66":2,"69":5,"70":2,"72":2,"73":2,"75":1,"77":2,"80":1,"81":2,"82":1,"83":3,"84":2,"86":2,"87":5,"88":4,"90":3,"91":1,"92":1,"94":1,"95":1,"96":4,"100":2,"101":1,"102":2,"103":4,"105":2,"106":3,"107":1,"108":7,"109":7,"110":2,"112":2,"113":1,"115":1,"116":1,"120":1,"122":2,"123":3,"124":4,"128":4,"130":1,"131":2,"132":3,"135":1,"140":1,"141":2,"143":5,"144":1,"145":1,"147":2,"148":4,"149":2,"152":1,"153":1,"154":1,"155":5,"158":1,"160":2,"163":6,"164":3,"167":2,"169":2,"170":6,"171":1,"172":8,"174":4,"175":2,"176":1,"179":1,"180":2,"181":1,"182":1,"183":6,"184":2,"185":2,"186":3,"187":11,"188":2,"189":1,"190":5,"191":1,"192":6,"193":3,"194":5,"195":2,"196":15,"197":1,"198":5,"199":1,"201":1,"203":2,"204":19,"206":1,"210":1,"211":1,"212":1,"214":2,"217":4,"218":6,"219":3,"221":6,"223":1,"230":1,"233":5,"234":1,"236":1,"238":5,"239":5,"240":2,"241":8,"242":4,"243":2,"245":4,"246":5,"247":1,"249":3,"251":1,"252":2,"254":2,"255":3,"256":2,"257":5,"258":1,"261":1,"262":1,"264":4,"265":2,"266":9,"267":5,"268":1,"269":13,"270":10,"272":6,"274":1,"275":1,"276":2,"277":2,"278":6,"279":6,"280":4,"282":1,"283":2,"284":2,"287":1,"288":6,"289":3,"290":3,"291":1,"295":1,"298":1,"299":2,"300":1,"301":2,"302":2,"303":1,"308":11,"309":4,"310":11,"311":7,"312":7,"313":5,"317":3,"318":7,"319":2,"320":3,"321":5,"322":2,"323":1,"324":4,"325":1,"326":4,"327":8,"328":2,"329":1,"330":7,"332":3,"333":1,"335":2,"337":9,"338":2,"339":3,"340":10,"346":1,"348":1,"349":2,"360":1,"379":1,"380":1,"388":1,"399":2,"400":6,"401":1,"402":4,"403":3,"405":1,"407":4,"410":2,"411":3,"412":3,"414":1,"415":3,"416":5,"419":4,"420":2,"421":3,"424":1,"425":3,"427":2,"428":2,"431":1,"432":5,"436":2,"438":3,"439":3,"440":2,"441":8,"442":1,"443":6,"444":1,"446":2,"447":2,"448":4,"449":3,"451":4,"452":2,"455":1,"461":1,"467":1,"468":2,"470":1,"471":3,"472":1,"473":1,"474":3,"477":3,"478":3,"479":1,"480":2,"482":2,"483":1,"484":1,"485":6,"488":1,"492":1,"495":3,"496":3,"498":1}}],["fpic",{"2":{"5":1}}],["bcdefghijk",{"2":{"381":1}}],["bcompare",{"2":{"53":1}}],["blacklister",{"2":{"327":4}}],["blacklisted",{"2":{"327":5}}],["blacklist",{"2":{"327":8}}],["blacklisting",{"0":{"327":1},"2":{"327":3}}],["blank",{"2":{"241":1,"308":1}}],["blue",{"2":{"303":2}}],["blog",{"2":{"284":1,"285":1,"308":4,"334":1}}],["blogs",{"2":{"277":1,"288":3,"332":2,"334":1}}],["blob",{"2":{"194":1,"196":2,"423":3,"425":2}}],["blockcompressed",{"2":{"191":1}}],["blocking",{"2":{"149":1}}],["blocks",{"0":{"160":1},"2":{"149":1,"158":1,"160":2,"308":1,"321":2,"326":1}}],["blocked",{"0":{"154":1},"2":{"93":2,"154":1,"185":1,"326":1,"330":2}}],["block",{"2":{"86":1,"93":1,"146":1,"149":1,"159":1,"160":5,"162":2,"204":1,"324":1,"327":1}}],["bloated",{"2":{"84":1}}],["band",{"2":{"330":1}}],["balancer",{"2":{"327":2}}],["balanced",{"2":{"196":2,"439":1}}],["balancing",{"2":{"318":1}}],["bar=",{"2":{"483":2}}],["bar",{"2":{"233":2,"441":4,"443":4}}],["baremetal",{"2":{"190":1,"194":1,"242":1}}],["bare",{"0":{"134":1},"2":{"134":1,"190":2,"196":13,"247":1,"282":1,"303":1,"324":2,"326":2,"330":1,"340":2}}],["barely",{"2":{"106":1}}],["batch",{"2":{"128":1}}],["bad",{"2":{"58":1,"169":1}}],["backendrequest>",{"2":{"486":2,"487":2,"488":2}}],["backendrequest",{"2":{"486":2,"487":2,"488":2}}],["backtraces",{"2":{"247":1}}],["backup",{"2":{"192":1}}],["background",{"2":{"106":1,"323":3,"339":1}}],["backwards",{"2":{"311":1}}],["backward",{"2":{"105":1,"245":2,"414":1}}],["back",{"2":{"56":1,"80":1,"82":1,"90":1,"103":2,"192":1,"196":1,"249":1,"266":1,"270":2,"272":1,"310":1,"318":1,"330":1,"357":2,"441":1}}],["basis",{"2":{"245":1}}],["basically",{"2":{"321":1}}],["basicconstraints=ca",{"2":{"240":1}}],["basic",{"2":{"111":1,"128":1,"146":1,"196":1,"217":1,"219":1,"266":1,"273":1,"278":1,"315":1,"328":1,"379":2,"380":2}}],["bash>",{"2":{"391":1}}],["bashmake",{"2":{"123":1,"124":1}}],["bashcmake",{"2":{"123":1,"129":1}}],["bashgit",{"2":{"17":1}}],["bash",{"2":{"15":1,"18":1,"39":1,"126":3,"128":4,"133":1}}],["bashsudo",{"2":{"6":1,"122":1}}],["base64",{"2":{"239":1,"240":6,"260":1,"261":2,"274":3}}],["based",{"2":{"15":1,"22":1,"32":1,"37":1,"45":1,"90":2,"106":1,"167":1,"183":1,"189":1,"196":3,"202":1,"233":1,"242":1,"243":1,"246":1,"280":2,"308":1,"339":1,"340":1,"399":1,"400":3,"403":1,"421":1,"427":3,"439":2,"448":2,"471":3}}],["base",{"0":{"185":1},"2":{"3":2,"4":1,"16":1,"88":1,"149":1,"184":2,"186":2,"188":1,"189":2,"217":1,"219":1,"233":5,"234":1,"242":1,"246":3,"269":2,"272":4,"311":4,"353":1,"438":1}}],["boat",{"2":{"324":1}}],["bottlenecks",{"2":{"461":1}}],["bottom",{"2":{"321":1}}],["both",{"2":{"27":2,"50":1,"70":1,"76":1,"91":1,"103":1,"129":1,"135":1,"140":1,"186":1,"187":1,"189":1,"190":1,"202":1,"204":1,"214":1,"217":1,"219":1,"242":1,"267":1,"277":1,"282":1,"284":1,"308":1,"400":1,"412":3,"416":2,"419":1,"425":1,"441":1,"443":1,"474":1,"475":1}}],["boiler",{"2":{"312":1}}],["bob",{"2":{"302":1}}],["bobsmith66",{"2":{"302":1}}],["boca",{"2":{"240":1}}],["bocks",{"2":{"103":1}}],["borrowing",{"2":{"162":1}}],["box",{"2":{"108":1,"304":1,"335":4,"340":1}}],["boxes",{"2":{"108":1}}],["boundaries",{"2":{"278":1}}],["boundary",{"2":{"152":1,"281":1,"309":1}}],["bound",{"2":{"91":3,"478":1}}],["body",{"0":{"363":1,"389":1,"390":1},"2":{"70":1,"351":2,"357":3,"360":1,"376":4,"377":3,"403":1,"486":4,"487":4,"488":4}}],["bool",{"2":{"220":2,"269":1,"275":1,"408":4,"409":1,"413":1,"418":4,"422":1,"426":1,"428":1,"430":1,"442":1,"486":1,"487":1}}],["boolean",{"2":{"69":2,"191":6,"311":1,"321":2,"436":1,"439":1,"451":1,"464":1,"479":1}}],["book",{"2":{"38":2,"131":1,"282":1,"285":1}}],["bootstrap",{"2":{"13":1}}],["boost",{"2":{"7":1,"8":1,"10":1,"11":1,"12":1,"13":1,"18":1,"23":3,"83":1,"217":2,"234":2}}],["bufsiz",{"2":{"355":1}}],["buf",{"2":{"355":4}}],["buffering",{"2":{"319":1}}],["buffer",{"2":{"179":1,"195":1,"270":1,"326":2,"418":3,"419":1,"422":1,"423":1,"424":1,"426":1,"428":1,"439":1,"442":1}}],["buffered",{"2":{"155":1,"157":1}}],["business",{"2":{"337":1}}],["busy",{"2":{"169":1,"170":1,"320":1}}],["bubble",{"2":{"337":1}}],["bullet",{"2":{"337":7,"339":2}}],["bulk",{"2":{"69":1,"250":1,"310":2}}],["buddy",{"2":{"169":1,"320":5}}],["buckets",{"2":{"148":1}}],["bucket",{"2":{"145":1,"148":3,"152":1,"170":2,"181":10,"182":2}}],["bug",{"2":{"106":1,"108":1,"194":1,"245":3}}],["bugs",{"2":{"102":1,"319":1,"461":2}}],["bundles",{"2":{"346":1,"347":1,"348":1}}],["bundle",{"0":{"343":1,"487":1},"1":{"344":1,"345":1,"346":1,"347":1,"348":1},"2":{"33":1,"345":3,"346":8,"347":10,"348":3,"385":1,"471":1,"473":2,"474":5,"476":6,"478":1,"485":2,"486":1,"487":1}}],["button",{"2":{"103":2,"120":1,"239":1,"299":1,"304":1}}],["but",{"2":{"23":1,"45":1,"50":1,"56":2,"58":5,"59":1,"64":2,"65":1,"66":1,"67":1,"69":1,"70":1,"73":1,"75":1,"76":1,"83":2,"93":1,"100":1,"102":1,"103":3,"106":9,"108":2,"110":1,"114":1,"128":1,"130":1,"131":1,"140":1,"143":2,"146":1,"150":1,"152":1,"153":2,"154":1,"155":2,"159":1,"160":1,"162":1,"163":2,"168":1,"182":1,"184":1,"186":3,"192":2,"195":1,"196":4,"201":1,"204":1,"216":1,"217":4,"219":1,"220":1,"221":1,"238":1,"242":2,"245":3,"249":1,"261":1,"262":1,"266":2,"269":2,"270":1,"308":7,"309":2,"310":3,"311":2,"312":4,"313":1,"317":2,"318":1,"319":2,"321":3,"324":2,"326":4,"327":2,"329":2,"330":4,"402":1,"403":1,"405":1,"406":1,"412":3,"419":1,"420":1,"421":3,"425":1,"427":1,"428":1,"431":3,"439":1,"441":3,"443":1,"450":1,"451":2,"452":1,"480":1}}],["built",{"2":{"22":1,"31":1,"42":1,"72":1,"123":2,"238":1,"243":1,"246":1,"277":1,"353":1}}],["buildversion=",{"2":{"274":1}}],["builddir",{"2":{"128":2}}],["builddataset",{"2":{"92":1}}],["builddatasetassign",{"2":{"92":1}}],["builditerate",{"2":{"92":1}}],["building",{"0":{"5":1,"16":1,"20":1,"90":1,"121":1,"123":1},"1":{"19":1,"91":1,"92":1,"93":1,"94":1,"95":1,"122":1,"123":1},"2":{"6":1,"16":1,"33":1,"37":1,"87":1,"90":1,"119":1,"122":1,"123":2,"198":2,"242":1}}],["builder",{"2":{"92":1}}],["buildexprassign",{"2":{"91":3}}],["buildexpr",{"2":{"91":3}}],["buildassign",{"2":{"91":1}}],["buildtempexpr",{"2":{"91":1}}],["buildctx",{"2":{"87":2,"312":2}}],["buildr",{"2":{"53":1}}],["builds",{"0":{"241":1},"2":{"18":1,"86":1,"87":1,"238":1,"239":1,"241":2,"242":5,"243":2,"312":1,"495":1}}],["build",{"0":{"0":1,"23":1,"54":1,"237":1,"238":1,"242":1,"497":1},"1":{"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"238":1,"239":1,"240":1,"241":1,"242":1,"243":1},"2":{"3":1,"4":1,"5":2,"7":1,"12":2,"13":3,"18":15,"20":3,"21":3,"23":4,"36":5,"37":3,"43":1,"53":2,"54":1,"55":1,"61":1,"92":1,"100":1,"122":1,"123":7,"126":2,"128":1,"129":6,"166":1,"198":1,"238":3,"239":1,"242":8,"243":1,"246":1,"393":1,"446":1,"447":1,"471":1}}],["br",{"2":{"390":1}}],["braces",{"2":{"310":1}}],["brainstorm",{"2":{"337":2}}],["brainstorming",{"2":{"308":1,"336":1}}],["brainer",{"2":{"217":1}}],["branches",{"2":{"110":1,"128":1,"245":1,"496":2}}],["branch",{"0":{"110":1,"499":1},"2":{"17":1,"22":1,"108":1,"120":1,"128":5,"242":1,"246":1,"497":1,"498":1,"499":1}}],["bring",{"2":{"145":1}}],["briefly",{"2":{"278":1}}],["brief",{"2":{"44":1,"283":1,"292":1,"299":1,"337":1}}],["brotli",{"0":{"388":1},"2":{"388":3,"460":2,"467":1}}],["broad",{"2":{"270":1,"339":1}}],["broadly",{"2":{"51":1,"83":1,"196":1}}],["browseresourcegroups",{"2":{"335":1}}],["browse",{"2":{"243":1,"308":3,"310":1}}],["broken",{"2":{"393":1}}],["broke",{"2":{"85":1}}],["brought",{"2":{"58":1,"326":1}}],["breaks",{"2":{"310":1}}],["break",{"2":{"254":1,"255":1,"266":3,"275":2}}],["breakdown",{"2":{"186":1}}],["breakpoint",{"2":{"56":1}}],["breaking",{"2":{"26":1,"169":1,"441":1}}],["brew",{"2":{"23":7}}],["b",{"2":{"17":1,"70":4,"75":2,"108":1,"133":1,"134":1,"170":1,"194":1,"196":3,"246":2,"269":1,"274":1,"326":1,"337":1,"347":4,"496":6}}],["besides",{"2":{"403":1}}],["best",{"2":{"70":1,"155":2,"186":1,"193":1,"216":1,"283":1,"285":1,"298":1,"310":1,"313":1,"330":1}}],["believes",{"2":{"427":1}}],["believe",{"2":{"223":1}}],["belong",{"2":{"218":1,"416":1,"479":1}}],["belongs",{"2":{"149":1,"161":1,"310":1,"412":1,"416":4,"421":1}}],["below",{"2":{"18":1,"23":1,"27":1,"49":1,"53":1,"59":2,"63":1,"64":1,"67":1,"70":1,"108":1,"124":1,"136":1,"143":1,"150":1,"183":1,"188":1,"190":1,"203":1,"204":1,"209":1,"212":1,"217":2,"233":1,"256":1,"257":1,"258":1,"287":1,"309":1,"337":1,"393":1,"448":1,"452":1,"460":2,"471":1,"474":1,"477":1,"486":1}}],["beat",{"0":{"320":1},"2":{"169":1}}],["bearer",{"2":{"379":2,"380":2}}],["bear",{"2":{"70":2,"105":1,"192":1}}],["beginning",{"2":{"217":1}}],["begin",{"2":{"163":1,"185":1,"266":2,"339":1}}],["begins",{"2":{"141":1,"249":1,"310":1,"311":1}}],["behave",{"2":{"327":1}}],["behavior",{"2":{"245":6,"462":1,"469":1,"475":1}}],["behaviour",{"2":{"53":1,"72":1,"99":1,"102":1,"106":1,"145":1,"152":1,"217":1,"260":1,"325":1,"327":3}}],["behalf",{"2":{"140":1}}],["behind",{"2":{"72":1,"128":1,"163":1,"249":1}}],["benefits",{"2":{"292":1,"339":1}}],["benefit",{"2":{"69":1,"477":1}}],["became",{"2":{"310":1}}],["because",{"2":{"56":1,"64":2,"69":2,"70":1,"84":1,"90":1,"114":1,"154":1,"160":1,"184":1,"191":1,"234":1,"254":1,"308":1,"310":2,"311":1,"313":1,"317":1,"319":1,"320":2,"326":2,"330":2,"412":1,"416":1,"441":2,"443":2,"477":1}}],["becomes",{"2":{"323":1,"483":1,"484":1}}],["become",{"2":{"58":1,"84":1,"103":1}}],["better",{"2":{"56":1,"72":1,"84":1,"102":1,"103":1,"106":1,"155":1,"158":1,"170":1,"196":2,"236":1,"249":1,"289":1,"310":1,"327":1}}],["between",{"0":{"44":1},"2":{"44":1,"51":1,"53":1,"56":2,"62":1,"65":1,"81":1,"128":2,"130":1,"141":1,"143":1,"158":1,"164":2,"170":2,"172":2,"196":2,"249":1,"254":2,"265":1,"267":1,"269":5,"270":1,"281":1,"311":3,"327":3,"337":2,"399":2,"400":1,"405":1,"419":1,"425":1,"439":1,"478":1,"496":1}}],["being",{"2":{"56":1,"58":1,"68":1,"69":1,"72":1,"85":2,"106":1,"112":1,"141":1,"145":1,"154":1,"155":1,"164":1,"183":1,"194":1,"196":1,"209":1,"216":1,"217":1,"223":1,"242":1,"267":2,"269":2,"272":1,"308":2,"312":1,"318":1,"320":1,"321":1,"324":1,"326":1,"327":1,"330":1,"403":1,"432":1,"441":1,"452":1}}],["beyond",{"2":{"53":1,"169":1,"209":1,"317":1}}],["beforehand",{"2":{"394":1}}],["beforedispose",{"2":{"234":1}}],["before",{"2":{"50":1,"53":2,"70":1,"106":2,"109":1,"123":1,"128":1,"149":2,"155":1,"204":1,"214":2,"234":1,"238":1,"242":1,"247":1,"249":1,"254":2,"267":1,"270":1,"309":2,"310":1,"311":2,"313":1,"318":2,"320":1,"326":4,"327":1,"329":1,"330":2,"394":1,"441":1,"460":1,"494":1,"497":1}}],["been",{"2":{"13":1,"49":1,"58":1,"59":1,"75":1,"82":1,"90":2,"91":1,"102":1,"103":6,"105":1,"108":1,"143":2,"147":1,"149":2,"155":2,"160":1,"183":1,"190":2,"242":1,"245":1,"254":1,"264":1,"266":2,"284":1,"308":1,"309":1,"310":4,"311":1,"312":3,"313":1,"317":1,"319":1,"321":1,"324":1,"327":1,"330":2,"455":1,"498":1}}],["be",{"0":{"79":2},"2":{"6":1,"12":2,"18":1,"20":1,"21":1,"22":1,"23":3,"26":1,"37":1,"43":1,"44":2,"45":2,"48":4,"49":2,"50":2,"51":1,"53":5,"54":1,"55":1,"56":9,"58":9,"59":3,"61":3,"62":1,"63":2,"64":5,"65":7,"66":4,"67":4,"68":3,"69":5,"70":10,"72":2,"73":3,"74":1,"76":1,"77":3,"79":1,"81":1,"82":3,"83":3,"84":4,"87":2,"90":2,"91":6,"92":4,"99":1,"100":2,"103":9,"104":3,"105":2,"106":10,"108":9,"109":3,"110":4,"112":5,"114":3,"115":1,"116":1,"120":1,"123":1,"128":3,"131":3,"132":2,"133":1,"140":1,"141":1,"142":1,"143":2,"145":3,"149":6,"150":1,"153":1,"154":1,"155":2,"157":2,"158":4,"159":2,"160":4,"161":4,"162":3,"163":6,"164":2,"166":2,"169":5,"170":4,"172":2,"175":1,"179":1,"183":1,"185":1,"187":2,"190":4,"191":5,"192":7,"193":1,"194":2,"195":1,"196":20,"202":1,"204":5,"205":1,"207":1,"208":1,"209":1,"217":2,"218":4,"220":4,"221":4,"222":1,"223":1,"224":2,"233":2,"234":2,"238":1,"239":1,"240":2,"241":2,"242":4,"243":1,"245":6,"246":1,"249":2,"250":1,"251":1,"252":2,"254":5,"256":1,"258":2,"264":3,"265":2,"266":8,"267":5,"268":1,"269":3,"270":3,"271":4,"272":4,"273":1,"274":2,"277":1,"278":1,"279":2,"283":1,"284":1,"285":2,"286":1,"288":1,"289":1,"290":1,"299":5,"301":2,"308":29,"309":7,"310":14,"311":3,"312":9,"313":8,"317":1,"318":7,"319":3,"320":1,"321":9,"322":1,"323":3,"324":1,"325":1,"326":13,"327":8,"328":1,"329":4,"330":10,"331":1,"335":1,"338":1,"339":1,"340":1,"345":1,"346":2,"347":2,"349":1,"381":2,"385":1,"387":1,"388":1,"397":3,"399":3,"400":1,"401":2,"402":5,"403":1,"405":3,"406":1,"407":7,"408":2,"410":5,"412":2,"414":4,"416":2,"419":7,"420":4,"421":6,"423":4,"424":2,"425":2,"427":3,"428":2,"431":7,"432":6,"433":1,"436":1,"437":2,"438":3,"439":5,"440":3,"441":14,"442":1,"443":5,"446":2,"447":2,"448":2,"450":1,"451":6,"452":2,"460":1,"473":2,"474":2,"476":5,"477":1,"480":2,"482":5,"488":1,"492":3,"494":1,"495":2,"496":3}}],["bypass",{"2":{"329":1}}],["bytes=0",{"2":{"381":1}}],["bytes=100",{"2":{"381":1}}],["bytes=1",{"2":{"381":2}}],["bytes",{"2":{"189":1,"270":1,"378":2}}],["byte",{"2":{"95":1,"152":1,"275":1,"326":1}}],["by",{"0":{"466":1},"2":{"5":1,"17":1,"20":1,"33":1,"37":1,"47":1,"53":2,"54":2,"59":1,"60":1,"61":1,"62":2,"63":4,"64":2,"65":1,"66":1,"67":1,"70":2,"73":1,"74":1,"77":1,"79":1,"80":1,"81":2,"83":1,"84":1,"85":2,"87":1,"89":1,"90":1,"92":1,"93":1,"95":1,"96":2,"105":2,"106":1,"108":2,"109":3,"113":2,"115":2,"123":2,"129":1,"133":1,"135":1,"145":4,"148":1,"152":1,"153":2,"155":2,"156":1,"157":1,"159":1,"161":1,"162":1,"163":2,"166":1,"167":2,"169":1,"170":2,"172":4,"176":2,"181":1,"182":1,"185":2,"186":2,"187":5,"192":3,"194":1,"195":1,"196":2,"197":1,"199":1,"203":1,"205":1,"211":1,"212":1,"220":1,"221":1,"222":1,"233":2,"234":3,"239":2,"241":1,"242":1,"245":1,"249":5,"250":1,"251":1,"253":1,"254":1,"255":1,"256":1,"258":1,"260":1,"265":1,"266":4,"267":2,"269":5,"270":6,"272":7,"273":1,"274":1,"276":2,"278":1,"280":1,"282":1,"296":1,"308":1,"310":5,"311":4,"312":1,"313":8,"317":1,"318":2,"319":1,"320":2,"324":1,"326":5,"327":6,"329":1,"330":5,"337":2,"339":2,"345":1,"347":1,"348":2,"365":1,"394":1,"397":3,"399":2,"400":1,"402":4,"406":1,"407":2,"410":2,"411":1,"412":1,"414":3,"415":1,"416":1,"419":3,"420":3,"421":6,"423":3,"425":2,"427":1,"428":1,"431":3,"433":1,"435":2,"437":1,"439":3,"440":2,"441":6,"442":1,"443":2,"444":1,"446":1,"448":5,"449":2,"452":1,"455":1,"461":1,"473":1,"474":3,"476":2,"477":1,"478":1,"479":2,"480":2,"483":1,"485":1,"490":2,"492":1,"494":1}}],["biases",{"2":{"339":1}}],["biased",{"2":{"95":1}}],["billing",{"2":{"326":1}}],["bits",{"2":{"240":1}}],["bit",{"2":{"149":1,"319":2,"325":1}}],["bitmap",{"0":{"147":1},"2":{"147":1}}],["bigger",{"2":{"329":1}}],["big",{"2":{"24":2,"30":1,"84":1,"95":1,"159":1,"195":1,"321":1,"446":1}}],["binding>",{"2":{"487":1,"488":1}}],["bindings",{"2":{"471":2}}],["binding",{"0":{"488":1},"2":{"471":9,"473":2,"474":4,"476":5,"477":6,"478":4,"479":3,"480":8,"483":1,"484":1,"485":1,"486":1,"487":2,"488":3}}],["bind",{"0":{"352":1},"2":{"176":1,"352":2,"471":3,"478":1,"480":2}}],["binary",{"2":{"45":1,"191":2,"195":1,"466":2}}],["binaries",{"2":{"20":1,"36":1,"128":1}}],["bin",{"2":{"20":1,"23":3,"53":1,"126":3,"128":2}}],["binutils",{"2":{"3":1,"4":1,"7":1,"8":1,"10":1,"11":1,"12":1,"18":1}}],["bison",{"2":{"3":1,"4":1,"7":1,"8":1,"10":1,"11":1,"12":1,"13":1,"23":4,"85":1}}],["cygwin",{"2":{"394":1}}],["cycles",{"2":{"143":1,"182":2}}],["cycled",{"2":{"143":1}}],["cycle",{"2":{"103":1,"108":1}}],["cgroup",{"2":{"326":2}}],["ccontext",{"0":{"435":1},"2":{"445":1}}],["cc",{"0":{"391":1},"2":{"353":1,"391":1}}],["ccdcache",{"2":{"326":1}}],["ccdquery",{"2":{"313":1}}],["cc=",{"2":{"23":1}}],["cves",{"2":{"303":1}}],["cfixedoutputmetadata",{"2":{"270":2,"275":2}}],["cfoo",{"2":{"233":4}}],["cfoocool",{"2":{"233":1}}],["cfooalot",{"2":{"233":1}}],["cfoowarm",{"2":{"233":2}}],["c=",{"2":{"262":1,"274":1}}],["c=us",{"2":{"240":1}}],["cn",{"2":{"240":1}}],["cn=example",{"2":{"240":1}}],["city",{"2":{"427":1}}],["cite",{"2":{"339":1}}],["cient",{"2":{"333":1}}],["circumstances",{"2":{"431":1}}],["circular",{"2":{"326":2}}],["circuit",{"2":{"70":1}}],["circuited",{"2":{"66":1,"70":1}}],["cinterface",{"2":{"234":2}}],["cinterfaceof",{"2":{"234":1}}],["cbar",{"2":{"233":1}}],["cserialtokenrule",{"0":{"439":1},"2":{"445":1}}],["css",{"2":{"353":2}}],["csr",{"2":{"240":5}}],["cs",{"2":{"160":1}}],["csvterminator",{"2":{"191":1}}],["csv",{"2":{"94":1,"191":1,"195":1,"461":1}}],["cthorquantilearg",{"2":{"311":1}}],["cthorsortarg",{"2":{"272":1}}],["cthorworkunitwritearg",{"2":{"257":1,"275":1}}],["cthorindexreadarg",{"2":{"257":1,"269":1,"275":1}}],["cthorxyz",{"2":{"88":1}}],["ctx",{"2":{"91":6,"92":3,"266":4,"269":5,"275":11}}],["cells",{"2":{"277":1}}],["cert",{"2":{"239":2,"240":12,"385":2}}],["certificates",{"2":{"451":1}}],["certificate",{"0":{"240":1},"2":{"239":2,"240":4,"385":1}}],["certainly",{"2":{"427":1}}],["certain",{"2":{"53":1,"112":1,"254":1,"307":1,"410":2,"431":1}}],["center",{"2":{"335":1}}],["centiles",{"2":{"308":1,"309":1}}],["centric",{"2":{"170":1,"403":1}}],["centos",{"0":{"6":1,"7":1,"8":1},"1":{"7":1,"8":1},"2":{"6":1,"16":1,"124":1}}],["cues",{"2":{"424":1,"427":2}}],["cut",{"2":{"51":1}}],["customized",{"2":{"436":1}}],["customize",{"2":{"311":1}}],["customizations",{"2":{"279":1}}],["customers",{"2":{"196":1}}],["customer",{"2":{"196":1,"338":1,"410":1}}],["customname",{"2":{"187":1}}],["custom",{"0":{"44":1,"180":1},"2":{"38":2,"44":1,"113":1,"174":3,"180":4,"187":3,"402":1,"407":1,"408":2,"410":1,"411":2,"414":1,"415":2,"432":2,"435":1,"441":1,"450":1,"455":1,"492":1}}],["curated",{"2":{"336":1}}],["cursors",{"0":{"93":1}}],["cursor",{"2":{"65":3,"87":1,"93":1}}],["currently",{"2":{"61":1,"64":1,"69":2,"72":1,"100":1,"124":1,"133":1,"146":1,"162":1,"163":1,"170":1,"196":2,"204":1,"238":1,"246":1,"247":1,"250":1,"256":1,"261":1,"266":1,"310":2,"322":1,"326":1,"328":1,"347":1,"385":1,"421":6}}],["current",{"0":{"244":1},"1":{"245":1,"246":1,"247":1},"2":{"18":1,"26":2,"31":1,"44":1,"95":1,"99":1,"102":1,"141":1,"143":1,"144":2,"149":1,"167":1,"169":1,"174":1,"179":2,"183":1,"189":1,"194":1,"196":1,"233":1,"244":1,"245":1,"264":1,"308":1,"312":1,"313":1,"321":1,"322":1,"327":1,"328":1,"414":1,"456":1,"499":1}}],["curly",{"2":{"310":1}}],["curl",{"2":{"15":1}}],["cxx=",{"2":{"23":1}}],["cpartialmaskstyle",{"0":{"437":1},"2":{"445":1}}],["cprofile",{"2":{"435":1,"441":1}}],["cpphttplib",{"2":{"365":1,"378":1,"381":2,"382":1,"383":1,"385":2,"387":1,"388":1}}],["cppint",{"2":{"352":1}}],["cppinterface",{"2":{"233":2}}],["cppsvr",{"2":{"354":1,"355":1,"356":1,"357":1,"358":1,"359":1,"361":1}}],["cppswitch",{"2":{"266":2}}],["cppstruct",{"2":{"255":1,"257":1,"269":1,"270":1}}],["cppstyle",{"2":{"217":1}}],["cppstd",{"2":{"187":1,"376":1,"377":1}}],["cppextern",{"2":{"255":1}}],["cppowned",{"2":{"234":1}}],["cppcli",{"2":{"380":1,"384":1}}],["cppclass",{"2":{"220":1,"366":1}}],["cppconst",{"2":{"270":1,"358":1}}],["cppcfoocool",{"2":{"233":1}}],["cppcfoo",{"2":{"233":2}}],["cppguide",{"2":{"225":1}}],["cppmetricmetadata",{"2":{"188":1}}],["cpppcounter",{"2":{"187":1}}],["cppusing",{"2":{"187":1}}],["cppunit",{"2":{"13":1,"18":1}}],["cpp",{"0":{"350":1},"1":{"351":1,"352":1,"353":1,"354":1,"355":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"394":1,"395":1,"396":1},"2":{"56":2,"87":1,"218":2,"266":1,"274":3,"275":1,"310":3,"312":1,"353":3,"360":2,"379":1,"385":1,"394":2,"471":1}}],["cpus",{"2":{"128":1}}],["cpu",{"2":{"20":1}}],["crule",{"0":{"438":1},"2":{"439":1}}],["crucial",{"2":{"277":1,"339":1}}],["croxieserverquantileactivity",{"2":{"313":1}}],["croxieserverindexreadactivity",{"2":{"270":1}}],["cross",{"2":{"123":1,"350":1}}],["crself",{"2":{"269":3,"275":3}}],["crt",{"2":{"240":5,"385":1}}],["critsecs",{"2":{"328":1}}],["criteria",{"2":{"181":1}}],["critical",{"2":{"26":7,"58":1,"105":1,"149":1,"157":1,"244":1,"245":1}}],["credential",{"2":{"131":2,"492":1}}],["credentials",{"0":{"131":1,"140":1},"2":{"130":1,"131":1,"132":2,"139":1,"140":5,"211":1,"446":2,"448":1,"449":3}}],["creator=",{"2":{"262":1,"274":1}}],["creation",{"2":{"61":1,"169":1,"170":1,"186":1,"313":1,"317":2,"441":1,"480":1}}],["creating",{"0":{"21":1,"498":1},"2":{"63":1,"70":2,"84":1,"91":1,"108":2,"114":1,"131":1,"169":1,"172":1,"174":1,"187":5,"193":1,"196":1,"257":1,"279":1,"313":1,"318":1,"336":1,"339":1,"468":1,"471":1}}],["createctx",{"2":{"312":2}}],["createcustommetricandaddtomanager",{"2":{"187":1}}],["createwildkeysegmentmonitor",{"2":{"269":1,"275":1}}],["createkeysegmentmonitor",{"2":{"269":1,"275":1}}],["createmetricandaddtomanager",{"2":{"187":1}}],["createglobalrowmanager",{"2":{"164":1}}],["createfixedrowheap",{"2":{"154":1}}],["createprocess",{"2":{"80":1,"255":1,"266":1,"275":1}}],["creatertlstringset",{"2":{"269":1,"275":1}}],["createrepository",{"2":{"72":1}}],["createrowstream",{"2":{"192":1}}],["createrow",{"2":{"59":1}}],["createsegmentmonitors",{"2":{"269":2,"275":1}}],["createselectexpr",{"2":{"64":1}}],["creates",{"2":{"68":1,"79":1,"90":1,"172":1,"176":1,"180":1,"186":1,"187":1,"188":1,"251":1,"254":2,"268":1,"270":2,"313":1,"318":1,"443":1,"455":1,"472":1,"477":1,"483":1,"484":1}}],["createattribute",{"2":{"63":1}}],["createextraattribute",{"2":{"63":1}}],["createx",{"2":{"59":1}}],["createvalue",{"2":{"59":1}}],["createdefault",{"2":{"311":1}}],["createdictionary",{"2":{"59":1}}],["createdataset",{"2":{"59":1}}],["created",{"2":{"20":1,"21":1,"23":2,"49":2,"53":1,"56":4,"58":4,"59":4,"65":1,"80":1,"81":1,"86":2,"87":1,"103":1,"106":1,"108":1,"123":1,"143":2,"164":1,"169":1,"176":1,"178":1,"179":1,"181":1,"187":4,"188":1,"217":1,"234":1,"242":1,"246":1,"249":1,"262":1,"264":3,"266":1,"267":2,"269":2,"270":3,"272":1,"308":2,"309":2,"311":1,"312":1,"317":1,"324":1,"329":3,"335":1,"347":1,"348":1,"406":1,"440":1,"443":1,"455":1,"456":1,"476":2,"477":2,"480":1,"499":1}}],["create",{"0":{"317":1,"499":1},"2":{"18":2,"20":1,"21":1,"23":2,"58":1,"70":1,"87":1,"88":1,"91":1,"106":2,"123":3,"124":2,"131":1,"170":1,"172":1,"187":2,"194":3,"195":1,"196":1,"220":1,"221":1,"238":1,"239":1,"240":2,"249":1,"250":1,"251":2,"255":1,"264":1,"266":1,"268":1,"269":1,"270":6,"271":1,"273":1,"276":1,"277":1,"280":1,"289":1,"291":1,"309":1,"311":3,"313":3,"318":1,"335":1,"337":2,"338":2,"399":1,"401":1,"447":1,"474":1,"475":1,"476":3,"496":1,"497":1}}],["craft",{"2":{"339":1}}],["crashed",{"2":{"320":1}}],["crashes",{"2":{"106":1,"245":1,"319":1}}],["cran",{"2":{"3":3,"16":3}}],["crcs",{"2":{"59":1}}],["clues",{"2":{"403":1}}],["clustername=roxie",{"2":{"340":1}}],["clustering",{"2":{"221":1}}],["cluster",{"2":{"27":1,"28":2,"29":1,"140":1,"166":3,"170":1,"172":1,"196":3,"212":1,"264":3,"265":5,"326":1,"340":4,"451":1,"460":2,"471":1}}],["clusters",{"2":{"27":3,"170":1}}],["clever",{"2":{"278":1}}],["cleared",{"2":{"211":1}}],["clearer",{"2":{"106":1,"219":1}}],["clearing",{"2":{"163":1}}],["clearly",{"2":{"103":2,"309":1,"339":1}}],["clear",{"2":{"51":1,"103":2,"106":1,"169":1,"170":2,"223":1,"249":1,"277":1,"280":1,"339":1}}],["cleanup",{"0":{"455":1},"1":{"456":1,"457":1,"458":1},"2":{"455":1,"456":2,"458":2}}],["cleaned",{"2":{"216":1}}],["cleanly",{"2":{"194":1,"234":1}}],["clean",{"2":{"36":1,"38":4,"89":1,"106":2,"196":1,"313":1}}],["cleandeb",{"2":{"36":1}}],["climate",{"2":{"339":1}}],["click",{"2":{"303":3,"304":1,"335":17,"342":1}}],["clicking",{"2":{"239":2}}],["cli",{"0":{"295":1},"2":{"109":1,"131":3,"288":1,"332":1,"333":1,"367":7,"368":2,"369":2,"370":2,"371":1,"372":1,"373":1,"374":2,"375":2,"376":2,"377":1,"378":1,"379":3,"380":3,"381":2,"382":6,"383":4,"385":3,"389":1,"390":1}}],["clibraryworkunitreadarg",{"2":{"88":1}}],["clibrarysplitarg",{"2":{"88":1}}],["clienttools",{"2":{"23":1,"242":1}}],["client",{"0":{"23":1,"341":1,"367":1,"389":1,"390":1},"1":{"342":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1},"2":{"18":1,"27":1,"170":1,"209":1,"333":1,"342":1,"364":1,"367":6,"378":2,"381":1,"382":1,"383":1,"446":1,"449":3,"451":1,"479":1,"486":1,"487":1}}],["cloud",{"2":{"196":7,"246":2,"247":1,"288":1,"302":1,"320":1,"322":1,"324":1,"326":1,"330":3,"332":1,"340":4}}],["closed",{"2":{"323":1}}],["closedown0",{"2":{"274":1}}],["closely",{"2":{"104":1,"446":1}}],["close",{"2":{"103":1,"112":1,"222":1,"233":1,"234":1,"327":1,"382":2}}],["closeexpr",{"2":{"58":1}}],["cloneable=",{"2":{"274":1}}],["clones",{"2":{"251":1}}],["cloned",{"2":{"145":1,"250":1}}],["clone",{"2":{"13":1,"17":1,"70":1,"120":1,"495":8}}],["claims",{"0":{"452":1},"1":{"453":1,"454":1},"2":{"448":2,"449":1}}],["claim",{"2":{"441":1,"443":1,"452":5,"453":1,"454":1}}],["clarify",{"2":{"309":1}}],["clarification",{"2":{"103":1,"106":1}}],["clarity",{"2":{"280":1,"337":2}}],["clause",{"2":{"267":6}}],["classify",{"2":{"338":2}}],["classic",{"2":{"239":1}}],["classname",{"2":{"338":1}}],["classctx",{"2":{"312":3}}],["classes",{"0":{"94":1,"224":1,"348":1},"2":{"58":3,"79":1,"81":1,"84":1,"87":2,"90":1,"94":1,"96":3,"106":1,"194":2,"195":2,"223":1,"224":2,"233":2,"234":1,"249":1,"250":2,"269":1,"270":1,"271":2,"295":1,"308":1,"311":2,"312":2,"313":1,"315":1,"348":1,"397":2,"437":1,"444":1}}],["class",{"2":{"58":3,"69":1,"80":2,"81":3,"84":1,"87":1,"88":7,"89":1,"91":1,"149":1,"170":1,"176":1,"180":3,"182":1,"188":1,"192":5,"194":1,"195":1,"219":3,"220":2,"226":1,"233":8,"234":5,"255":2,"257":3,"266":1,"269":4,"270":5,"272":3,"311":2,"312":3,"313":3,"318":1,"348":1,"433":1,"437":1,"438":1,"439":2,"440":1,"441":1,"444":2}}],["clang++",{"2":{"23":1}}],["clang",{"2":{"23":2}}],["cl",{"2":{"54":1}}],["camel",{"2":{"349":1}}],["camelcase",{"2":{"220":2}}],["cakey",{"2":{"240":1}}],["ca",{"2":{"240":2,"385":2}}],["capacity",{"2":{"324":1,"399":1}}],["capable",{"2":{"183":1,"204":1,"419":1,"420":1,"424":1,"428":1}}],["capability",{"2":{"166":1}}],["capabilities",{"2":{"29":1,"169":1,"308":2,"336":1,"425":1,"432":1,"446":1,"474":1}}],["capitalization",{"2":{"300":1}}],["capital",{"2":{"220":3}}],["captured",{"2":{"328":1}}],["capture",{"2":{"170":1,"326":1,"328":1}}],["category",{"2":{"310":1,"410":1,"479":1,"485":1}}],["categories",{"2":{"186":1,"338":2,"477":1,"479":1}}],["categorized",{"2":{"169":1}}],["cat",{"2":{"240":3}}],["catches",{"2":{"108":1}}],["catch",{"2":{"102":2,"103":1}}],["caution",{"2":{"349":1}}],["caught",{"2":{"102":1}}],["causing",{"2":{"99":1,"327":1}}],["caused",{"2":{"245":1,"327":1}}],["cause",{"2":{"61":1,"72":1,"76":1,"100":1,"105":1,"106":3,"153":1,"158":1,"159":1,"196":1,"221":2,"234":1,"309":1,"310":1,"327":2}}],["causes",{"2":{"49":1,"73":1,"167":1,"170":1,"310":1,"474":1}}],["cac3",{"2":{"257":2,"275":2}}],["cac2",{"2":{"257":2,"269":1,"270":1,"275":2}}],["cacreateserial",{"2":{"240":1}}],["cac",{"2":{"81":1,"269":1}}],["caching",{"2":{"69":2,"131":1,"211":2}}],["cachetimeout",{"2":{"204":1}}],["cached",{"2":{"69":3,"132":1,"142":1,"174":1,"204":1,"211":3}}],["caches",{"2":{"61":1,"234":1}}],["cache",{"0":{"326":1},"2":{"44":3,"163":2,"178":1,"211":2,"269":1,"313":1,"321":4,"326":9}}],["carefully",{"2":{"110":1,"245":1,"326":1,"336":1}}],["careful",{"2":{"67":1,"70":2,"160":1}}],["care",{"2":{"58":1,"65":1,"76":1,"221":1,"245":1,"320":1,"410":1,"414":1,"441":1}}],["calc",{"2":{"314":1}}],["calcrowinformation",{"2":{"310":1}}],["calculating",{"2":{"69":1,"70":1,"308":1,"310":1}}],["calculatepartition",{"2":{"192":1}}],["calculate",{"2":{"59":1,"69":1,"196":1,"269":1,"309":1,"310":3,"326":1}}],["calculated",{"2":{"58":1,"69":3,"192":1,"264":1,"270":1,"318":1,"326":1}}],["callbacks",{"2":{"157":3,"158":2,"159":1}}],["callback",{"0":{"159":1,"378":1},"2":{"157":2,"159":4,"160":3,"426":1,"428":2}}],["callee",{"2":{"221":1}}],["callers",{"2":{"328":1,"410":1}}],["caller",{"2":{"67":1,"181":1,"209":1,"214":1,"221":2,"329":1,"400":1,"407":2,"408":1,"412":1,"427":4,"431":3,"432":1,"435":1,"441":1,"447":1}}],["called",{"2":{"44":1,"50":3,"56":1,"58":1,"91":2,"115":1,"116":1,"149":2,"157":3,"160":1,"192":1,"209":1,"210":1,"234":1,"249":1,"266":1,"269":1,"270":5,"272":2,"312":3,"313":4,"346":2,"347":2,"448":1}}],["calling",{"2":{"56":1,"61":1,"62":1,"67":1,"69":1,"80":1,"90":1,"91":2,"92":1,"192":1,"210":1,"233":1,"266":3,"448":1}}],["calls",{"2":{"51":1,"56":1,"70":1,"91":2,"92":1,"100":1,"108":1,"154":1,"196":1,"211":1,"214":2,"221":2,"266":1,"270":1,"319":1,"347":1,"428":1}}],["call",{"2":{"20":1,"56":1,"80":1,"91":2,"92":4,"160":2,"192":1,"209":1,"221":1,"249":1,"266":1,"270":1,"272":2,"309":1,"310":1,"311":1,"313":3,"319":1,"408":1,"420":1,"447":1,"448":2,"449":2,"478":1}}],["cast",{"2":{"281":1}}],["cassandra",{"2":{"250":1,"258":1}}],["cassandraembed",{"2":{"18":1}}],["casing",{"2":{"193":1}}],["cascade",{"2":{"61":1}}],["cased",{"2":{"190":1}}],["case",{"2":{"56":1,"73":1,"103":1,"155":4,"185":1,"186":1,"192":1,"211":1,"212":1,"220":1,"245":2,"255":2,"259":2,"266":3,"269":2,"270":1,"271":1,"275":2,"310":5,"311":1,"312":1,"313":2,"321":1,"323":2,"327":1,"349":6,"439":4,"446":1,"479":2,"485":1}}],["cases",{"0":{"171":1,"309":1},"2":{"18":1,"88":1,"92":1,"104":2,"145":1,"170":6,"220":1,"278":1,"287":1,"308":1,"309":9,"313":1,"318":2,"319":1,"321":2,"327":1,"329":1,"397":1,"400":1,"428":1,"441":1,"461":1,"470":1}}],["canmaskmarkupvalue",{"2":{"428":1}}],["canmaskcontent",{"2":{"424":1}}],["canmaskvalue",{"2":{"420":1}}],["cancel",{"2":{"358":2,"359":1,"376":2,"377":1,"378":1}}],["candidate",{"2":{"149":1,"284":1,"321":1,"496":5,"498":1}}],["cannot",{"2":{"53":2,"56":1,"58":1,"66":1,"132":1,"133":1,"159":2,"163":1,"191":1,"221":1,"258":1,"266":1,"272":1,"280":1,"393":1,"405":1,"412":1,"428":1,"432":1,"458":1,"476":1}}],["can",{"2":{"12":1,"19":1,"20":1,"21":1,"26":1,"27":1,"28":1,"29":1,"37":1,"45":1,"48":2,"49":1,"53":1,"54":1,"55":1,"56":4,"58":2,"59":1,"63":1,"64":3,"67":2,"69":2,"70":4,"72":1,"73":2,"75":2,"77":4,"79":1,"82":2,"84":2,"85":2,"92":2,"100":1,"103":6,"106":4,"108":1,"112":4,"114":2,"115":2,"116":1,"120":3,"123":3,"125":1,"126":3,"128":1,"129":2,"131":4,"143":1,"145":1,"148":1,"149":2,"150":1,"153":1,"154":1,"156":1,"157":2,"158":1,"159":1,"160":1,"161":1,"162":1,"163":3,"166":2,"170":1,"174":1,"175":1,"187":1,"191":1,"192":3,"194":2,"195":2,"196":5,"202":1,"205":1,"214":1,"217":2,"221":2,"233":1,"238":1,"239":1,"240":1,"242":2,"249":1,"264":1,"265":1,"267":2,"269":1,"271":3,"272":3,"274":2,"276":1,"277":4,"280":3,"281":1,"288":1,"289":1,"290":2,"299":2,"308":6,"309":4,"310":4,"311":1,"312":3,"313":2,"318":2,"319":1,"321":4,"322":2,"323":3,"324":1,"326":2,"327":3,"328":1,"329":1,"330":3,"335":1,"336":1,"337":1,"339":3,"340":11,"345":1,"365":1,"386":1,"397":2,"399":2,"400":1,"401":2,"402":1,"407":3,"408":2,"412":1,"420":3,"424":1,"427":2,"428":1,"431":2,"432":1,"438":1,"439":1,"441":3,"446":2,"447":3,"448":1,"451":1,"452":3,"460":1,"473":1,"476":1,"477":1,"488":1,"495":1,"496":1,"497":1}}],["cmaskstyle",{"0":{"436":1},"2":{"437":1}}],["cmakelists",{"2":{"36":1,"44":1}}],["cmake3",{"2":{"7":1}}],["cmake",{"0":{"18":1,"35":1,"36":1,"40":1,"44":1,"45":1},"1":{"19":1,"36":1,"37":2,"38":2,"39":2,"40":1,"41":2,"42":2,"43":2,"44":2,"45":1},"2":{"3":1,"4":1,"5":1,"10":1,"11":1,"18":12,"19":1,"20":1,"21":2,"23":1,"33":1,"36":13,"39":1,"44":5,"45":2,"123":7,"129":3}}],["cmd",{"2":{"16":1}}],["cdata",{"2":{"481":1,"482":6,"483":2,"484":2,"486":2,"487":5,"488":4}}],["cd",{"2":{"13":1,"18":1,"23":1,"447":1}}],["chlorophyll",{"2":{"339":1}}],["cherry",{"2":{"498":1}}],["cheaper",{"2":{"196":1}}],["checkcompatibility",{"2":{"430":1,"432":1}}],["checkallnodes=0",{"2":{"340":1}}],["checklist",{"0":{"105":1},"2":{"108":1}}],["checkscopescans",{"2":{"451":1}}],["checks",{"2":{"100":1,"105":1,"211":1,"266":1,"313":2,"327":1,"410":1,"411":2,"412":1,"414":1,"415":2,"416":1,"420":1,"428":2,"431":4,"448":1,"462":1}}],["checkseqid",{"2":{"56":1}}],["checking",{"0":{"74":1},"2":{"72":2,"100":2,"149":1,"190":1,"214":2,"309":1,"326":1}}],["checked",{"2":{"22":1,"105":1,"149":2,"308":1,"495":1}}],["check",{"2":{"18":1,"23":1,"59":1,"70":2,"103":2,"106":1,"108":5,"109":1,"128":2,"156":1,"203":1,"211":1,"214":2,"219":1,"242":1,"304":2,"309":10,"310":1,"326":1,"327":3,"340":1,"411":1,"415":1,"420":1,"431":1,"432":4,"448":1}}],["chunked",{"0":{"359":1},"2":{"359":2}}],["chunks",{"2":{"326":1}}],["chunk",{"2":{"160":1,"195":1,"358":2}}],["chqlboundtarget",{"2":{"90":1}}],["chqlboundexpr",{"2":{"90":1}}],["choose",{"2":{"242":2,"310":1}}],["choosen",{"2":{"76":1}}],["choices",{"0":{"305":1},"1":{"306":1,"307":1}}],["choice",{"2":{"217":1}}],["chosen",{"2":{"83":1,"110":1,"245":1,"451":1}}],["choco",{"2":{"13":1}}],["chocolatey",{"2":{"13":3}}],["chart",{"2":{"495":1}}],["charts",{"2":{"183":1,"190":2,"196":1,"202":1,"340":1}}],["char>",{"2":{"358":1}}],["charge",{"2":{"326":1}}],["char",{"2":{"269":4,"275":6,"355":1,"357":2,"376":2,"408":6,"409":2,"413":2,"418":9,"422":2,"426":3,"428":3}}],["characters",{"2":{"222":1,"349":1,"403":1,"404":1,"436":1,"437":9,"439":2}}],["character",{"2":{"190":1,"419":2,"436":1,"437":2,"439":2}}],["characteristics",{"2":{"49":1}}],["changing",{"0":{"282":1},"2":{"166":1,"245":1,"308":1,"309":1}}],["change",{"2":{"53":2,"58":1,"64":1,"66":2,"67":1,"72":1,"73":1,"87":1,"102":1,"103":4,"104":1,"105":3,"106":3,"108":5,"109":2,"128":1,"166":1,"192":1,"194":1,"245":7,"261":1,"269":1,"282":2,"283":1,"289":2,"290":1,"310":3,"311":2,"312":2,"313":1,"320":1,"322":1,"326":1,"330":1,"339":1,"365":1,"399":2,"402":1,"411":2,"412":2,"448":1,"451":1,"460":1,"475":1,"497":1}}],["changes",{"2":{"26":1,"53":1,"70":1,"103":2,"105":1,"106":4,"108":7,"109":5,"110":2,"114":1,"120":1,"128":1,"145":1,"152":1,"164":1,"172":1,"175":1,"187":1,"190":1,"219":1,"238":1,"245":7,"246":2,"283":1,"290":1,"310":8,"311":3,"312":1,"313":7,"321":1,"448":1,"496":3,"497":1}}],["changed",{"2":{"13":1,"58":1,"66":1,"103":2,"108":1,"128":3,"175":1,"310":1,"326":1,"441":1,"446":1,"448":1}}],["channel",{"2":{"164":1,"192":3,"320":3,"322":1,"326":1,"330":2}}],["channels",{"0":{"164":1},"2":{"164":4,"192":2}}],["chaos",{"2":{"73":1}}],["challenges",{"0":{"98":1},"1":{"99":1,"100":1},"2":{"50":1,"99":1}}],["children",{"2":{"73":1,"266":1,"328":1}}],["childdataset",{"2":{"65":7,"310":1}}],["child",{"2":{"23":1,"50":3,"51":1,"56":1,"59":2,"64":4,"65":2,"68":2,"70":1,"90":1,"92":3,"93":1,"183":1,"192":3,"266":1,"270":7,"272":1,"309":2,"313":2,"318":1,"326":3,"328":1,"440":1,"477":2,"481":1,"483":1,"484":1}}],["c++res",{"2":{"369":1,"372":1,"373":1,"374":1}}],["c++cli",{"2":{"368":1,"375":1,"389":1,"390":1}}],["c++httplib",{"2":{"367":1,"368":1,"370":2,"371":1}}],["c++std",{"2":{"376":1}}],["c++svr",{"2":{"362":1,"363":1}}],["c++source",{"2":{"91":1}}],["c++11",{"0":{"227":1},"2":{"236":1,"350":1}}],["c++target",{"2":{"91":1}}],["c++",{"0":{"87":1,"217":1,"275":1},"1":{"218":1,"219":1,"220":1,"221":1,"222":1,"223":1,"224":1,"225":1,"226":1,"227":1},"2":{"7":1,"8":1,"10":1,"11":1,"30":2,"48":1,"50":1,"53":1,"79":1,"83":1,"87":2,"88":1,"90":8,"91":3,"96":2,"99":2,"187":1,"198":2,"217":4,"218":1,"219":1,"233":1,"250":1,"252":1,"255":1,"270":1,"271":6,"274":1,"312":2,"351":1,"367":1,"385":1,"433":1,"471":1}}],["cosnt",{"2":{"408":1}}],["cost",{"2":{"157":1,"308":3}}],["color",{"2":{"338":1,"355":1}}],["column",{"2":{"269":2,"270":3}}],["columns",{"2":{"249":2,"269":2}}],["collator",{"2":{"318":1}}],["collapse",{"2":{"103":1,"328":1}}],["collapsed",{"2":{"103":1}}],["collecting",{"2":{"172":1}}],["collection",{"2":{"166":1,"167":5,"169":1,"170":2,"172":6,"174":1,"175":7,"176":1,"179":1,"180":1,"183":1,"184":1,"186":1,"187":1,"189":1,"273":1,"288":1,"399":1,"401":1,"414":1,"432":1,"440":1,"444":1,"445":3}}],["collected",{"2":{"169":2}}],["coordination",{"2":{"172":1}}],["coordinated",{"2":{"162":1,"266":1}}],["coding",{"0":{"215":1,"216":1,"217":1,"228":1},"1":{"216":1,"217":1,"218":2,"219":2,"220":2,"221":2,"222":2,"223":2,"224":2,"225":2,"226":2,"227":2,"228":1,"229":2,"230":2},"2":{"102":1,"106":1,"216":2,"217":1,"226":1}}],["coder",{"2":{"370":2}}],["codeversion=",{"2":{"274":1}}],["coded",{"2":{"102":1}}],["code",{"0":{"52":1,"83":1,"86":1,"96":1,"101":1,"107":1,"229":1,"312":1,"325":1,"447":1},"1":{"53":1,"54":1,"55":1,"56":1,"87":1,"88":1,"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1,"97":1,"102":1,"103":1,"104":1,"105":1,"106":1,"108":1,"109":1,"110":1},"2":{"12":1,"24":1,"31":2,"33":1,"37":1,"47":1,"48":2,"50":3,"53":3,"56":3,"58":2,"62":1,"63":1,"64":1,"65":1,"67":1,"68":2,"69":1,"73":1,"79":1,"80":2,"81":1,"83":5,"84":5,"85":1,"86":2,"87":4,"91":2,"92":1,"93":1,"96":3,"99":3,"101":2,"102":2,"103":5,"104":3,"105":2,"106":10,"108":5,"113":1,"128":1,"172":2,"178":1,"187":5,"194":7,"196":3,"198":4,"199":1,"204":1,"216":3,"217":1,"219":4,"223":2,"232":1,"233":1,"246":1,"249":5,"251":1,"254":3,"255":5,"257":1,"266":4,"267":2,"269":4,"270":7,"271":3,"272":5,"277":1,"282":1,"283":1,"285":1,"287":1,"289":3,"290":1,"296":1,"301":2,"308":2,"309":1,"310":7,"311":6,"312":12,"313":5,"314":1,"317":2,"318":2,"319":1,"320":1,"321":2,"324":1,"326":3,"327":1,"329":1,"330":1,"338":3,"345":1,"349":1,"350":1,"447":1,"458":1,"471":3,"474":2}}],["covering",{"2":{"209":1}}],["covers",{"2":{"135":1,"172":1,"186":1,"201":1,"209":1,"210":1,"298":1,"464":1}}],["cover",{"2":{"75":1,"137":1,"160":1,"163":1,"174":1,"249":1,"287":1,"309":1,"471":1,"474":1}}],["covered",{"2":{"63":1,"70":1,"190":1,"201":1,"209":2,"256":1,"309":1}}],["cout",{"2":{"367":1}}],["course",{"2":{"83":1}}],["could",{"2":{"58":1,"62":1,"66":2,"84":1,"85":2,"91":1,"103":1,"105":3,"106":2,"158":1,"160":2,"161":2,"162":1,"191":1,"217":1,"234":1,"282":1,"284":1,"286":1,"308":3,"310":5,"312":2,"317":2,"318":1,"321":1,"326":1,"327":2,"328":1,"329":2,"338":1,"399":1,"408":1,"441":2,"474":1,"492":3}}],["country",{"2":{"399":1}}],["countries",{"2":{"399":2}}],["count=",{"2":{"262":1,"274":1}}],["countermetric",{"2":{"187":1}}],["countermetric>",{"2":{"187":2,"188":4}}],["counters",{"2":{"185":1}}],["counter",{"0":{"178":1},"2":{"105":1,"174":1,"178":1,"187":2,"308":2,"310":1,"311":2}}],["counted",{"0":{"234":1},"2":{"49":1,"58":1,"93":3,"145":1,"221":2}}],["counting",{"2":{"84":1}}],["count",{"2":{"58":1,"75":1,"76":3,"149":2,"156":4,"169":1,"172":2,"178":1,"181":1,"184":1,"186":1,"189":1,"190":1,"191":1,"234":1,"269":1,"330":1,"339":1,"361":1,"365":3,"366":1,"437":4}}],["counts",{"2":{"58":1,"63":1,"81":1,"169":2,"171":1,"172":1,"178":1,"181":1,"269":1,"308":2,"309":1,"310":1,"319":1}}],["coupled",{"2":{"402":1,"435":1}}],["couple",{"2":{"53":1,"63":1,"69":2,"85":1,"103":1,"196":1,"214":1,"310":1}}],["copilot",{"0":{"336":1},"1":{"337":1,"338":1,"339":1},"2":{"277":3,"336":2}}],["copied",{"2":{"217":1,"250":1,"347":1,"473":1,"495":1}}],["copies",{"2":{"194":1,"196":2,"265":1}}],["copyright",{"2":{"108":1}}],["copy",{"2":{"82":1,"108":1,"192":3,"193":1,"196":7,"240":1,"241":1,"247":1,"262":1,"312":1,"323":1,"329":1,"340":9,"347":1,"348":1,"486":1,"487":1,"488":1,"494":1}}],["copying",{"2":{"53":1,"160":2,"191":1,"196":4,"329":1,"474":1}}],["cope",{"2":{"48":1,"308":1}}],["conduct",{"2":{"461":1}}],["condition",{"2":{"72":1,"81":1,"267":2,"327":1}}],["conditions",{"2":{"72":1,"103":1,"105":1,"106":1,"170":1,"245":1,"321":1,"326":1,"431":1}}],["conditionally",{"2":{"418":1,"419":1,"421":1}}],["conditional",{"2":{"70":1,"83":1,"318":1,"419":3,"421":2,"423":1}}],["connnected",{"2":{"273":1}}],["connects",{"2":{"204":1}}],["connected",{"2":{"196":1,"254":1,"269":1,"323":1,"327":2}}],["connect",{"2":{"141":1,"143":2,"327":9,"446":3,"449":4}}],["connections",{"0":{"142":1},"1":{"143":1,"144":1},"2":{"142":1,"143":6,"323":1,"327":1,"480":1}}],["connection",{"0":{"143":1,"361":1,"382":1},"2":{"136":1,"143":12,"204":2,"327":2,"375":1,"382":2}}],["connecting",{"2":{"131":1,"269":1,"327":1}}],["conventions",{"0":{"215":1,"216":1,"217":1,"228":1},"1":{"216":1,"217":1,"218":2,"219":2,"220":2,"221":2,"222":2,"223":2,"224":2,"225":2,"226":2,"227":2,"228":1,"229":2,"230":2},"2":{"217":1,"230":1,"299":1,"303":1,"441":1}}],["convention",{"2":{"184":1,"185":1}}],["convenience",{"2":{"172":1,"187":2,"189":1}}],["conversion",{"2":{"474":1}}],["conversions",{"2":{"182":1,"189":1}}],["conversing",{"2":{"330":1}}],["conversations",{"2":{"103":1}}],["conversation",{"2":{"103":2,"299":1}}],["convertable",{"2":{"187":1}}],["converts",{"2":{"182":1}}],["converted",{"2":{"58":1,"70":2,"73":2,"77":1,"90":3,"259":1}}],["converting",{"2":{"50":1,"96":1,"99":1,"167":1,"186":1,"189":1}}],["convert",{"2":{"47":1,"85":1,"89":1,"95":1,"113":1,"172":1,"194":1,"196":2,"240":1}}],["conent",{"2":{"117":1}}],["conflicts",{"2":{"310":1}}],["confess",{"2":{"263":1}}],["confident",{"2":{"307":1}}],["confirmation",{"2":{"420":1}}],["confirm",{"2":{"241":1,"397":2,"420":2}}],["configmanager",{"2":{"301":1}}],["configmgr",{"2":{"301":2,"451":2}}],["configurable",{"2":{"145":1,"327":1,"328":1}}],["configurations",{"0":{"202":1},"2":{"36":1,"402":1,"420":1,"461":2,"480":1}}],["configuration",{"0":{"138":1,"183":1,"201":1,"282":1,"293":1,"451":1},"1":{"139":1,"140":1,"202":1,"203":1,"204":1,"205":1,"206":1,"207":1,"208":1},"2":{"18":1,"36":1,"134":1,"138":1,"139":2,"140":1,"143":1,"167":1,"183":5,"201":1,"202":1,"203":1,"204":4,"205":1,"213":1,"278":1,"279":1,"301":1,"407":1,"436":1,"437":1,"438":1,"439":1,"440":5,"441":4,"442":1,"443":3,"448":1,"449":2,"451":1,"472":2,"473":1,"474":3,"491":1}}],["configuring",{"0":{"132":1},"2":{"131":1,"441":1,"473":1}}],["configured",{"2":{"123":1,"176":1,"202":1,"204":1,"205":1,"211":2,"213":1,"323":1,"407":1,"439":2,"441":1}}],["configure",{"2":{"36":1,"54":1,"162":1,"196":1,"201":1,"293":1,"471":3,"487":1,"488":1}}],["config",{"2":{"3":1,"5":1,"36":2,"112":2,"116":2,"139":1,"140":3,"183":1,"201":1,"204":1,"240":1,"326":1,"328":2}}],["conf",{"2":{"240":6}}],["confusing",{"2":{"249":1}}],["confusion",{"2":{"106":1,"279":1,"299":1}}],["confused",{"2":{"160":1}}],["conforming",{"2":{"106":1}}],["concurrency",{"2":{"365":1}}],["concurrently",{"2":{"175":1,"425":1}}],["concurrent",{"2":{"29":1,"83":1,"425":3}}],["concluded",{"2":{"308":2}}],["conclusion",{"2":{"308":2}}],["concrete",{"2":{"105":1,"170":1,"219":1,"233":1,"278":1,"309":1,"441":4,"443":2}}],["concern",{"2":{"106":1}}],["concentrate",{"2":{"104":1}}],["concept",{"2":{"84":1,"317":2,"318":1,"337":2,"339":1,"403":1,"405":1,"410":1,"411":1,"414":1,"415":1,"446":1,"448":1}}],["concepts",{"2":{"63":1,"233":1}}],["concisely",{"2":{"317":1}}],["conciseness",{"2":{"280":1}}],["concise",{"2":{"83":1,"92":1,"280":1,"299":1,"311":1}}],["consecutive",{"2":{"270":1}}],["consequently",{"2":{"267":1}}],["consequence",{"2":{"249":1}}],["consolidating",{"2":{"217":1}}],["constitute",{"2":{"399":1}}],["construct",{"2":{"450":1}}],["constructs",{"2":{"188":1,"270":1,"448":1}}],["constructors",{"2":{"220":1}}],["constructor",{"2":{"187":1,"188":2,"270":1,"313":1,"367":1}}],["constructing",{"2":{"188":1}}],["construction",{"2":{"180":1}}],["constructive",{"2":{"106":1}}],["constructed",{"2":{"149":1}}],["constraints",{"2":{"50":1,"58":1,"217":2,"339":2,"414":1}}],["const",{"2":{"90":1,"269":2,"270":8,"275":16,"351":4,"354":2,"355":1,"356":2,"357":5,"358":3,"359":1,"360":2,"376":3,"408":8,"409":2,"413":2,"418":9,"422":2,"426":3,"428":4,"430":2}}],["constants",{"2":{"59":1,"75":2,"310":1,"452":1}}],["constant",{"0":{"75":1},"2":{"59":2,"69":1,"70":1,"72":1,"75":1,"77":1,"84":1,"149":1,"164":1,"309":2,"310":2}}],["consistency",{"2":{"170":1,"172":1,"299":1}}],["consistently",{"2":{"70":1}}],["consistent",{"2":{"51":1,"216":1,"232":1,"280":1,"299":2,"300":1,"308":1,"310":1,"320":1}}],["consist",{"2":{"140":1,"185":1}}],["consists",{"2":{"79":1,"166":1,"172":1,"250":1,"269":1,"462":1,"471":1}}],["considerations",{"2":{"278":1}}],["considers",{"2":{"106":1}}],["consider",{"2":{"103":2,"106":2,"108":1,"186":1,"217":1,"279":2,"307":1,"399":1,"441":1,"443":1}}],["considered",{"2":{"65":1,"108":1,"143":1,"193":1,"217":1,"437":1,"438":1,"443":2}}],["considering",{"2":{"58":1,"328":1}}],["consumed",{"2":{"319":1}}],["consumer",{"2":{"182":1,"419":3}}],["consumption",{"2":{"58":3,"153":1,"308":1}}],["consuming",{"2":{"28":1}}],["contact",{"2":{"307":1}}],["containerized",{"2":{"196":9,"202":1,"204":2,"246":1,"282":1,"324":1,"326":1,"340":3}}],["container",{"2":{"194":1,"196":4}}],["containers",{"2":{"113":1,"217":1}}],["contained",{"2":{"18":1,"89":1,"195":1,"249":1,"330":1,"345":1}}],["contain",{"2":{"48":1,"53":1,"58":3,"65":1,"69":1,"73":1,"81":1,"83":1,"87":1,"105":1,"108":3,"149":1,"183":1,"192":2,"233":1,"242":1,"246":1,"260":1,"267":1,"269":2,"270":1,"321":1,"326":1,"330":1,"423":2,"432":1,"448":1,"479":1,"483":1,"484":1}}],["containing",{"2":{"36":1,"50":1,"53":1,"60":1,"133":1,"204":1,"243":1,"250":1,"264":2,"270":1,"326":1,"428":1,"437":1,"449":1,"477":1}}],["containsassertkeyed",{"2":{"69":1}}],["contains",{"2":{"36":2,"48":1,"53":3,"56":3,"59":1,"60":1,"61":2,"79":3,"80":1,"81":1,"82":2,"110":1,"122":1,"149":2,"160":1,"161":1,"181":1,"192":1,"197":1,"246":1,"249":2,"250":2,"254":1,"255":1,"256":2,"260":2,"261":1,"267":2,"269":3,"270":2,"272":1,"308":3,"309":1,"310":4,"311":2,"312":1,"313":3,"405":1,"455":1,"482":1,"483":1,"494":2}}],["contingency",{"2":{"267":5}}],["continuing",{"2":{"412":1}}],["continually",{"2":{"249":1}}],["continuously",{"2":{"179":1}}],["continue",{"0":{"360":1},"2":{"143":1,"157":2,"360":4}}],["contiguous",{"2":{"93":1,"313":1}}],["contended",{"2":{"328":1}}],["contenttype",{"2":{"422":1,"423":1,"432":1,"438":3,"439":1}}],["contentreader",{"2":{"357":1}}],["contention",{"2":{"153":1}}],["content",{"0":{"357":2,"358":2,"376":2,"377":2},"2":{"105":1,"277":1,"351":5,"355":1,"356":2,"357":6,"358":6,"359":1,"400":2,"419":1,"423":2,"424":2,"425":1,"427":2,"432":2,"436":1,"438":6,"439":5,"440":1,"441":1,"443":2,"474":2,"475":1,"477":2,"481":1,"482":1,"483":3,"484":3,"486":1}}],["contents",{"0":{"250":1,"275":1},"2":{"96":1,"108":1,"249":2,"250":1,"253":1,"256":1,"269":1,"311":1,"349":1,"386":1,"397":1,"448":2,"474":1,"482":1,"494":1}}],["contextual",{"2":{"403":1,"412":2,"416":2}}],["context",{"0":{"402":1},"2":{"61":1,"70":3,"74":1,"83":1,"91":1,"105":1,"167":1,"194":1,"249":1,"312":2,"318":1,"339":2,"400":2,"401":1,"402":2,"404":1,"405":1,"407":3,"408":3,"410":1,"414":1,"420":1,"423":2,"427":1,"428":1,"432":6,"441":3,"442":1,"443":1,"474":1,"482":1,"486":2,"487":2,"488":2}}],["contexts",{"2":{"58":1,"70":1,"83":1,"267":1,"445":1}}],["contract",{"2":{"233":1}}],["controlling",{"2":{"419":1,"436":1,"439":1}}],["controlled",{"2":{"37":1,"62":1,"87":1,"324":1,"402":1}}],["controled",{"2":{"330":1}}],["controls",{"2":{"234":1,"266":1,"269":1,"310":1}}],["control",{"2":{"30":1,"58":1,"81":1,"192":1,"196":1,"234":1,"238":1,"254":1,"266":2,"269":1,"302":1,"308":1,"327":2,"330":1,"402":2,"473":1,"485":1}}],["contributions",{"2":{"277":1,"396":1}}],["contributing",{"0":{"276":1},"1":{"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"298":1}}],["contribute",{"2":{"120":1,"276":2,"277":2,"331":1}}],["contributers",{"2":{"103":1}}],["contributors",{"2":{"103":2,"109":2,"120":1,"238":1}}],["contributor",{"2":{"103":4,"106":5,"109":1}}],["contrib",{"2":{"16":3}}],["corner`",{"2":{"335":1}}],["corner",{"2":{"335":2}}],["corresponds",{"2":{"270":1,"273":1,"308":1,"311":2}}],["correspond",{"2":{"82":1,"249":1,"260":1,"273":1,"308":1,"310":1}}],["correspondingly",{"2":{"327":1}}],["corresponding",{"2":{"81":1,"84":1,"172":1,"220":1,"222":1,"243":1,"257":1,"269":1,"272":1,"311":1,"420":1,"425":1,"439":1}}],["correctness",{"2":{"397":1}}],["correctly",{"2":{"48":1,"65":1,"66":1,"242":1,"270":1,"309":3,"310":1,"313":2}}],["correct",{"0":{"79":1},"2":{"13":1,"18":1,"105":3,"196":1,"308":2,"309":1,"313":1,"327":1}}],["corruption",{"2":{"245":1}}],["corrupt",{"2":{"48":1,"460":2,"462":2,"469":1}}],["cores",{"2":{"23":1}}],["core",{"2":{"7":1,"8":1,"16":1,"20":1,"104":2,"268":1,"309":1,"397":3,"446":1,"449":4}}],["coming",{"2":{"288":1,"299":1,"310":1,"318":1,"332":2,"349":1}}],["combination",{"2":{"77":1,"195":1,"264":1,"311":1,"406":1,"420":1,"421":1,"424":1,"428":1,"485":1}}],["combining",{"2":{"76":2,"183":1,"473":1}}],["combined",{"2":{"77":1,"145":1,"183":2,"186":1}}],["combine",{"2":{"76":2}}],["combines",{"2":{"30":1,"266":1}}],["comes",{"2":{"73":1,"195":1,"270":1,"310":1}}],["come",{"2":{"72":1,"85":1,"124":1}}],["comma",{"2":{"139":1,"204":1,"479":1,"485":1}}],["command",{"0":{"283":1,"455":1,"456":1,"489":1},"1":{"456":1,"457":1,"458":1,"490":1,"491":1,"492":1},"2":{"19":1,"44":1,"53":2,"55":1,"124":1,"129":1,"131":1,"196":1,"241":1,"259":1,"264":2,"279":1,"283":1,"301":1,"302":4,"340":5,"348":1,"455":2,"456":1,"460":1,"471":1,"472":2,"474":1,"476":3,"478":1,"482":1,"485":1,"486":1,"490":2,"491":1,"492":1}}],["commands",{"0":{"44":1},"2":{"18":1,"114":1,"460":1,"471":2,"490":1,"495":1}}],["commit",{"2":{"105":1,"108":4,"128":1,"146":1,"309":4,"310":2,"311":2,"312":2,"313":4}}],["commits",{"2":{"103":1,"270":1,"308":1,"313":1}}],["commenting",{"2":{"106":2}}],["commented",{"2":{"105":1,"108":1}}],["comment",{"0":{"106":1},"2":{"103":3,"106":5,"109":1,"241":1,"283":1}}],["comments",{"0":{"103":1,"223":1},"2":{"103":7,"104":1,"105":1,"106":4,"108":1,"109":1,"223":2,"311":1,"338":1,"349":2}}],["communicate",{"2":{"265":1,"280":1}}],["communications",{"2":{"27":1}}],["community",{"2":{"22":1,"242":5,"243":1}}],["commonly",{"2":{"230":1,"234":1,"279":1,"303":1}}],["commonbasedn",{"2":{"204":1}}],["commoning",{"2":{"73":1}}],["commoned",{"2":{"49":1,"58":1,"73":1}}],["commonsetup",{"2":{"36":1}}],["common",{"0":{"37":1,"41":1,"42":1,"43":1,"303":1},"2":{"27":1,"36":4,"56":1,"69":2,"70":1,"96":2,"108":1,"174":3,"176":1,"194":1,"217":1,"218":1,"220":1,"221":2,"233":4,"250":1,"258":1,"266":1,"267":1,"269":1,"278":1,"297":1,"303":1,"308":1,"312":1,"326":1,"333":1,"403":1,"486":1,"487":1,"488":1,"494":1}}],["comprised",{"2":{"449":1}}],["comprehensive",{"2":{"280":1,"461":1}}],["compress",{"0":{"389":1,"390":1},"2":{"321":4,"389":1,"460":12,"462":1,"486":1,"487":1,"488":1}}],["compressible",{"2":{"321":1}}],["compressiontype=",{"2":{"460":12}}],["compression",{"0":{"321":1,"386":1,"467":1},"1":{"387":1,"388":1,"389":1,"390":1,"468":1},"2":{"195":1,"321":3,"386":1,"387":1,"388":1,"461":1,"462":1,"467":2}}],["compressed",{"2":{"79":1,"191":2,"192":1,"195":1,"321":3,"345":1,"390":1}}],["composite",{"2":{"273":1}}],["composed",{"2":{"267":1}}],["componentfiles",{"2":{"491":1}}],["component",{"0":{"187":1},"1":{"188":1,"189":1},"2":{"79":1,"112":1,"133":1,"136":1,"167":2,"170":1,"172":17,"174":3,"175":1,"176":2,"178":1,"179":2,"180":4,"181":1,"183":5,"184":1,"186":4,"187":7,"188":1,"196":1,"204":1,"279":1,"287":1,"397":2,"451":4}}],["components",{"0":{"277":1,"278":1,"279":1},"1":{"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"12":3,"27":2,"33":1,"112":1,"126":1,"140":1,"166":2,"168":1,"174":3,"175":3,"196":1,"203":1,"204":1,"205":2,"249":1,"250":1,"251":1,"265":2,"277":1,"280":1,"288":1,"300":1,"301":1,"332":1,"461":1}}],["compound",{"2":{"68":2,"76":1,"87":3,"185":1,"273":1,"349":1}}],["computed",{"2":{"63":1}}],["compliant",{"2":{"203":1}}],["complicating",{"2":{"402":1}}],["complication",{"2":{"95":1,"130":1}}],["complications",{"0":{"158":1},"2":{"64":1,"65":1,"95":1,"158":1,"196":1}}],["complicated",{"2":{"64":1,"270":2,"312":1,"313":1}}],["complicates",{"2":{"50":1}}],["completion",{"2":{"238":1,"428":1}}],["completing",{"2":{"169":1}}],["completes",{"2":{"243":1,"323":1}}],["completely",{"2":{"69":1,"79":1,"103":1,"127":1,"156":1,"191":1,"195":1,"326":1}}],["completed",{"2":{"58":1,"114":1,"188":2,"238":1,"266":1,"274":1,"308":1,"318":1,"402":1}}],["complete",{"2":{"31":1,"79":1,"87":1,"109":1,"114":1,"166":1,"168":1,"175":1,"192":1,"238":1,"268":1,"270":2,"308":2,"326":1,"378":2,"399":1,"400":2,"410":1,"477":1,"481":1,"490":2}}],["complexity",{"2":{"473":1}}],["complex",{"2":{"58":1,"73":1,"105":1,"109":1,"245":2,"317":1,"319":1,"336":1,"337":1}}],["compatible",{"2":{"448":1,"449":1,"450":2}}],["compatibility",{"0":{"411":1,"415":1,"420":1,"424":1,"428":1,"430":1},"1":{"431":1,"432":1},"2":{"105":1,"194":1,"196":1,"245":2,"311":1,"402":1,"410":1,"411":1,"412":2,"414":2,"415":1,"416":1,"420":1,"428":1,"431":3,"432":5,"446":1,"469":1}}],["company",{"2":{"241":1}}],["comparing",{"2":{"420":1}}],["comparisons",{"2":{"103":1,"439":2}}],["comparison",{"2":{"53":2,"128":1,"213":1,"312":1,"469":1,"470":1}}],["compares",{"2":{"462":1,"467":1}}],["compare",{"2":{"53":3,"128":4,"249":1,"309":1,"311":1,"461":1}}],["compared",{"2":{"24":1}}],["compacted",{"2":{"161":1}}],["compactrows",{"2":{"161":1}}],["compacting",{"0":{"161":1},"2":{"161":1}}],["compact",{"2":{"26":1,"56":1,"83":1,"321":1}}],["compiles",{"2":{"264":2}}],["compile",{"2":{"83":2,"106":1,"128":2,"130":1,"262":1,"271":1,"274":1,"340":2}}],["compilers",{"2":{"123":1,"271":1}}],["compilerpath",{"2":{"54":1}}],["compiler",{"0":{"128":1},"2":{"53":2,"96":1,"128":3,"245":1,"250":1,"309":1}}],["compiled",{"2":{"30":1,"56":1,"61":1,"79":1,"126":1,"128":3,"264":3,"265":1,"271":1,"325":1}}],["compiling",{"2":{"23":2,"125":1,"262":1,"274":1,"313":1}}],["compilation",{"2":{"20":1}}],["com",{"2":{"0":1,"13":1,"15":1,"17":1,"131":6,"225":1,"240":4,"302":2,"307":1,"308":3,"309":1,"310":2,"311":1,"312":1,"313":2,"334":2,"335":1,"338":1,"383":1,"388":1,"449":1,"495":8}}],["c",{"2":{"3":1,"4":1,"10":2,"11":2,"53":2,"70":2,"75":1,"108":1,"128":2,"133":1,"194":2,"196":3,"220":1,"233":1,"240":1,"246":1,"255":1,"257":2,"269":1,"274":1,"275":4,"347":1,"353":2,"496":2}}],["aωb",{"2":{"371":1}}],["akin",{"2":{"340":1}}],["aeaa209092ea1af9660c6908062c1b0b9acff36b",{"2":{"313":1}}],["azuread",{"2":{"204":1}}],["azureactivedirectory",{"2":{"204":1}}],["azure",{"0":{"335":1},"2":{"194":1,"288":2,"332":2,"333":2,"335":3,"340":5}}],["away",{"2":{"194":1,"327":1}}],["awareness",{"2":{"411":1,"415":1,"427":1,"441":1}}],["aware",{"2":{"175":1,"277":1,"301":1,"407":1,"441":2}}],["awkward",{"2":{"73":1}}],["a=b",{"2":{"75":1}}],["ai",{"0":{"339":1},"2":{"337":1,"339":9}}],["aids",{"2":{"280":1,"471":1}}],["aid",{"2":{"73":1,"218":1}}],["aiming",{"2":{"106":1}}],["aimed",{"2":{"101":1,"288":1}}],["aim",{"2":{"58":1,"75":1,"108":2,"192":1,"246":1,"308":1,"309":1}}],["aims",{"0":{"48":1},"2":{"83":1}}],["availability",{"2":{"420":1,"432":1}}],["available",{"0":{"352":1},"2":{"15":1,"69":1,"74":1,"102":1,"123":1,"162":1,"163":1,"204":1,"217":1,"234":1,"238":1,"239":1,"277":1,"308":1,"312":1,"326":3,"340":2,"384":1,"385":1,"387":1,"388":1,"403":1,"420":1,"428":1,"431":1,"446":1,"447":1,"450":1,"467":1,"490":1,"495":1}}],["avaialable",{"2":{"265":1}}],["average",{"2":{"154":1,"174":1,"301":1}}],["avoiding",{"2":{"149":1}}],["avoids",{"2":{"103":1,"132":1,"329":1}}],["avoided",{"2":{"66":1}}],["avoid",{"0":{"339":1},"2":{"58":2,"61":1,"73":2,"85":1,"106":1,"108":1,"131":1,"233":1,"270":1,"271":1,"299":1,"308":1,"310":2,"318":1,"321":1,"324":2,"326":1,"327":1,"330":2,"402":1}}],["afford",{"2":{"221":1}}],["affected",{"2":{"405":1,"431":1}}],["affect",{"2":{"106":1,"170":1,"267":1,"400":1,"402":1,"419":1}}],["affecting",{"2":{"58":1,"451":1}}],["affects",{"2":{"56":1,"285":2}}],["afterwards",{"2":{"83":1}}],["after",{"2":{"56":1,"58":2,"93":1,"108":1,"109":1,"125":1,"221":1,"238":1,"301":1,"308":3,"312":1,"313":2,"318":3,"320":2,"323":1,"324":1,"352":1,"442":1,"483":1,"498":1}}],["absence",{"2":{"431":1,"438":1}}],["absent",{"2":{"408":1,"412":1,"416":1}}],["abstracts",{"2":{"473":1}}],["abstraction",{"2":{"410":1,"414":1,"420":1,"421":1}}],["abstract",{"2":{"105":1,"233":1,"272":1,"435":1}}],["abastract",{"2":{"405":1}}],["abandoned",{"2":{"320":1,"329":1}}],["abcdefg",{"2":{"358":1}}],["ability",{"2":{"196":2,"317":1,"400":1,"448":1}}],["able",{"2":{"48":1,"56":1,"106":1,"141":1,"157":1,"159":1,"196":1,"277":1,"321":1,"446":1}}],["abortpendingdata",{"2":{"329":1}}],["abortpending",{"2":{"329":1}}],["about",{"0":{"316":1,"321":1},"1":{"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1},"2":{"25":1,"59":1,"61":1,"67":1,"69":2,"79":1,"81":1,"89":2,"102":1,"106":1,"108":1,"120":1,"131":1,"141":1,"149":1,"153":1,"191":1,"192":1,"249":1,"250":1,"269":2,"270":1,"272":1,"279":1,"282":1,"287":1,"288":2,"290":1,"307":1,"308":1,"310":1,"320":2,"326":3,"332":1,"339":1,"400":1,"424":1,"428":1,"446":1,"451":1,"455":1}}],["above",{"2":{"23":1,"38":1,"56":1,"122":1,"124":1,"132":1,"140":1,"141":1,"166":1,"167":1,"170":1,"187":2,"210":1,"218":1,"254":1,"267":1,"269":1,"270":2,"309":2,"319":1,"321":1,"327":2,"449":1,"460":1,"477":1,"486":1}}],["adoption",{"2":{"280":1}}],["adopted",{"2":{"106":1,"219":1}}],["advising",{"2":{"276":1}}],["advantages",{"2":{"214":1,"446":1}}],["advantage",{"2":{"56":1,"153":1,"163":2,"308":1,"474":1}}],["advanced",{"2":{"44":1,"45":1,"279":2}}],["adhere",{"2":{"216":1,"446":2}}],["administrators",{"2":{"448":1}}],["administrator",{"2":{"340":1,"451":1,"452":1}}],["admin",{"2":{"204":2,"335":2,"485":1}}],["admingroupname",{"2":{"204":1}}],["adapt",{"2":{"196":1,"312":1}}],["adapted",{"2":{"53":1}}],["adjective",{"2":{"302":1}}],["adjusted",{"2":{"141":1}}],["adjusts",{"2":{"141":1}}],["adjacent",{"2":{"76":1,"160":1}}],["ads",{"2":{"139":3,"140":1,"141":4,"143":1,"204":1}}],["ad",{"0":{"139":1,"140":1,"141":1,"144":1},"2":{"135":2,"139":1,"140":3,"141":5,"142":1,"143":5,"144":2,"204":15,"211":2,"337":1}}],["adddummynode",{"2":{"325":1}}],["addtimingtoworkunit>",{"2":{"259":1,"274":1}}],["addtimingtoworkunit>0",{"2":{"259":1,"274":1}}],["addresses",{"2":{"163":2,"322":2,"411":1,"415":1}}],["addressed",{"2":{"103":3,"106":1}}],["addressing",{"2":{"106":1}}],["address",{"2":{"103":2,"106":3,"139":1,"146":1,"147":1,"149":3,"163":1,"172":1,"192":1,"204":1,"241":2,"252":1,"261":2,"269":1,"270":3,"274":3,"275":1,"279":1,"297":1,"340":4,"384":1}}],["addfilter",{"2":{"87":1}}],["addexpr",{"2":{"87":1}}],["added",{"2":{"48":1,"59":1,"63":2,"64":1,"65":2,"66":1,"67":1,"75":1,"84":2,"85":1,"86":1,"87":1,"109":1,"116":1,"128":1,"139":1,"143":2,"149":1,"172":2,"186":1,"187":1,"207":1,"208":1,"211":1,"223":1,"245":1,"264":5,"286":1,"308":1,"309":1,"310":3,"313":1,"326":1,"327":1,"329":1,"330":1,"346":1,"451":2,"492":1}}],["additions",{"2":{"183":1}}],["addition",{"2":{"64":1,"172":1,"277":1,"279":1,"290":1,"321":1,"324":1,"340":1,"412":1,"428":1,"438":1}}],["additionally",{"2":{"141":1,"170":1,"181":1,"202":1,"461":1}}],["additional",{"0":{"5":1,"469":1},"2":{"5":1,"12":1,"65":1,"73":1,"106":2,"113":1,"130":1,"131":1,"154":1,"157":1,"170":1,"279":1,"280":1,"308":1,"349":1,"401":1,"402":2,"407":1,"408":1,"410":1,"411":1,"412":1,"415":1,"428":1,"444":1,"448":2,"479":1,"485":1,"492":1}}],["adding",{"0":{"115":1,"116":1,"188":1,"283":1,"284":1},"2":{"56":2,"87":1,"108":1,"187":3,"204":1,"245":1,"308":4,"309":1,"311":3,"312":1,"313":1,"317":1,"319":1,"326":1,"327":1}}],["adds",{"2":{"18":1,"37":1,"65":1,"74":1,"299":1,"313":2,"442":1}}],["add",{"2":{"18":1,"37":9,"44":3,"56":2,"67":1,"106":1,"108":1,"112":2,"115":3,"116":4,"133":3,"174":1,"187":5,"196":1,"234":2,"266":1,"270":1,"276":1,"286":1,"290":1,"308":7,"310":3,"312":3,"326":2,"327":1,"335":1,"340":3,"451":1}}],["academics",{"2":{"280":1}}],["achieve",{"2":{"267":1,"308":1}}],["achieved",{"2":{"157":1,"266":1,"267":1}}],["acquainted",{"2":{"217":1}}],["acknowledge",{"2":{"103":1,"278":1}}],["act",{"2":{"410":1,"419":2}}],["actor",{"2":{"239":1}}],["actual",{"2":{"189":1,"323":1,"448":1,"473":1}}],["actually",{"2":{"64":1,"73":1,"192":1,"196":1,"264":1,"308":1,"324":1,"326":1,"347":1}}],["action5",{"2":{"458":3}}],["action4",{"2":{"458":3}}],["action2",{"2":{"458":3}}],["action1",{"2":{"458":3}}],["action",{"0":{"283":1},"2":{"68":3,"73":1,"166":1,"169":1,"210":1,"211":1,"254":1,"267":1,"283":1,"325":1,"437":4,"458":8,"460":18}}],["actions",{"2":{"41":1,"59":1,"68":1,"80":2,"100":1,"170":1,"172":1,"196":1,"198":2,"238":1,"239":1,"240":2,"241":3,"251":1,"267":3}}],["activityinterfaceenum",{"2":{"311":1}}],["activity",{"0":{"88":1,"257":1},"2":{"50":1,"51":1,"70":1,"81":10,"83":1,"87":1,"88":3,"89":1,"92":1,"155":1,"192":1,"256":4,"257":4,"269":27,"270":24,"272":4,"273":3,"275":1,"308":14,"309":1,"310":8,"311":9,"312":8,"313":11,"315":1,"318":9,"327":5}}],["activities",{"0":{"318":1},"2":{"48":1,"51":1,"70":1,"73":1,"75":1,"76":1,"81":4,"92":1,"155":1,"256":5,"268":1,"269":8,"270":10,"271":1,"273":1,"274":1,"308":2,"310":2,"311":1,"312":4,"313":1,"318":4,"319":4,"327":4}}],["activedirectory",{"2":{"204":2}}],["activetransactions",{"2":{"170":1}}],["active",{"2":{"26":14,"61":1,"64":3,"65":4,"69":1,"70":1,"74":1,"85":1,"87":2,"135":1,"147":1,"148":1,"155":1,"169":1,"185":1,"204":1,"211":1,"251":1,"264":3,"280":1,"299":3,"326":1,"347":1}}],["accuracy",{"2":{"339":1}}],["accurate",{"2":{"308":2,"339":4}}],["accurately",{"2":{"48":1,"319":1}}],["accumulate",{"2":{"181":2}}],["accomplish",{"2":{"170":1,"337":1,"338":1}}],["accounts",{"2":{"190":1,"196":2}}],["account",{"2":{"131":2,"132":1,"196":1,"309":1,"397":1,"400":1,"437":1}}],["according",{"2":{"128":1,"181":1,"307":1,"433":1,"448":1,"451":1}}],["accidental",{"2":{"108":1}}],["accidentally",{"2":{"108":1}}],["acceptance",{"2":{"411":1,"412":2,"415":1,"432":2}}],["acceptable",{"2":{"170":1,"301":1,"432":1,"441":1,"443":1}}],["accept",{"2":{"310":1,"368":2,"390":1,"412":1,"431":1,"451":1}}],["accepting",{"2":{"310":1,"432":1}}],["acceptsproperty",{"2":{"411":1,"415":1}}],["accepts",{"2":{"186":1,"214":1,"432":3}}],["accepted",{"2":{"53":1,"106":1,"108":1,"230":1,"408":2,"411":1,"412":4,"415":1,"416":3,"432":1,"435":2}}],["accessed",{"2":{"69":2,"105":1,"131":1,"133":1,"141":2,"143":1,"250":1,"258":1,"326":1,"401":1,"403":1,"404":1,"447":1}}],["accessor",{"2":{"69":3}}],["accessing",{"2":{"58":1,"155":2,"190":1,"194":1}}],["access",{"0":{"94":1},"2":{"31":3,"32":2,"58":1,"69":1,"131":4,"132":2,"133":1,"134":1,"135":1,"163":1,"174":2,"180":1,"190":2,"192":2,"196":2,"204":1,"209":1,"212":1,"214":4,"239":2,"272":1,"342":1,"400":1,"449":1,"451":3,"452":1}}],["across",{"2":{"28":1,"299":1,"308":1,"327":3,"330":2,"461":2,"462":1}}],["auxillary",{"0":{"480":1},"2":{"477":1,"480":1}}],["auxiliary",{"2":{"27":1}}],["audiences",{"2":{"279":1}}],["audience",{"2":{"276":1,"278":1,"280":1,"485":3}}],["augmented",{"2":{"250":1}}],["authplugintype",{"2":{"451":1}}],["authmethod",{"2":{"204":1,"451":1}}],["auth",{"2":{"131":2,"379":3,"380":3,"474":1,"479":1,"486":1,"487":2}}],["authenticating",{"2":{"212":1,"214":1}}],["authentication",{"0":{"209":1,"210":1,"214":1,"379":1},"1":{"210":1,"211":2,"212":2,"213":2,"214":1},"2":{"130":1,"131":3,"132":1,"203":1,"204":1,"209":3,"210":3,"211":5,"212":2,"213":1,"214":1,"379":4,"380":4,"400":1,"446":4,"449":2,"451":1}}],["authenticates",{"2":{"212":1,"214":1,"448":1}}],["authenticated",{"2":{"209":1,"211":2,"214":1,"448":2}}],["authenticateuser",{"2":{"209":1,"210":2}}],["authenticate",{"2":{"131":1,"211":2,"214":3}}],["authors",{"2":{"474":1}}],["authoritative",{"2":{"419":2}}],["authorities",{"2":{"307":1}}],["authoritykeyidentifier=keyid",{"2":{"240":1}}],["authority",{"2":{"240":1}}],["authorize",{"2":{"448":1}}],["authorizing",{"2":{"212":1}}],["authorizations",{"2":{"452":1}}],["authorization",{"0":{"214":1,"446":1,"452":1},"1":{"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":2,"454":2},"2":{"105":1,"203":1,"204":1,"209":2,"212":1,"214":7,"446":2,"448":6,"479":1}}],["author",{"2":{"109":1}}],["autoscaling",{"2":{"326":1}}],["auto",{"2":{"187":2,"217":1,"351":3,"353":1,"354":2,"355":3,"356":5,"358":2,"367":2,"368":2,"370":2,"371":1,"376":2,"377":1,"378":1,"381":1,"383":1,"490":2}}],["automates",{"2":{"474":1}}],["automated",{"2":{"474":1}}],["automation",{"2":{"326":1}}],["automatic",{"2":{"108":1,"458":1}}],["automatically",{"2":{"12":1,"114":1,"246":1,"308":1,"455":1}}],["automake",{"2":{"3":1,"4":1}}],["autotools",{"2":{"3":1,"4":1}}],["axisformat",{"2":{"26":1}}],["ago",{"2":{"312":1}}],["agentexec",{"2":{"264":1}}],["agents",{"2":{"170":1,"320":3,"322":1,"330":5}}],["agent",{"2":{"169":1,"264":1,"265":1,"287":1,"301":1,"318":2,"320":1,"324":1,"325":1,"326":2,"329":1,"330":2,"340":1}}],["agreements",{"2":{"410":1}}],["agreed",{"2":{"311":1}}],["agree",{"2":{"103":1,"216":1}}],["aggregate",{"2":{"233":1,"310":1,"312":1}}],["aggregated",{"2":{"166":2,"172":2}}],["aggregates",{"2":{"65":1}}],["aggregating",{"2":{"92":1,"186":1,"270":1}}],["aggregation",{"2":{"92":1}}],["again",{"2":{"90":1,"114":1,"128":1,"159":1,"163":1,"308":1,"313":1,"329":1}}],["against",{"2":{"23":1,"53":1,"181":1,"211":1,"213":1,"245":1,"269":1,"308":1,"326":1}}],["agnostic",{"2":{"20":1,"21":1}}],["ahead",{"2":{"23":1,"85":1,"318":1}}],["am",{"2":{"327":1,"437":1}}],["among",{"2":{"320":1}}],["amounts",{"2":{"28":1,"145":1,"163":1,"170":1}}],["amount",{"2":{"18":1,"152":1,"161":1,"163":1,"164":1,"179":1,"326":2}}],["ambiguities",{"2":{"310":1}}],["ambiguity",{"2":{"310":1}}],["ambiguously",{"2":{"249":1}}],["ambiguous",{"2":{"70":1,"310":1,"339":1}}],["amp",{"0":{"293":1},"2":{"333":1,"337":1,"458":5,"486":2,"487":2,"488":2}}],["amd64",{"2":{"22":1}}],["alphabetic",{"2":{"437":2}}],["alphanumeric",{"2":{"437":2}}],["aliases",{"2":{"494":2}}],["alive",{"0":{"361":1,"382":1},"2":{"361":1,"382":2}}],["aligned",{"2":{"349":1}}],["alignment",{"2":{"277":1}}],["align",{"2":{"277":1}}],["along",{"2":{"196":1,"276":1,"297":1,"328":1}}],["alone",{"2":{"156":1,"264":1,"313":1}}],["alerts",{"2":{"166":3}}],["alert",{"2":{"103":1}}],["alternately",{"2":{"480":1}}],["alternate",{"2":{"414":1,"441":1}}],["alternative",{"2":{"65":1,"122":1,"155":1,"324":1,"470":1}}],["alternatively",{"2":{"20":1,"21":1,"266":1,"274":1,"428":1}}],["altogether",{"2":{"329":1}}],["alt",{"2":{"240":4}}],["although",{"2":{"99":1,"194":1,"267":1,"310":1,"474":1}}],["almost",{"2":{"90":1,"308":1,"312":1,"329":1,"427":1}}],["always",{"2":{"49":1,"50":1,"53":1,"58":1,"64":1,"70":1,"72":1,"90":1,"104":1,"106":2,"108":1,"110":2,"129":1,"160":1,"179":1,"245":1,"267":1,"277":1,"280":1,"301":2,"302":1,"308":6,"399":1,"404":1,"405":1,"410":2,"412":1,"414":1,"416":1,"423":1,"477":1}}],["algorithms",{"2":{"217":1,"249":3,"313":1,"462":1}}],["algorithm",{"2":{"30":1,"314":1,"321":1,"448":4,"451":1}}],["also",{"2":{"23":1,"48":1,"50":1,"55":1,"56":3,"58":1,"66":2,"68":1,"75":1,"79":1,"80":1,"81":1,"82":1,"85":2,"87":2,"92":1,"93":1,"113":1,"122":1,"126":1,"130":1,"149":2,"157":1,"159":1,"160":1,"162":1,"163":1,"164":1,"166":2,"169":2,"180":1,"188":1,"195":1,"196":4,"209":1,"210":1,"211":1,"212":1,"214":1,"217":1,"219":1,"223":1,"234":1,"242":1,"249":2,"252":1,"255":1,"261":1,"266":2,"267":1,"268":1,"269":2,"270":1,"272":1,"282":1,"285":1,"288":1,"290":1,"308":10,"309":5,"310":1,"312":5,"313":3,"318":2,"320":1,"321":2,"326":1,"327":2,"328":1,"329":1,"330":1,"340":3,"346":1,"351":1,"405":1,"408":1,"412":1,"431":2,"432":2,"444":1,"447":1,"448":3,"451":1,"455":1,"478":1}}],["already",{"2":{"21":1,"70":1,"90":1,"91":1,"99":1,"102":1,"126":1,"149":1,"174":1,"187":1,"190":1,"196":1,"211":1,"219":1,"309":1,"310":1,"313":1,"317":2,"320":1,"326":1,"448":2,"498":1}}],["all=hpcc",{"2":{"496":1}}],["all`",{"2":{"335":1}}],["allnodes",{"2":{"325":1}}],["allman",{"2":{"222":1}}],["allocators",{"2":{"155":2}}],["allocator",{"2":{"149":2,"155":1,"156":1,"329":1}}],["allocation",{"2":{"148":2,"149":1,"152":1,"153":1,"155":1,"158":1,"329":1}}],["allocations",{"2":{"147":2,"150":3,"158":1,"160":1}}],["allocating",{"2":{"145":1,"148":1,"149":1,"158":1,"160":1}}],["allocates",{"2":{"149":2}}],["allocated",{"2":{"147":1,"149":4,"154":1,"155":1,"156":1,"161":2,"324":2,"330":1}}],["allocate",{"2":{"145":1,"147":1,"149":1,"154":1,"159":2,"160":1,"162":1,"272":1}}],["allowfilescopedelete",{"2":{"454":1}}],["allowfilescopemodify",{"2":{"454":1}}],["allowfilescopeview",{"2":{"454":1}}],["allowworkunitscopedelete",{"2":{"453":1}}],["allowworkunitscopemodify",{"2":{"453":1}}],["allowworkunitscopeview",{"2":{"453":1}}],["allowed",{"2":{"143":1,"204":1,"209":1,"308":1,"456":1,"485":1}}],["allows",{"2":{"49":1,"53":1,"58":1,"65":1,"93":1,"114":1,"130":2,"132":1,"143":1,"149":1,"160":1,"163":1,"164":2,"166":1,"174":1,"180":1,"182":1,"185":1,"186":2,"190":1,"209":1,"211":1,"213":1,"234":1,"238":1,"254":3,"255":1,"269":1,"273":1,"309":1,"313":1,"408":1,"450":1,"474":1,"490":1}}],["allow",{"2":{"48":1,"50":1,"56":1,"67":1,"77":1,"87":1,"91":1,"99":2,"100":1,"143":1,"145":2,"157":1,"164":1,"174":1,"189":1,"196":4,"247":1,"250":1,"269":1,"289":1,"308":5,"310":1,"324":1,"326":3,"327":1,"330":1,"412":1,"416":1,"455":1,"492":2}}],["allowing",{"2":{"31":1,"157":1,"186":1,"196":1,"311":1,"315":1,"423":1,"473":1}}],["all",{"0":{"321":1},"2":{"12":1,"16":1,"18":1,"20":1,"23":1,"44":1,"45":1,"49":1,"58":1,"65":1,"66":1,"73":1,"74":1,"84":1,"85":1,"89":1,"100":1,"103":3,"104":1,"105":3,"106":1,"108":2,"109":1,"110":1,"114":1,"123":1,"124":1,"128":1,"131":1,"132":1,"136":1,"139":1,"140":1,"141":2,"144":1,"145":3,"147":1,"150":1,"153":1,"161":1,"162":1,"163":1,"164":2,"168":1,"172":2,"174":3,"175":1,"176":1,"182":1,"187":2,"192":4,"193":1,"194":2,"204":1,"213":1,"216":1,"224":2,"226":1,"233":1,"236":1,"238":1,"241":1,"242":1,"243":1,"247":1,"249":3,"258":1,"266":3,"267":1,"268":1,"269":3,"270":4,"276":1,"278":1,"291":1,"299":1,"300":1,"308":4,"309":4,"310":2,"311":2,"312":2,"313":4,"318":3,"319":1,"320":3,"322":5,"323":1,"324":1,"326":2,"327":8,"329":1,"330":5,"332":1,"334":1,"335":3,"340":2,"348":2,"349":3,"386":1,"397":2,"403":2,"404":1,"406":1,"408":1,"410":2,"414":2,"419":1,"421":1,"423":1,"428":1,"432":4,"437":4,"438":4,"441":1,"443":2,"449":1,"452":1,"456":1,"461":1,"465":1,"467":1,"471":2,"473":1,"474":1,"476":2,"479":2,"481":1,"483":1,"484":1,"485":9,"490":2,"496":3}}],["a",{"0":{"21":1,"79":1,"115":1,"116":1,"241":1,"242":1,"250":1,"275":1,"277":1,"282":1,"283":1,"284":1,"285":1,"352":1,"384":1,"497":1,"498":1,"499":1},"1":{"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"18":6,"20":1,"23":4,"24":4,"26":1,"28":3,"31":1,"33":1,"36":1,"37":1,"38":2,"44":3,"45":1,"47":1,"48":1,"49":3,"50":2,"51":1,"53":15,"54":1,"55":2,"56":7,"58":8,"59":6,"60":1,"61":2,"62":9,"63":1,"64":13,"65":9,"66":5,"68":6,"69":7,"70":19,"72":5,"73":6,"74":3,"75":5,"76":5,"77":3,"79":4,"80":5,"81":6,"83":4,"84":5,"85":6,"86":1,"87":7,"88":8,"89":2,"90":4,"91":6,"92":9,"93":4,"94":2,"95":5,"96":2,"100":1,"102":3,"103":12,"104":5,"105":2,"106":21,"108":11,"109":7,"112":3,"113":1,"114":2,"115":3,"116":2,"120":3,"122":1,"123":4,"124":4,"125":1,"126":1,"128":3,"129":4,"130":1,"131":6,"132":3,"133":6,"134":1,"136":1,"139":2,"140":2,"143":9,"144":3,"145":2,"146":1,"147":1,"148":4,"149":19,"150":2,"152":2,"153":2,"154":1,"155":7,"156":3,"157":5,"158":3,"159":5,"160":9,"161":10,"162":4,"163":4,"164":3,"166":5,"167":10,"168":2,"169":3,"170":15,"172":9,"174":9,"175":4,"178":2,"179":6,"180":6,"181":3,"182":4,"183":6,"184":4,"185":2,"186":7,"187":23,"188":7,"189":5,"190":3,"191":2,"192":16,"193":8,"194":7,"195":11,"196":47,"199":2,"202":1,"203":2,"204":6,"205":1,"209":5,"211":3,"213":3,"214":3,"216":2,"217":5,"218":3,"219":1,"220":5,"221":10,"222":2,"233":5,"234":5,"239":2,"240":2,"241":4,"242":2,"245":7,"246":6,"247":2,"249":24,"250":6,"251":3,"252":3,"253":4,"254":8,"255":3,"256":4,"257":2,"258":1,"260":2,"261":2,"262":1,"264":20,"266":10,"267":7,"268":6,"269":13,"270":25,"271":2,"272":6,"273":5,"274":1,"277":3,"278":3,"279":4,"280":3,"281":1,"282":2,"283":5,"284":3,"285":5,"286":1,"287":3,"288":3,"289":4,"290":3,"291":1,"292":1,"296":1,"299":5,"301":2,"304":4,"306":2,"307":1,"308":35,"309":14,"310":24,"311":16,"312":17,"313":16,"317":4,"318":11,"319":7,"320":12,"321":9,"322":1,"323":6,"324":4,"325":7,"326":21,"327":35,"328":9,"329":5,"330":15,"333":1,"335":3,"337":28,"338":3,"339":3,"340":8,"345":2,"346":1,"347":8,"348":5,"349":8,"350":1,"360":2,"365":1,"396":1,"397":6,"399":10,"400":12,"401":9,"402":8,"403":8,"404":5,"405":5,"406":4,"407":10,"408":7,"410":2,"411":5,"412":8,"414":2,"415":5,"416":5,"419":8,"420":6,"421":13,"423":6,"424":6,"425":6,"427":8,"428":10,"431":5,"432":13,"433":1,"435":2,"436":1,"437":2,"438":12,"439":8,"440":6,"441":15,"443":4,"444":1,"445":1,"446":5,"448":18,"449":5,"450":1,"451":1,"452":4,"455":1,"460":2,"461":1,"470":1,"471":3,"473":3,"474":5,"476":9,"477":3,"478":1,"479":1,"480":2,"481":4,"482":5,"483":2,"484":1,"485":1,"487":1,"488":2,"490":2,"492":3,"494":1,"495":1,"496":12,"497":2,"498":4,"499":1}}],["artifact",{"2":{"473":1,"474":1,"475":1}}],["artificial",{"2":{"339":2}}],["articulate",{"2":{"106":1}}],["artefact",{"2":{"312":1}}],["arrow",{"0":{"466":1},"2":{"461":1,"462":1,"464":2,"467":1}}],["arrive",{"2":{"324":1}}],["arrived",{"2":{"308":1,"330":1}}],["arrays",{"2":{"160":1}}],["array",{"2":{"93":3,"158":1,"313":2,"327":1}}],["arose",{"2":{"308":1}}],["arowbuilder",{"2":{"269":1,"272":1,"275":1}}],["around",{"2":{"37":2,"108":1,"132":1,"217":1,"310":2,"431":1,"448":1}}],["armor",{"2":{"241":1}}],["arise",{"2":{"172":1}}],["arbitrary",{"2":{"69":1,"195":1,"319":1}}],["argued",{"2":{"58":1}}],["arguments",{"2":{"37":1,"58":1,"59":2,"63":3,"70":1,"187":1,"267":1,"310":3}}],["argument",{"2":{"17":1,"37":1,"59":1,"245":1,"267":1,"308":2,"455":1,"492":2}}],["args",{"2":{"38":2,"45":2}}],["arg",{"2":{"37":1}}],["architectural",{"2":{"102":1,"106":1}}],["architecture",{"0":{"27":1},"1":{"28":1,"29":1,"30":1,"31":1,"32":1},"2":{"24":1,"27":2,"172":1,"175":1}}],["archive",{"2":{"72":1}}],["archives",{"2":{"61":1}}],["archived",{"2":{"53":1}}],["arch",{"2":{"36":1}}],["area",{"2":{"172":2,"173":1,"282":1,"308":1,"318":1}}],["areas",{"2":{"106":1,"172":3,"246":1,"308":1}}],["aren",{"2":{"84":1,"479":1,"480":1}}],["are",{"0":{"319":1},"2":{"12":1,"18":1,"26":3,"31":1,"36":1,"37":2,"48":1,"49":5,"50":1,"53":2,"54":2,"56":2,"58":11,"59":6,"61":3,"62":2,"63":6,"64":4,"65":2,"66":1,"68":1,"69":9,"70":3,"72":4,"73":10,"74":4,"75":4,"76":2,"77":1,"79":2,"80":3,"81":4,"83":5,"84":3,"85":4,"87":4,"88":2,"90":3,"91":5,"92":4,"93":4,"95":4,"96":1,"99":4,"100":2,"101":1,"103":5,"104":3,"105":4,"106":5,"108":4,"109":2,"110":1,"119":1,"120":2,"122":2,"123":1,"124":2,"128":3,"130":2,"131":6,"132":1,"133":1,"138":1,"139":1,"140":5,"141":1,"143":6,"146":2,"147":2,"149":2,"150":2,"153":1,"155":2,"157":4,"158":1,"159":1,"160":1,"161":2,"162":1,"163":4,"166":1,"167":1,"169":2,"170":6,"172":4,"174":1,"175":6,"176":1,"179":1,"182":1,"183":2,"184":1,"185":1,"187":4,"188":1,"189":3,"190":4,"191":3,"192":10,"193":2,"195":2,"196":10,"197":1,"201":1,"202":1,"203":1,"204":10,"205":1,"211":2,"213":2,"214":2,"216":2,"217":1,"218":1,"219":1,"220":2,"221":4,"222":2,"223":2,"226":1,"233":3,"234":1,"238":1,"239":1,"242":4,"245":2,"246":4,"249":4,"250":3,"251":1,"252":1,"254":3,"256":5,"257":1,"258":2,"259":3,"261":3,"262":4,"264":3,"265":3,"266":6,"267":3,"268":3,"269":11,"270":10,"271":4,"272":2,"273":1,"274":1,"276":1,"277":3,"279":1,"281":2,"286":2,"287":2,"291":1,"299":1,"300":1,"308":12,"309":8,"310":18,"311":6,"312":3,"313":5,"318":5,"319":2,"320":3,"321":2,"323":4,"324":3,"326":6,"327":7,"328":2,"329":8,"330":9,"335":2,"337":3,"338":3,"339":3,"340":4,"345":1,"346":1,"347":1,"348":1,"349":3,"351":1,"353":2,"393":1,"397":1,"399":2,"400":3,"401":1,"402":1,"403":5,"404":4,"405":1,"406":1,"407":3,"408":2,"411":1,"412":6,"414":1,"415":1,"416":4,"419":2,"420":3,"421":2,"423":1,"425":3,"428":2,"431":1,"432":5,"435":1,"437":4,"438":2,"439":4,"440":1,"441":3,"442":2,"443":5,"444":1,"445":5,"446":1,"447":1,"449":3,"450":1,"452":4,"456":4,"470":1,"474":1,"477":1,"478":1,"479":3,"480":3,"482":1,"485":4,"491":1,"494":1,"495":3,"496":3}}],["anticipates",{"2":{"419":1}}],["anticipated",{"2":{"279":1}}],["animalname",{"2":{"338":1}}],["animals",{"2":{"338":1}}],["analyze",{"2":{"492":1}}],["analysed",{"2":{"328":1}}],["analysis",{"2":{"24":1,"166":2}}],["analogies",{"2":{"337":2}}],["ansi",{"2":{"222":1}}],["answers",{"2":{"339":1}}],["answer",{"2":{"106":3,"308":3,"322":1,"326":1,"335":2,"339":1,"340":12}}],["ancillary",{"2":{"210":1}}],["announces",{"2":{"347":1}}],["announcement",{"2":{"282":1,"299":1,"337":1}}],["annotate",{"2":{"308":1}}],["annotated",{"2":{"67":1}}],["annotation",{"2":{"67":2,"103":1}}],["annotations",{"0":{"67":1},"2":{"59":1,"67":1,"70":3,"73":1}}],["annoations",{"2":{"56":1}}],["anotherfoo",{"2":{"234":1}}],["another",{"2":{"56":1,"70":1,"72":1,"73":1,"77":1,"87":1,"93":1,"103":1,"145":1,"149":1,"156":1,"160":1,"170":1,"182":1,"194":1,"210":1,"219":1,"220":1,"250":1,"264":1,"266":1,"269":1,"308":2,"310":1,"319":1,"320":1,"328":1,"396":1,"431":1,"449":1,"482":1,"492":1}}],["anythisgroup",{"2":{"313":1}}],["anytime",{"2":{"179":1}}],["anyone",{"2":{"276":2}}],["anywhere",{"2":{"112":1,"190":1,"301":1,"400":1}}],["any",{"0":{"352":1},"2":{"37":2,"45":1,"53":3,"56":1,"58":2,"62":1,"63":1,"64":1,"65":2,"66":2,"69":1,"70":1,"73":1,"74":2,"75":1,"77":1,"87":1,"88":1,"103":1,"105":3,"106":4,"109":1,"112":1,"120":1,"122":1,"131":1,"133":1,"149":3,"153":2,"157":1,"158":1,"159":1,"172":3,"182":1,"187":1,"189":1,"191":1,"192":1,"201":1,"214":1,"242":1,"243":1,"245":1,"246":4,"256":1,"262":1,"266":1,"270":3,"272":1,"276":2,"278":3,"279":1,"288":1,"293":1,"294":1,"295":1,"301":1,"307":1,"308":1,"309":3,"310":1,"311":1,"312":3,"313":6,"317":1,"318":1,"319":1,"324":1,"326":3,"327":5,"329":1,"330":3,"340":2,"347":1,"352":1,"402":1,"404":1,"408":1,"411":1,"412":1,"416":1,"419":1,"421":2,"423":1,"424":2,"427":1,"432":2,"437":2,"438":1,"446":1,"460":1,"461":1,"479":1,"482":3,"494":1}}],["an",{"0":{"284":1,"498":1},"2":{"17":1,"18":1,"24":1,"27":2,"32":1,"33":2,"44":1,"45":1,"47":1,"49":2,"51":1,"54":1,"56":6,"58":2,"59":1,"62":1,"63":2,"64":1,"65":7,"66":1,"67":1,"69":4,"70":3,"72":1,"73":1,"79":1,"80":1,"82":1,"83":2,"85":1,"86":1,"88":2,"89":1,"90":6,"91":1,"92":2,"93":1,"94":1,"96":2,"103":2,"104":2,"106":7,"108":1,"112":2,"116":1,"124":1,"133":1,"135":1,"141":1,"142":1,"143":2,"148":2,"149":2,"154":1,"155":2,"160":1,"166":1,"167":2,"170":2,"172":1,"174":1,"179":2,"180":1,"186":1,"187":1,"188":1,"192":2,"193":1,"194":2,"195":1,"196":7,"199":1,"202":1,"203":1,"204":3,"210":1,"211":1,"214":2,"216":1,"219":2,"220":1,"221":1,"233":4,"234":1,"238":2,"243":1,"245":2,"249":2,"250":2,"251":2,"252":2,"254":2,"255":1,"256":3,"257":1,"264":4,"266":3,"267":4,"269":3,"270":7,"272":2,"273":1,"279":1,"284":1,"285":1,"290":2,"302":1,"307":1,"308":12,"309":1,"310":10,"311":4,"312":2,"313":3,"318":4,"319":3,"320":1,"321":3,"324":2,"326":1,"327":7,"328":4,"330":2,"333":1,"337":1,"338":3,"339":1,"340":1,"342":2,"345":1,"346":1,"348":3,"397":1,"400":3,"401":1,"403":2,"405":3,"406":2,"407":6,"408":2,"410":1,"412":1,"414":1,"419":1,"421":2,"424":1,"425":2,"427":3,"428":3,"431":2,"432":4,"433":1,"435":1,"436":4,"438":4,"439":6,"440":2,"441":10,"442":2,"443":6,"445":1,"448":4,"449":5,"450":1,"451":1,"455":1,"471":2,"472":3,"473":5,"474":3,"479":1,"480":1,"481":2,"482":3,"483":3,"484":3,"485":1,"486":2,"487":1,"488":1,"490":1,"491":2,"492":1,"496":1,"497":1}}],["and",{"0":{"35":1,"56":1,"63":1,"82":1,"164":1,"246":1,"262":1,"272":1,"277":1,"352":1,"391":1,"452":1,"455":1,"468":1},"1":{"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"453":1,"454":1,"456":1,"457":1,"458":1},"2":{"0":1,"5":1,"12":1,"13":1,"15":1,"18":7,"20":2,"21":1,"22":1,"23":1,"24":3,"25":1,"26":4,"27":6,"28":2,"29":2,"30":3,"31":3,"32":1,"33":4,"36":5,"37":1,"38":2,"43":1,"44":2,"47":1,"48":5,"49":2,"50":1,"51":3,"53":6,"54":1,"56":5,"58":9,"59":4,"60":1,"61":1,"62":2,"63":2,"64":1,"65":2,"66":3,"67":1,"68":1,"69":9,"70":8,"72":4,"73":4,"74":3,"75":3,"76":4,"77":2,"79":4,"81":3,"82":1,"83":4,"84":2,"85":3,"86":2,"87":3,"88":1,"89":2,"90":1,"91":4,"92":5,"93":1,"94":3,"95":1,"96":5,"100":4,"101":1,"102":1,"103":7,"104":2,"105":4,"106":11,"108":7,"110":4,"113":3,"114":2,"115":1,"122":1,"123":5,"124":3,"125":1,"126":1,"128":9,"129":1,"130":1,"131":3,"132":3,"133":2,"135":1,"136":1,"139":2,"140":4,"141":2,"142":1,"143":3,"144":2,"145":3,"148":1,"149":9,"150":1,"152":1,"154":1,"155":4,"156":1,"157":3,"158":1,"160":3,"161":2,"163":2,"166":8,"167":1,"169":9,"170":4,"171":1,"172":15,"174":5,"175":3,"176":2,"179":2,"180":1,"186":5,"187":12,"188":1,"189":2,"190":2,"191":5,"192":7,"193":2,"194":6,"195":3,"196":11,"198":1,"199":2,"201":1,"202":3,"203":3,"204":8,"205":1,"206":1,"212":1,"213":2,"214":3,"216":2,"217":7,"218":3,"220":4,"221":5,"222":2,"223":1,"224":1,"226":1,"233":7,"234":6,"238":4,"239":3,"240":1,"241":5,"242":9,"243":1,"245":6,"246":3,"247":2,"249":12,"250":4,"251":4,"252":2,"255":1,"256":2,"257":1,"259":1,"261":1,"262":1,"264":14,"265":1,"266":7,"267":5,"268":2,"269":6,"270":17,"271":3,"272":2,"273":1,"274":1,"276":3,"277":7,"278":9,"280":11,"281":1,"282":3,"286":1,"288":4,"289":2,"290":1,"292":1,"293":1,"294":1,"298":1,"299":4,"300":2,"301":7,"303":3,"307":2,"308":18,"309":14,"310":18,"311":6,"312":7,"313":12,"314":2,"315":1,"317":5,"318":7,"319":1,"320":4,"321":1,"322":2,"323":2,"324":4,"326":9,"327":8,"328":4,"329":4,"330":6,"331":1,"332":2,"333":2,"335":1,"336":3,"337":1,"338":4,"339":11,"340":4,"342":2,"346":1,"347":2,"348":2,"349":2,"351":1,"353":5,"364":1,"365":1,"385":1,"393":1,"394":2,"397":4,"400":4,"401":3,"402":4,"403":3,"404":1,"405":2,"406":1,"407":3,"410":2,"411":1,"412":6,"414":1,"415":1,"416":6,"419":4,"420":1,"421":2,"423":1,"425":5,"427":3,"428":5,"431":1,"432":5,"433":2,"435":3,"437":6,"438":2,"439":3,"440":2,"441":7,"443":5,"444":1,"445":2,"446":4,"447":1,"448":7,"449":4,"450":3,"451":5,"452":3,"455":2,"456":1,"461":10,"462":2,"464":2,"467":1,"469":3,"470":1,"471":3,"472":1,"473":4,"474":9,"476":1,"477":4,"478":1,"479":1,"480":4,"482":1,"483":2,"484":2,"485":3,"486":3,"490":2,"491":3,"492":2,"494":2,"495":3}}],["atoms",{"2":{"310":1}}],["atom",{"2":{"310":1}}],["atomically",{"2":{"160":1}}],["atomic",{"2":{"105":1,"154":1,"155":3,"156":1,"180":1,"234":1}}],["atmost",{"2":{"310":1}}],["att",{"2":{"256":1,"269":20,"270":1,"274":12}}],["att>",{"2":{"256":2,"269":2,"274":2}}],["attack",{"2":{"327":1}}],["attacks",{"2":{"106":1}}],["attach",{"2":{"188":1}}],["attached",{"2":{"186":1,"329":1}}],["attaches",{"2":{"186":1}}],["attempted",{"2":{"143":1}}],["attempts",{"2":{"143":1,"327":3,"435":1}}],["attempt",{"2":{"143":1,"242":1,"327":3,"339":1,"404":1,"412":1}}],["attr",{"2":{"63":2}}],["attribute",{"0":{"65":1},"2":{"59":1,"63":2,"64":1,"65":3,"74":1,"85":1,"195":1,"267":3,"269":2,"270":1,"308":1,"309":1,"310":6,"311":3,"426":1,"428":2,"439":1,"474":1,"476":1,"478":1,"479":3,"480":1,"481":1,"482":1,"485":1}}],["attributes",{"0":{"63":1,"478":1,"479":1,"480":1},"2":{"58":1,"59":1,"63":3,"65":1,"69":1,"85":2,"256":2,"310":4,"313":1,"349":6,"432":2,"449":1,"477":7,"478":1,"479":2,"480":2}}],["at",{"2":{"12":1,"18":1,"45":1,"53":1,"56":3,"58":1,"64":1,"68":1,"69":1,"73":1,"77":1,"80":1,"87":1,"99":1,"101":1,"105":1,"106":2,"131":1,"144":1,"149":1,"158":1,"169":1,"170":1,"182":1,"183":1,"194":1,"195":1,"197":1,"217":4,"249":2,"255":1,"258":1,"268":1,"269":1,"270":3,"274":1,"277":1,"286":1,"288":1,"307":1,"308":3,"310":2,"313":1,"318":1,"319":2,"321":2,"323":1,"324":1,"326":6,"327":1,"328":1,"329":1,"330":3,"335":5,"349":1,"399":3,"401":1,"405":1,"407":1,"410":1,"411":1,"414":1,"415":1,"424":1,"432":1,"437":3,"446":1,"448":1,"491":1}}],["atlas",{"2":{"7":1,"8":1}}],["asci",{"2":{"439":1}}],["ascii",{"2":{"260":2,"261":6,"274":8,"437":3,"439":1}}],["asynchronously",{"2":{"326":1}}],["aspects",{"2":{"103":1,"108":1,"327":1}}],["asks",{"2":{"419":1}}],["asking",{"2":{"339":1}}],["asked",{"2":{"106":1,"240":1,"279":1,"397":1}}],["ask",{"2":{"103":1,"106":1,"241":1,"277":1,"339":2,"340":1,"419":1,"427":1}}],["assme",{"2":{"428":1}}],["assure",{"0":{"307":1},"2":{"307":1}}],["assuming",{"2":{"128":1,"320":1,"399":1,"437":1,"447":1}}],["assumes",{"2":{"408":1,"419":2,"446":1}}],["assume",{"2":{"103":1,"128":1,"308":1,"432":2}}],["assumed",{"2":{"49":1,"59":1,"157":1,"187":1,"221":1,"419":1,"423":1,"439":1,"441":1,"442":1}}],["assumptions",{"2":{"99":1,"320":1}}],["assumption",{"2":{"68":1}}],["asset",{"0":{"243":1},"2":{"238":1,"242":2}}],["assets",{"0":{"237":1,"238":1},"1":{"238":1,"239":1,"240":1,"241":1,"242":1,"243":1},"2":{"238":4,"239":1,"242":1,"243":2}}],["assess",{"2":{"172":1}}],["assist",{"2":{"114":1,"410":1,"414":1}}],["assigning",{"2":{"412":1}}],["assign",{"2":{"91":2,"92":1}}],["assignment",{"2":{"90":1,"91":1}}],["assignments",{"2":{"83":1}}],["assigned",{"2":{"87":1,"90":1,"91":1,"184":1,"221":1,"310":1,"410":1,"414":1,"416":1,"423":2,"443":3}}],["assigns",{"2":{"75":1,"83":1,"266":1}}],["association",{"2":{"87":1}}],["associations",{"2":{"87":2}}],["associated>",{"2":{"274":2}}],["associated",{"0":{"68":1},"2":{"66":1,"80":1,"87":1,"88":2,"104":1,"108":1,"131":1,"132":1,"148":1,"149":2,"157":1,"191":1,"255":3,"258":1,"261":1,"264":3,"269":1,"272":2,"278":1,"307":1,"403":1,"406":1,"424":2,"425":1,"428":1,"435":1,"438":1,"452":2,"471":4}}],["as",{"0":{"275":1},"2":{"12":1,"23":1,"24":1,"27":2,"28":1,"31":1,"44":1,"45":1,"48":2,"50":1,"53":1,"58":3,"61":1,"64":5,"65":3,"68":2,"69":2,"70":4,"72":1,"73":1,"74":1,"77":1,"79":1,"84":1,"85":2,"86":2,"87":3,"90":2,"92":2,"93":1,"99":1,"100":1,"103":3,"106":3,"108":2,"109":1,"112":1,"113":2,"128":4,"132":1,"133":1,"140":2,"141":1,"142":1,"143":2,"144":1,"145":3,"146":2,"155":1,"157":2,"162":3,"163":2,"166":3,"169":9,"170":4,"172":6,"174":1,"176":1,"179":1,"180":1,"181":4,"182":1,"183":2,"184":2,"185":1,"186":1,"187":4,"188":2,"189":1,"190":1,"193":3,"194":1,"196":2,"200":1,"201":1,"204":2,"205":1,"209":1,"210":2,"211":1,"216":1,"217":4,"222":1,"233":2,"238":1,"243":1,"245":1,"246":1,"249":1,"250":1,"252":2,"256":2,"264":3,"266":1,"267":1,"268":1,"269":4,"270":4,"271":2,"273":1,"280":1,"284":1,"287":1,"299":4,"301":1,"302":1,"306":2,"308":10,"309":6,"310":4,"311":3,"312":2,"313":3,"317":1,"318":4,"320":2,"321":3,"323":1,"326":3,"327":4,"328":3,"329":3,"330":1,"337":1,"339":2,"340":3,"347":1,"349":4,"360":1,"365":1,"397":1,"399":2,"400":2,"401":1,"402":1,"405":1,"407":1,"410":1,"411":1,"412":2,"415":1,"416":3,"419":3,"423":1,"424":1,"425":2,"428":1,"432":1,"437":2,"439":2,"440":2,"441":1,"446":3,"448":3,"449":1,"451":4,"452":2,"461":3,"474":5,"475":1,"476":1,"477":1,"478":1,"480":2,"481":1,"482":3,"486":2,"494":1,"495":1}}],["apos",{"2":{"256":2,"269":6,"274":6,"419":2}}],["api",{"0":{"295":1,"489":1},"1":{"490":1,"491":1,"492":1},"2":{"196":1,"255":1,"257":2,"275":6,"279":3,"419":1}}],["apis",{"2":{"196":1,"233":1}}],["apiversion",{"2":{"133":1}}],["app",{"2":{"342":1}}],["appends",{"2":{"491":1}}],["append",{"2":{"357":2,"376":2}}],["appending",{"2":{"313":1}}],["appendix",{"0":{"271":1},"1":{"272":1,"273":1,"274":1,"275":1},"2":{"269":1}}],["appended",{"2":{"93":1,"196":1}}],["appearing",{"2":{"483":1,"484":1}}],["appearance",{"2":{"402":1}}],["appears",{"2":{"169":1,"204":2,"400":1}}],["appear",{"2":{"145":1,"169":1,"271":1,"301":1,"311":1,"327":1,"475":1,"480":1}}],["approximate",{"2":{"308":3}}],["approve",{"2":{"103":1}}],["approaches",{"2":{"102":1,"131":1,"308":1}}],["approach",{"2":{"84":1,"104":1,"155":1,"233":1,"266":1,"285":1,"308":1,"315":1,"337":2,"405":1}}],["appropriately",{"2":{"103":1,"115":1}}],["appropriate",{"2":{"21":1,"92":1,"105":1,"112":1,"115":1,"124":1,"170":1,"178":1,"264":1,"283":1,"296":1,"308":1,"321":1,"326":1,"327":1,"404":1,"407":1,"482":1}}],["apparent",{"2":{"62":1}}],["application",{"2":{"353":6,"369":1,"371":2,"386":4,"446":1,"473":1,"480":1,"487":1}}],["applications",{"2":{"170":1,"279":1,"339":2}}],["applicaple",{"2":{"291":1}}],["applicable",{"2":{"18":1,"196":1,"293":1,"295":1,"399":1,"401":1,"438":1,"442":1}}],["applie",{"2":{"386":1}}],["applies",{"2":{"106":1,"312":1,"327":1,"423":1,"441":1,"443":1,"477":1}}],["applied",{"2":{"69":1,"87":1,"166":1,"245":1,"308":1,"310":1,"327":1,"399":1,"402":2,"404":1,"419":3,"423":2,"431":1,"438":2,"441":1,"442":1,"482":2}}],["applyrule",{"2":{"442":1}}],["applying",{"2":{"403":1}}],["apply",{"2":{"51":1,"103":1,"108":1,"133":1,"136":1,"276":1,"309":1,"329":1,"400":1,"401":1,"424":1,"428":1,"436":2,"438":2,"441":1,"443":3}}],["apr",{"2":{"7":2,"8":2,"10":2,"11":2,"13":2}}],["apt",{"2":{"3":1,"4":1,"15":1,"16":1,"22":1,"122":3}}],["svr",{"2":{"351":7,"352":2,"353":9,"358":1,"360":2,"362":2,"366":1,"385":1}}],["svc",{"2":{"340":2}}],["svg+xml",{"2":{"353":1,"386":1}}],["svg",{"2":{"328":1,"353":1}}],["svn",{"2":{"225":1}}],["sz",{"2":{"262":1,"274":1}}],["s=",{"2":{"262":1,"274":1}}],["s3",{"2":{"194":1}}],["switched",{"2":{"323":1}}],["switches",{"2":{"270":1}}],["switching",{"2":{"196":1}}],["switch",{"2":{"194":3,"196":1,"255":2,"275":1,"312":1,"323":1,"347":1}}],["swapped",{"2":{"76":1}}],["swapping",{"2":{"76":1}}],["smcaccess",{"2":{"452":1}}],["smith",{"2":{"252":1,"266":2,"275":1,"302":1}}],["smartscreen",{"2":{"342":1}}],["smart",{"2":{"217":1,"221":2,"234":4,"321":1,"324":1}}],["smaller",{"2":{"108":1,"161":1,"270":1,"309":1}}],["small",{"2":{"83":1,"108":1,"145":1,"309":1,"312":1,"321":1}}],["smeasuresize",{"2":{"189":1}}],["smeasuretimens",{"2":{"189":1}}],["smeasurecount",{"2":{"188":2,"189":1}}],["ssn",{"2":{"403":4,"431":2}}],["sshkey",{"2":{"131":1}}],["ssh",{"2":{"131":8,"132":1,"239":1}}],["sslclient",{"2":{"385":1}}],["sslserver",{"2":{"385":1}}],["ssl",{"2":{"5":4,"204":1,"240":1,"385":1}}],["snprintf",{"2":{"355":1}}],["snippet",{"2":{"325":1,"348":1,"421":1,"441":1,"443":1}}],["snippets",{"2":{"296":1}}],["snappy",{"2":{"460":2,"467":1}}],["snapshots",{"2":{"399":2,"401":3}}],["snapshot",{"2":{"120":1,"399":2,"400":2,"403":1,"411":3,"415":3,"419":2,"420":5,"421":1,"423":3,"424":3,"428":2,"432":6}}],["snake",{"2":{"185":1}}],["snow",{"0":{"12":1}}],["squash",{"2":{"103":1}}],["sqlite3embed",{"2":{"18":1}}],["sqlite",{"2":{"7":1}}],["skews",{"2":{"309":1}}],["skew",{"2":{"308":4,"309":1,"311":1,"314":1,"315":1}}],["skeleton",{"2":{"88":1}}],["skipped",{"2":{"313":2}}],["skipping",{"2":{"12":1}}],["skip",{"2":{"59":1,"310":1,"497":1}}],["s",{"2":{"67":1,"82":1,"90":1,"103":1,"109":1,"129":1,"172":1,"174":1,"175":1,"181":1,"183":1,"187":1,"217":1,"219":2,"225":1,"233":3,"234":2,"270":1,"274":1,"276":1,"278":2,"279":1,"281":2,"283":1,"301":1,"302":1,"318":1,"319":2,"320":1,"322":2,"324":1,"325":1,"326":4,"327":5,"328":2,"329":3,"336":1,"339":1,"346":2,"350":1,"399":2,"400":2,"401":1,"403":1,"407":1,"408":1,"419":3,"423":2,"425":1,"441":1,"448":3,"451":2,"452":2,"461":2,"462":1,"473":1,"474":2,"475":1,"482":2}}],["said",{"2":{"431":1}}],["sandwich",{"2":{"337":1}}],["sasha",{"2":{"258":1,"301":1,"340":1}}],["sake",{"2":{"217":1}}],["satisfied",{"2":{"148":1,"266":1}}],["satisfy",{"2":{"50":1}}],["safer",{"2":{"496":1}}],["safely",{"2":{"128":1}}],["safety",{"2":{"105":1}}],["safe",{"2":{"83":1,"84":4,"154":1,"160":2,"234":1,"245":2,"353":1}}],["saying",{"2":{"342":1}}],["say",{"2":{"64":1,"299":2}}],["saved",{"2":{"143":2,"261":1}}],["save",{"2":{"58":1,"91":1,"335":1,"336":1,"337":1}}],["same",{"2":{"58":2,"66":2,"76":1,"85":1,"87":1,"90":1,"100":1,"103":1,"106":2,"112":1,"128":2,"139":2,"140":1,"141":1,"146":1,"147":1,"153":1,"161":1,"162":1,"163":1,"164":2,"184":1,"186":2,"192":4,"193":1,"195":1,"196":2,"202":1,"204":1,"211":1,"220":1,"233":2,"238":1,"242":1,"249":1,"266":1,"267":3,"268":2,"269":1,"270":2,"274":1,"286":1,"299":3,"308":2,"309":3,"310":1,"311":1,"317":1,"320":2,"321":1,"324":1,"326":2,"327":2,"329":1,"330":2,"340":2,"401":1,"406":1,"421":1,"440":1,"451":1,"452":1}}],["samples",{"2":{"328":1}}],["sample",{"0":{"458":1},"2":{"53":1,"168":1,"412":2,"486":1}}],["sys",{"2":{"326":2}}],["systemuser",{"2":{"204":1}}],["systembasedn",{"2":{"204":1}}],["systempassword",{"2":{"140":1,"204":2}}],["systemcommonname",{"2":{"140":1,"204":2}}],["system",{"0":{"121":1,"123":1,"125":1,"129":1,"199":1,"285":1},"1":{"122":1,"123":1,"126":1,"127":1,"128":1},"2":{"12":1,"13":1,"15":1,"16":1,"20":1,"21":2,"23":2,"27":1,"28":2,"29":1,"33":1,"54":1,"58":1,"83":1,"102":1,"104":1,"109":3,"119":1,"120":1,"123":3,"124":3,"129":5,"131":1,"147":1,"160":1,"162":1,"163":3,"167":3,"169":2,"172":4,"175":4,"186":1,"189":1,"196":14,"197":1,"198":1,"203":1,"204":4,"205":1,"245":1,"246":1,"247":2,"249":5,"251":1,"254":1,"264":1,"265":2,"271":1,"278":1,"300":1,"308":7,"309":1,"310":1,"311":1,"340":2,"348":1,"397":1,"447":1,"451":1,"455":2,"480":1}}],["systems®",{"0":{"301":1},"2":{"298":1,"300":1,"301":2,"302":1,"452":1}}],["systems",{"0":{"17":1,"276":1,"451":1,"452":1},"1":{"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"453":1,"454":1},"2":{"0":1,"15":1,"17":1,"21":1,"24":2,"27":1,"107":1,"129":1,"166":1,"172":1,"175":2,"181":1,"184":1,"192":1,"193":1,"217":5,"238":3,"243":2,"300":1,"301":1,"317":1,"320":1,"324":2,"326":2,"327":1,"330":3,"331":1,"339":1,"340":4,"446":5,"448":3,"449":1,"451":2,"460":1,"461":1,"495":8}}],["sytems",{"2":{"326":1}}],["synthesize",{"2":{"432":1}}],["syntax",{"0":{"475":1},"1":{"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"484":1},"2":{"30":1,"72":1,"73":1,"74":1,"100":2,"196":1,"245":1,"308":2,"309":2,"310":10,"438":1}}],["sync",{"2":{"330":2}}],["synchronization",{"2":{"172":1}}],["synchronized",{"2":{"155":1,"174":1}}],["symbols",{"2":{"70":1,"73":2,"85":1,"247":6}}],["symbol",{"2":{"61":1,"67":1,"72":1,"85":1}}],["spot",{"2":{"308":1,"328":2}}],["spotted",{"2":{"106":1}}],["spotting",{"2":{"70":1}}],["sprays",{"2":{"265":1}}],["sprayed",{"2":{"196":1}}],["spraying",{"2":{"196":1}}],["spray",{"2":{"196":6}}],["spread",{"2":{"28":1}}],["spilled",{"2":{"269":1}}],["spill",{"2":{"157":1,"159":2,"269":1,"315":1}}],["spilling",{"0":{"157":1},"1":{"158":1,"159":1,"160":1,"161":1},"2":{"157":1,"158":1}}],["spin",{"2":{"149":1}}],["span>",{"2":{"355":1}}],["span",{"2":{"355":1}}],["spanmultiplecpp>",{"2":{"259":1,"274":1}}],["spanmultiplecpp>0",{"2":{"259":1,"274":1}}],["spark",{"2":{"194":1,"338":1,"495":1}}],["spare",{"2":{"149":2}}],["spaces",{"2":{"108":1,"222":1,"349":1}}],["space",{"2":{"65":1,"146":1,"147":2,"149":1,"158":1,"163":1,"172":1}}],["speaking",{"2":{"299":1,"452":1}}],["speeds",{"2":{"469":1}}],["speed",{"2":{"145":1,"163":1}}],["spelling",{"2":{"299":1}}],["spell",{"2":{"106":1}}],["specs",{"2":{"55":1,"446":1,"449":4}}],["specitic",{"0":{"384":1}}],["specifier",{"2":{"267":1}}],["specifies",{"2":{"195":1}}],["specified",{"2":{"55":1,"63":1,"153":1,"190":1,"192":1,"214":1,"308":1,"309":1,"310":1,"327":1,"340":1,"348":1,"353":1,"407":1,"485":2,"491":1}}],["specifiying",{"2":{"54":1}}],["specification",{"0":{"449":1},"2":{"284":1,"308":1,"446":4,"448":2,"482":1}}],["specifications",{"2":{"279":1}}],["specifically",{"2":{"197":1,"431":1,"432":1,"452":1}}],["specifics",{"2":{"172":1}}],["specific",{"0":{"338":1,"479":1},"2":{"18":1,"81":1,"108":1,"112":2,"129":1,"169":1,"170":1,"172":1,"174":1,"183":1,"195":1,"196":1,"209":2,"214":1,"217":1,"269":1,"278":1,"300":2,"327":5,"337":4,"338":1,"339":3,"348":1,"407":3,"420":1,"427":1,"435":2,"438":2,"441":1,"449":1,"452":1,"464":1,"477":3,"479":2,"481":1,"492":1}}],["specifying",{"2":{"339":1,"345":1,"441":1,"479":1}}],["specify",{"2":{"19":1,"20":1,"56":1,"148":1,"157":1,"196":1,"278":1,"327":1,"340":1,"347":1,"432":1,"482":1,"492":1}}],["specialized",{"2":{"150":1,"311":1}}],["specialised",{"0":{"151":1,"267":1},"1":{"152":1,"153":1,"154":1,"155":1,"156":1,"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1},"2":{"59":1,"100":1,"266":1,"269":1,"311":2}}],["special",{"0":{"396":1},"2":{"18":1,"68":1,"88":1,"132":1,"190":1,"193":1,"196":1,"212":1,"441":1,"486":1,"487":1,"488":1}}],["spurious",{"2":{"105":1}}],["splitters",{"2":{"319":1}}],["splitter",{"2":{"319":5}}],["splitting",{"2":{"254":1}}],["splittype",{"2":{"191":1}}],["splits",{"2":{"249":1,"309":1}}],["split",{"0":{"391":1},"2":{"84":1,"105":1,"106":1,"184":1,"191":2,"192":3,"195":1,"196":3,"249":1,"308":7,"309":1,"311":1,"313":1,"329":1,"330":1,"391":1}}],["scw",{"2":{"327":1}}],["scoring",{"2":{"309":1}}],["score",{"2":{"308":4,"310":1,"314":1}}],["scop",{"2":{"185":1}}],["scope=",{"2":{"262":1,"274":1}}],["scope",{"0":{"74":1,"453":1,"454":1},"2":{"58":1,"74":1,"85":1,"103":1,"106":3,"108":1,"158":1,"185":3,"209":1,"221":1,"222":1,"319":1,"327":1,"339":1,"449":1,"451":3,"452":3,"453":6,"454":6}}],["scopes",{"2":{"51":1,"186":1,"327":1,"451":1}}],["scratch",{"2":{"310":1}}],["screenshots",{"2":{"278":1,"280":1,"296":1}}],["screen",{"2":{"239":1}}],["script",{"2":{"36":3,"53":7,"123":1,"128":2,"133":1,"279":1,"328":3,"431":1,"474":1,"486":4,"487":4,"488":4,"490":2}}],["scripts>",{"2":{"474":4,"486":2,"487":10,"488":10}}],["scripts",{"0":{"483":1},"2":{"20":1,"33":1,"45":1,"123":1,"128":1,"473":1,"474":3,"477":1,"482":2,"483":12,"486":1,"494":2,"495":1}}],["scenes",{"2":{"163":1}}],["scenarios",{"0":{"168":1},"1":{"169":1,"170":1,"171":1},"2":{"196":1,"461":1}}],["scenario",{"2":{"131":1,"278":1}}],["scarcer",{"2":{"323":1}}],["scattered",{"2":{"161":1}}],["scanned",{"2":{"155":1}}],["scanning",{"0":{"155":1},"2":{"155":4,"156":1}}],["scaling",{"2":{"106":1,"166":1,"170":1,"172":1,"182":1,"322":1}}],["scalar",{"0":{"91":1},"2":{"59":1,"75":2,"96":1,"172":1,"174":1,"187":1,"266":1}}],["scaled",{"0":{"182":1},"2":{"182":2}}],["scales",{"2":{"28":1,"29":1}}],["scale",{"2":{"28":1,"58":1,"182":1}}],["scm",{"2":{"131":1}}],["scheduling",{"2":{"254":1}}],["scheduler",{"2":{"266":1}}],["schedule",{"2":{"26":1,"254":1,"266":1,"274":1}}],["scheme",{"2":{"144":1,"218":1,"219":1,"242":1,"367":1,"448":2}}],["schemas",{"2":{"462":1,"486":4,"487":4,"488":4}}],["schemaraw>",{"2":{"260":1,"261":2,"274":3}}],["schemaraw",{"2":{"260":1,"261":2,"274":3}}],["schema",{"2":{"82":1,"260":1,"460":3,"461":1,"462":1,"469":1,"479":1}}],["shutdown",{"2":{"366":2}}],["shift",{"2":{"310":3}}],["sha256",{"2":{"240":3}}],["shall",{"2":{"172":1,"174":1,"179":3,"184":1,"185":3}}],["share",{"2":{"174":1,"218":1,"338":1}}],["shareable",{"2":{"172":1}}],["shared",{"0":{"162":1},"2":{"5":1,"31":1,"44":1,"75":1,"79":3,"153":2,"162":1,"164":2,"183":1,"187":7,"188":4,"205":1,"217":2,"218":1,"221":6,"234":6,"236":1,"249":1,"254":1,"264":1,"273":1,"320":1,"321":1,"327":5,"349":1,"397":1,"406":1,"444":1}}],["sharing",{"2":{"49":1,"221":1,"314":1}}],["shnpm",{"2":{"114":1}}],["shrunk",{"2":{"62":1,"150":1}}],["shell",{"2":{"53":1}}],["sh",{"2":{"36":3,"53":2,"56":1,"128":3,"494":4,"496":3,"497":2,"498":1,"499":1}}],["shcd",{"2":{"34":1}}],["shot",{"2":{"328":1}}],["showing",{"2":{"416":1,"474":1}}],["shows",{"2":{"412":1,"421":1,"465":1}}],["showpaths",{"2":{"346":1}}],["showrecordcountingraph>",{"2":{"259":1,"274":1}}],["showrecordcountingraph>1",{"2":{"259":1,"274":1}}],["showmetaingraph>",{"2":{"259":1,"274":1}}],["showmetaingraph>1",{"2":{"259":1,"274":1}}],["show",{"2":{"128":1,"243":1,"249":1,"277":1,"309":1,"337":1,"340":1}}],["shown",{"2":{"23":1,"183":1,"279":1,"287":1,"486":1}}],["shorter",{"2":{"327":1}}],["shortcuts",{"2":{"278":1}}],["shortcomings",{"2":{"58":1}}],["short",{"2":{"66":1,"70":2,"143":1,"282":1,"299":2,"320":1,"337":4,"448":1}}],["shouldn",{"2":{"70":1}}],["should",{"0":{"79":2,"281":1},"1":{"282":1,"283":1,"284":1,"285":1,"286":1},"2":{"19":1,"23":2,"44":1,"50":1,"53":2,"58":1,"61":1,"63":1,"65":1,"67":1,"69":1,"73":1,"83":2,"100":2,"102":1,"103":13,"104":2,"105":1,"106":8,"108":10,"109":8,"110":2,"115":1,"116":1,"123":2,"128":4,"149":1,"166":1,"169":1,"170":1,"183":1,"191":1,"192":4,"193":1,"196":5,"209":2,"218":3,"219":1,"220":1,"221":4,"222":1,"223":1,"224":2,"225":1,"233":2,"241":1,"245":3,"249":2,"251":1,"266":2,"269":2,"270":1,"273":1,"276":1,"279":2,"282":1,"283":3,"284":1,"286":1,"287":1,"288":1,"290":3,"299":2,"301":2,"308":13,"309":5,"310":1,"311":1,"312":2,"313":3,"318":3,"320":1,"322":2,"326":4,"327":2,"328":2,"330":1,"335":1,"340":1,"349":2,"381":2,"385":1,"387":1,"388":1,"399":1,"410":5,"414":3,"419":1,"425":1,"427":1,"428":1,"436":1,"438":2,"441":1,"448":1,"449":1,"451":2,"460":1,"476":1,"480":1,"495":1,"496":1}}],["shlibdeps=off",{"2":{"18":1}}],["shlibdeps",{"2":{"18":1}}],["sibline",{"2":{"427":1}}],["sibling",{"2":{"400":1,"427":1}}],["situation",{"2":{"310":1,"313":1}}],["situations",{"2":{"63":1,"64":1,"65":1,"196":1,"254":1,"308":2,"309":1,"330":1,"397":1,"410":2,"414":1}}],["six",{"2":{"291":1}}],["silly",{"2":{"105":1,"108":2}}],["sign",{"2":{"239":2,"240":7}}],["signficant",{"2":{"221":1}}],["signature",{"2":{"211":3,"448":2,"451":1}}],["signatures",{"2":{"192":1,"211":1}}],["signed",{"2":{"95":1,"191":2,"211":1,"239":1,"240":2,"448":1,"451":1}}],["significant",{"2":{"84":1,"85":1,"103":1,"104":1,"106":2,"153":1,"158":1,"161":1,"163":1,"234":1,"245":1,"321":1,"474":1}}],["significantly",{"2":{"66":1,"67":1,"163":2,"280":1}}],["signing",{"0":{"240":1,"241":1},"2":{"18":1,"211":1,"239":5,"240":3,"241":2}}],["similarities",{"2":{"337":2}}],["similarly",{"2":{"74":1,"91":1,"106":1,"109":1,"299":1,"308":1,"310":1,"439":1}}],["similar",{"2":{"66":1,"92":1,"93":1,"95":1,"131":1,"134":1,"187":1,"192":1,"233":1,"268":1,"269":1,"279":1,"310":2,"312":2,"414":1}}],["simplified",{"2":{"270":1,"474":2}}],["simplifies",{"2":{"67":1,"75":1,"312":1}}],["simplification",{"2":{"270":1}}],["simplifying",{"2":{"75":1,"194":1}}],["simplify",{"2":{"75":1,"76":1,"103":1,"310":1,"492":1}}],["simplest",{"2":{"131":1,"192":1,"254":1,"308":1,"313":1,"325":1,"407":1}}],["simpler",{"2":{"106":1,"245":1,"441":1}}],["simple",{"2":{"37":2,"49":1,"87":1,"88":1,"92":2,"103":1,"106":1,"108":1,"109":1,"113":1,"172":2,"174":1,"193":1,"213":1,"245":8,"249":1,"252":1,"256":1,"270":1,"279":1,"280":1,"282":1,"287":1,"299":2,"308":1,"310":1,"312":2,"313":1,"317":1,"336":1,"337":3,"396":1,"446":1,"452":1}}],["simply",{"2":{"17":1,"123":2,"172":1,"180":1,"187":1,"243":1,"299":1,"324":1}}],["singularly",{"2":{"212":1}}],["singular",{"2":{"185":1}}],["singly",{"2":{"149":1,"155":1}}],["singlepartnosuffix",{"2":{"191":1}}],["singleton",{"2":{"187":2}}],["single",{"0":{"207":1,"213":1,"275":1},"2":{"24":3,"28":2,"53":1,"58":1,"76":1,"89":1,"92":1,"108":1,"125":1,"145":1,"146":1,"147":1,"148":1,"149":1,"154":1,"161":1,"164":1,"175":1,"186":1,"187":1,"191":1,"195":2,"196":2,"213":2,"218":2,"221":1,"249":1,"250":1,"256":2,"260":2,"270":1,"271":1,"273":1,"309":1,"310":1,"311":2,"313":3,"317":2,"321":2,"325":1,"327":1,"350":1,"399":1,"400":1,"401":1,"419":2,"432":2,"441":2,"443":1,"460":1}}],["sink",{"2":{"172":2,"175":3,"179":1,"181":1,"182":1,"183":11,"186":1,"189":2,"270":3,"358":4,"359":5,"377":2}}],["sinks",{"0":{"175":1},"2":{"172":6,"175":4,"176":2,"183":6,"187":1,"318":1}}],["since",{"2":{"64":1,"74":1,"108":1,"128":1,"148":1,"156":1,"157":1,"161":1,"170":2,"183":1,"196":2,"214":1,"221":1,"268":1,"284":1,"285":1,"309":1,"310":1,"312":1,"326":2,"330":1,"393":1,"481":1}}],["sizeof",{"2":{"355":1}}],["sized",{"2":{"308":1}}],["size32",{"2":{"269":2,"275":7}}],["sizepeakmemory",{"2":{"262":1,"274":1}}],["sizes",{"2":{"145":1,"148":1,"192":1,"195":1,"462":1,"467":1}}],["size",{"2":{"58":1,"62":1,"69":1,"81":1,"89":1,"105":1,"143":2,"146":1,"148":3,"149":2,"152":3,"153":2,"189":1,"191":6,"193":7,"247":1,"269":2,"270":1,"289":1,"311":1,"313":1,"326":5,"330":2,"338":1,"356":2,"357":2,"358":8,"359":1,"366":1,"376":2,"377":3,"418":6,"422":2,"426":2,"428":2,"460":2,"462":1,"466":1}}],["sides",{"2":{"330":1}}],["sidebar",{"0":{"116":1},"2":{"116":3}}],["side",{"0":{"68":1},"2":{"49":1,"69":1,"239":1,"329":1,"441":1}}],["session",{"2":{"448":3}}],["sessions",{"2":{"308":1}}],["se",{"2":{"402":1}}],["separator",{"2":{"349":2}}],["separately",{"2":{"186":1,"189":1,"229":1,"269":1,"323":1,"330":1,"473":1}}],["separates",{"2":{"181":1}}],["separated",{"2":{"139":1,"185":1,"203":1,"204":1}}],["separate",{"2":{"18":1,"29":1,"95":1,"100":1,"106":1,"108":1,"123":1,"148":1,"184":1,"194":1,"205":1,"209":1,"214":1,"233":1,"252":1,"254":1,"270":1,"289":2,"324":1,"399":1}}],["segfaults",{"2":{"326":2}}],["segmentation",{"2":{"221":1}}],["sequentially",{"2":{"425":1}}],["sequential",{"2":{"266":1,"267":2,"399":1,"425":2}}],["sequence=",{"2":{"261":2,"274":2}}],["sequence",{"2":{"56":2,"64":1,"161":1,"330":2,"436":1,"439":2}}],["searching",{"2":{"310":1,"321":3}}],["searchname",{"2":{"252":4,"260":3,"266":2,"269":2,"274":3,"275":3}}],["search",{"2":{"204":1,"252":2,"308":1,"321":3,"349":3,"353":2,"425":1,"485":1,"486":2,"487":2,"488":2}}],["searched",{"2":{"122":1,"143":1,"269":1}}],["sensitive",{"2":{"397":2,"439":2,"474":1}}],["sense",{"2":{"108":1,"112":1,"221":1,"233":1,"287":1,"326":1,"441":1}}],["sends",{"2":{"360":1}}],["sender",{"2":{"330":1}}],["send",{"0":{"358":1,"377":1},"2":{"318":1,"320":1,"322":1,"330":5,"338":1,"360":2,"449":1}}],["sending",{"0":{"330":1},"2":{"317":1,"330":4,"490":1}}],["sentences",{"2":{"299":2}}],["sentence",{"2":{"299":1}}],["sent",{"0":{"364":1},"2":{"196":2,"211":1,"320":3,"330":7}}],["semaphore",{"2":{"327":1}}],["semantic",{"2":{"72":2,"100":4,"219":1}}],["semantics",{"2":{"64":1,"245":1,"308":1}}],["semi",{"2":{"108":1}}],["severity",{"2":{"106":1}}],["several",{"2":{"48":1,"53":2,"58":1,"61":1,"64":1,"69":1,"72":1,"91":1,"99":1,"108":1,"145":1,"158":1,"166":1,"239":1,"241":1,"250":1,"258":1,"265":1,"267":1,"309":1,"311":1,"312":1,"321":1,"448":1,"449":1,"451":1,"471":1}}],["serially",{"2":{"442":1}}],["serial",{"2":{"425":2}}],["serializexml",{"2":{"275":1}}],["serializer",{"2":{"270":1}}],["serializes",{"2":{"192":1}}],["serialized",{"2":{"93":1,"145":1,"191":2,"192":4,"196":1,"249":1,"261":1,"329":2}}],["serialize",{"2":{"89":1,"311":1}}],["series",{"2":{"308":4,"328":1}}],["serious",{"2":{"245":3}}],["servicebinding>",{"2":{"474":1}}],["servicebinding",{"0":{"477":1},"1":{"478":1,"479":1,"480":1},"2":{"474":2,"477":2,"483":1,"484":1}}],["servicequery",{"2":{"340":4}}],["service",{"0":{"479":1},"2":{"124":2,"132":1,"136":1,"170":2,"191":1,"192":1,"194":1,"196":6,"267":1,"325":1,"335":1,"446":2,"448":7,"449":5,"450":2,"471":5,"477":1,"478":2,"479":5,"480":3,"487":1,"488":1,"490":1}}],["services",{"2":{"27":1,"32":1,"124":1,"125":1,"126":1,"196":2,"301":1,"349":1,"450":1,"471":2,"473":1,"490":6}}],["servers",{"2":{"322":1,"330":1}}],["servertype",{"2":{"204":2}}],["serverless",{"2":{"196":1}}],["server=",{"2":{"130":1}}],["server",{"0":{"141":1,"196":1,"322":1,"351":1,"353":1,"364":1,"380":1},"1":{"352":1,"353":1,"354":1,"355":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1},"2":{"114":5,"183":1,"202":1,"204":2,"240":9,"301":6,"322":1,"325":3,"327":1,"329":2,"330":5,"351":1,"353":1,"360":1,"364":1,"385":1,"386":1,"451":1,"491":2}}],["serve",{"2":{"29":1,"201":1}}],["self",{"2":{"126":1,"153":1,"223":1,"239":1,"240":2,"275":2,"312":1,"345":1,"451":1}}],["selftest",{"2":{"126":2}}],["selseq",{"2":{"64":2,"66":1}}],["select=",{"2":{"486":4,"487":4,"488":4}}],["selective",{"2":{"492":1}}],["selectinterface",{"2":{"311":1}}],["selecting",{"2":{"167":1,"310":1,"411":2,"415":2}}],["selection",{"2":{"53":1,"65":2,"412":1}}],["select",{"2":{"64":1,"65":3,"66":1,"93":1,"129":1,"196":1,"241":2,"243":1,"299":1,"304":1,"308":1,"310":1,"335":4,"402":2,"410":2,"411":1,"414":2,"415":3,"432":1,"441":1,"451":1}}],["selectors",{"2":{"65":1}}],["selector",{"2":{"64":8,"66":2,"87":1}}],["selected",{"2":{"64":1,"76":1,"129":1,"163":1,"242":1,"308":1,"410":1,"412":6,"414":2,"416":4,"419":2,"420":1,"421":13,"423":3,"424":2,"441":1,"443":1}}],["selects",{"0":{"66":1},"2":{"53":1,"65":1,"74":1,"299":1}}],["set5",{"2":{"412":2}}],["set4",{"2":{"412":2}}],["set2",{"2":{"412":6,"416":2}}],["set1",{"2":{"412":6,"416":5,"421":8}}],["setproperty",{"2":{"408":2,"409":1,"413":1}}],["setown",{"2":{"269":1,"275":1}}],["set3",{"2":{"269":4,"275":4,"412":6}}],["setfoo",{"2":{"221":2}}],["setter",{"2":{"220":1}}],["settotalmemorylimit",{"2":{"148":1}}],["setting2",{"2":{"183":1}}],["setting1",{"2":{"183":1}}],["setting",{"0":{"282":1},"2":{"54":1,"56":1,"94":1,"182":1,"183":2,"204":1,"270":1,"282":3,"329":1,"365":1,"448":1,"476":1}}],["settings",{"0":{"43":1},"2":{"36":1,"55":1,"183":3,"206":1,"239":2,"279":1,"335":1,"479":1,"494":1}}],["sets",{"0":{"409":1,"413":1},"1":{"410":1,"411":1,"412":1,"414":1,"415":1,"416":1},"2":{"100":2,"266":1,"410":1,"412":5,"414":2,"416":3,"421":1,"431":1,"441":1,"443":1,"464":1}}],["set",{"0":{"363":1},"2":{"36":1,"37":1,"38":2,"43":2,"44":2,"45":3,"53":2,"54":1,"81":1,"103":1,"109":1,"120":2,"140":1,"141":1,"149":3,"161":1,"169":2,"170":1,"172":1,"174":1,"181":1,"183":1,"184":1,"196":1,"204":1,"213":1,"221":1,"264":3,"276":1,"293":1,"308":4,"309":1,"312":1,"324":1,"326":1,"328":3,"329":1,"330":4,"335":2,"340":3,"348":1,"351":3,"353":7,"354":1,"355":2,"357":1,"358":2,"359":1,"360":2,"361":1,"362":3,"363":1,"365":1,"368":1,"375":3,"379":3,"380":4,"382":2,"383":1,"384":1,"385":1,"389":1,"390":1,"397":1,"399":2,"401":1,"402":2,"410":6,"411":8,"412":24,"414":9,"415":8,"416":18,"421":8,"431":1,"432":9,"435":1,"438":5,"443":4,"451":1,"452":1,"480":5,"486":4,"487":4,"488":4,"496":2}}],["setupcommand",{"2":{"460":1}}],["setup",{"0":{"293":1},"2":{"15":1,"125":1,"278":1,"350":1,"477":1,"478":1,"494":1}}],["sec",{"2":{"458":5,"460":16}}],["secmgrplugin",{"2":{"451":1}}],["secret",{"2":{"132":1,"133":7,"140":1,"190":4,"192":1,"239":3,"240":1,"241":4,"448":1,"449":1}}],["secrets",{"2":{"105":1,"133":2,"134":2,"140":1,"192":2,"196":1,"204":2,"239":4,"241":1,"451":2}}],["secure",{"2":{"131":1,"196":1,"204":2}}],["securely",{"2":{"105":1,"132":1}}],["security",{"0":{"135":1,"201":1,"203":1,"205":1,"206":1,"207":1,"208":1,"210":1,"446":1},"1":{"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"144":1,"202":1,"203":1,"204":2,"205":2,"206":3,"207":3,"208":3,"211":1,"212":1,"213":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1},"2":{"26":9,"102":1,"104":1,"106":3,"110":1,"135":2,"136":2,"139":1,"140":1,"141":2,"143":1,"192":1,"196":2,"201":2,"202":1,"203":4,"204":1,"205":2,"209":2,"210":2,"211":3,"213":1,"239":1,"244":1,"245":6,"246":3,"403":1,"441":1,"447":1,"448":2,"451":2}}],["seconds",{"2":{"327":1,"362":2,"375":2,"448":1}}],["secondly",{"2":{"326":2}}],["secondary",{"2":{"320":1}}],["second",{"2":{"29":1,"64":2,"65":1,"66":1,"103":1,"184":1,"193":2,"254":1,"267":2,"310":1,"313":1,"416":1,"479":1}}],["sections",{"2":{"27":1,"87":2,"105":1,"137":1,"149":1,"166":1,"173":1,"177":1,"195":1,"203":1,"210":1,"253":1,"254":1,"291":1,"471":1,"477":1,"482":1,"486":1}}],["section",{"2":{"26":7,"80":1,"87":2,"109":1,"116":2,"168":1,"172":1,"183":1,"184":1,"186":1,"195":1,"210":1,"219":1,"234":1,"242":1,"253":1,"256":1,"259":1,"283":1,"292":1,"298":1,"408":1,"433":1,"444":1,"481":1,"482":3,"483":1,"484":1}}],["sees",{"2":{"449":1}}],["seeing",{"2":{"397":1}}],["seen",{"2":{"327":1,"328":1,"399":1}}],["seems",{"2":{"327":2}}],["seem",{"2":{"308":1,"324":1}}],["seeps",{"2":{"169":1}}],["seeks",{"2":{"169":1}}],["see",{"2":{"22":1,"45":1,"49":1,"53":1,"56":2,"59":2,"64":1,"67":1,"68":1,"69":1,"85":1,"102":1,"108":1,"114":1,"120":1,"124":1,"128":2,"131":2,"136":1,"140":1,"141":1,"143":1,"149":1,"150":1,"190":1,"197":1,"206":1,"211":1,"212":1,"217":1,"219":1,"225":1,"233":1,"277":1,"280":1,"308":2,"310":1,"313":1,"314":1,"331":1,"335":1,"349":3,"364":1,"388":1,"397":1,"448":1,"449":2,"460":1,"477":1,"482":2}}],["stye",{"2":{"420":1}}],["style=",{"2":{"355":1}}],["styleguide",{"2":{"225":1}}],["stylesheet>",{"2":{"486":1,"487":1,"488":1}}],["stylesheet",{"2":{"486":1,"487":1,"488":1}}],["styles",{"2":{"104":1,"216":1,"404":3,"431":1,"441":1,"443":2,"445":1}}],["style",{"0":{"219":1,"298":1,"349":1,"404":1},"1":{"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1},"2":{"102":1,"104":1,"105":2,"106":6,"196":1,"198":4,"216":2,"217":1,"222":1,"225":1,"229":1,"249":1,"280":1,"300":1,"307":1,"349":2,"404":2,"419":2,"420":2,"425":1,"428":3,"431":1,"432":1,"436":5,"441":1,"443":2}}],["studies",{"2":{"339":1}}],["studio",{"2":{"20":1,"113":1,"123":3,"129":1}}],["st",{"2":{"240":1}}],["stl",{"0":{"235":1},"2":{"217":1,"236":1}}],["std",{"2":{"187":1,"188":4,"357":1,"358":3,"365":1,"366":1,"367":2}}],["stdout",{"2":{"56":1,"346":1}}],["still",{"2":{"161":1,"196":6,"204":1,"217":1,"233":1,"319":1,"326":1,"327":2,"329":1,"358":1}}],["steady",{"2":{"143":1}}],["stepping",{"2":{"320":1,"321":1}}],["steps",{"0":{"137":1},"1":{"138":1,"139":1,"140":1,"141":1},"2":{"128":1,"133":1,"134":1,"135":1,"137":1,"187":1,"199":1,"240":1,"293":1,"299":1,"306":1,"337":2,"410":1}}],["step",{"2":{"17":2,"114":1,"128":4,"196":1,"238":1,"249":1,"270":1,"278":2,"296":2,"299":2,"309":1,"320":1,"328":1,"337":4,"497":1}}],["stone",{"2":{"281":1}}],["stopping",{"2":{"327":1}}],["stopped",{"2":{"318":1,"319":2}}],["stop",{"2":{"106":2,"267":1,"319":1,"351":2}}],["storageplane",{"2":{"194":1,"196":1}}],["storage",{"0":{"190":1},"2":{"131":1,"181":1,"190":3,"191":2,"192":2,"193":6,"194":1,"195":1,"196":14,"288":1,"340":2}}],["stores",{"2":{"157":1,"267":2}}],["store",{"2":{"95":1,"134":1,"135":1,"153":1,"155":1,"160":1,"163":1,"167":1,"196":1,"204":1,"251":1,"267":1,"321":1,"470":1,"473":1}}],["stored",{"2":{"58":1,"69":2,"79":1,"80":1,"81":1,"82":1,"93":1,"95":1,"105":1,"132":1,"139":2,"140":1,"149":1,"191":2,"193":2,"196":1,"204":5,"211":1,"212":1,"213":1,"250":2,"252":1,"254":2,"258":1,"260":1,"262":2,"267":2,"269":2,"274":1,"311":1,"451":1}}],["storing",{"2":{"27":1,"36":2}}],["stress",{"2":{"461":1}}],["streamlines",{"2":{"280":1}}],["streamline",{"2":{"276":1,"336":1}}],["stream",{"2":{"194":1,"196":1,"358":2,"371":1,"376":1,"377":1,"386":1}}],["streams",{"2":{"158":1}}],["struct",{"2":{"219":1,"257":1,"275":4}}],["structures",{"0":{"87":1},"2":{"84":1,"87":1,"490":1}}],["structured",{"2":{"56":1,"84":1,"249":1,"400":3,"427":1,"492":1}}],["structure",{"0":{"35":1,"36":1,"146":1,"236":1,"269":1,"332":1,"347":1},"1":{"36":1,"37":2,"38":2,"39":2,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"154":1,"155":1,"156":1},"2":{"33":3,"58":1,"87":2,"112":1,"117":1,"191":1,"192":1,"195":2,"256":1,"267":2,"269":1,"280":1,"312":1,"315":1,"338":2,"349":1,"427":1}}],["strange",{"2":{"309":1}}],["strands",{"2":{"192":2}}],["straightforward",{"2":{"280":1}}],["strategy",{"2":{"166":2}}],["strive",{"2":{"299":1}}],["strings",{"2":{"490":1}}],["string80",{"2":{"252":1,"261":1,"269":1,"274":2}}],["string40",{"2":{"252":1,"261":1,"269":1,"274":2}}],["string",{"2":{"189":2,"252":1,"260":1,"261":1,"270":2,"349":3,"357":1,"358":1,"367":1,"376":2,"377":1,"407":1,"424":1,"452":1,"460":2,"462":2,"464":1,"466":1,"476":2,"478":1,"479":3,"480":6,"486":2,"487":2}}],["stringdata",{"2":{"133":1}}],["string=debug",{"2":{"18":1}}],["strict",{"2":{"104":1}}],["strictness",{"0":{"104":1},"2":{"104":1}}],["strongly",{"2":{"53":1,"277":1}}],["strong",{"2":{"48":1,"221":1,"245":1}}],["standalone",{"2":{"325":1,"326":2}}],["standardized",{"2":{"431":1}}],["standards",{"2":{"32":1,"102":1,"216":1}}],["standard",{"0":{"40":1,"283":1,"349":1,"478":1},"1":{"41":1,"42":1,"43":1,"44":1},"2":{"18":1,"75":1,"85":1,"106":1,"123":1,"187":1,"217":1,"221":1,"283":1,"330":1,"349":2,"435":1,"438":1,"477":1,"478":1}}],["stand",{"2":{"264":1,"313":1}}],["stability",{"2":{"217":1}}],["stable",{"2":{"217":1,"308":2}}],["stacks",{"2":{"328":3}}],["stack",{"2":{"85":1,"247":1,"328":9,"340":1}}],["stat",{"2":{"326":1}}],["statistic",{"2":{"262":1,"274":1}}],["statistics>",{"2":{"262":2,"274":2}}],["statistics",{"0":{"262":1},"2":{"250":1,"262":3}}],["staticsmeasure",{"2":{"189":1}}],["static",{"0":{"353":1},"2":{"50":1,"249":1,"353":1}}],["status",{"2":{"109":2,"172":1,"185":1,"211":3,"355":2,"360":2,"376":1,"458":1}}],["stateful",{"2":{"400":2,"401":1,"402":1}}],["state=",{"2":{"266":2,"274":3}}],["stateless",{"2":{"179":1,"400":2,"401":1,"407":2}}],["stated",{"2":{"140":1,"210":1,"267":1,"449":1}}],["statement",{"2":{"87":2,"141":1,"255":1,"259":1,"267":1,"310":2,"312":1,"347":1,"349":1}}],["statements",{"2":{"87":3,"267":1,"349":1,"486":1}}],["state",{"2":{"83":1,"143":1,"167":2,"169":1,"172":2,"174":6,"179":3,"185":2,"187":5,"251":2,"266":4,"322":1,"408":1}}],["stats",{"2":{"81":1,"169":2,"269":1}}],["stay",{"2":{"58":1,"66":1,"109":1,"233":1,"330":1}}],["starttoken",{"2":{"439":1}}],["startctx",{"2":{"312":4}}],["startoffset",{"2":{"192":1}}],["startup",{"2":{"169":1,"301":1,"326":2,"328":1}}],["starts",{"2":{"143":1,"222":1}}],["starting",{"0":{"242":1},"2":{"125":1,"143":1,"196":1,"220":1,"308":2,"309":1,"319":1,"320":1,"399":1,"425":1}}],["start",{"2":{"56":1,"99":1,"114":4,"124":2,"126":1,"193":4,"194":1,"196":1,"233":2,"241":1,"270":2,"284":1,"308":1,"313":3,"318":2,"319":2,"320":1,"324":1,"339":1,"340":4,"366":1,"425":2,"437":1,"439":2}}],["started",{"0":{"25":1},"2":{"17":1,"145":1,"217":1,"308":1,"309":2,"318":2,"319":1,"320":1,"327":1}}],["stage",{"2":{"56":1,"68":1,"310":2,"311":2}}],["stages",{"0":{"71":1,"264":1},"1":{"72":1,"73":1,"74":1,"75":1,"76":1,"77":1,"265":1,"266":1},"2":{"33":1,"48":1,"51":2,"58":1,"65":1,"84":1,"162":1,"253":1,"264":1,"308":1}}],["src",{"2":{"16":3,"18":7,"275":1}}],["sleep",{"2":{"325":2}}],["slow",{"2":{"217":1,"327":1}}],["slices",{"2":{"192":1}}],["slightly",{"2":{"51":1,"63":1,"64":1,"72":1,"85":1,"128":1,"190":1}}],["sla",{"2":{"169":1}}],["slave",{"2":{"148":1,"164":4}}],["slaves",{"2":{"145":1}}],["sln",{"2":{"123":2}}],["sl",{"2":{"15":1}}],["sunlight",{"2":{"339":1}}],["suffixed",{"2":{"271":1}}],["sufficiently",{"2":{"245":1}}],["sufficient",{"2":{"64":1,"163":1,"276":1,"290":1,"339":1,"407":1,"412":1,"423":1}}],["suspect",{"2":{"192":1,"196":2}}],["successful",{"2":{"313":1,"446":1,"449":1,"479":1}}],["successfully",{"2":{"242":1,"266":1,"327":1,"458":3}}],["success",{"2":{"169":1,"267":3,"449":1}}],["succeeds",{"2":{"157":1}}],["such",{"2":{"12":1,"113":2,"142":1,"169":6,"172":1,"174":1,"181":1,"182":2,"184":1,"204":1,"217":1,"219":1,"238":1,"299":2,"312":1,"321":1,"326":1,"328":1,"337":1,"339":2,"340":1,"397":1,"400":1,"403":1,"410":1,"424":1,"437":1,"439":2,"441":1,"461":1}}],["surprisingly",{"2":{"312":1}}],["surrounding",{"2":{"104":1,"400":1,"427":1}}],["sure",{"2":{"23":1,"106":3,"124":1,"190":1,"191":1,"204":1,"327":3,"451":1,"482":1}}],["suggestion",{"2":{"106":2}}],["suggestions",{"2":{"102":1,"196":1}}],["suggested",{"2":{"6":1,"242":1}}],["summarize",{"2":{"339":1}}],["summarizing",{"2":{"336":1}}],["summarised",{"2":{"308":2}}],["summarise",{"2":{"77":1}}],["summary",{"2":{"242":1,"328":1,"337":1}}],["summing",{"2":{"92":1}}],["suites",{"2":{"53":2}}],["suite",{"0":{"53":1,"286":1,"455":1,"459":1,"460":1,"463":1},"1":{"456":1,"457":1,"458":1,"460":1,"461":1},"2":{"53":5,"286":1,"309":3,"455":1,"458":2,"460":5,"461":3,"462":1,"465":1,"470":1}}],["suited",{"2":{"30":1,"72":1}}],["suitable",{"2":{"20":1,"47":1,"167":1,"325":1,"473":1,"487":1}}],["supersede",{"2":{"476":1}}],["superset",{"2":{"311":1,"476":1}}],["superuser",{"2":{"209":1}}],["superfiles",{"2":{"302":1}}],["superfile",{"2":{"192":1,"196":1,"266":1,"273":1}}],["super",{"2":{"192":2}}],["supercomputing",{"2":{"24":1}}],["suppress",{"2":{"328":1}}],["supply",{"2":{"133":1,"163":1,"400":1}}],["supplying",{"2":{"130":1}}],["supplied",{"2":{"79":1,"82":1,"254":1,"259":1,"264":1,"397":1,"428":2,"451":1,"485":1}}],["suppose",{"2":{"408":1}}],["supposedly",{"2":{"320":1}}],["supposing",{"2":{"126":1}}],["supporting",{"2":{"402":1,"407":1,"441":2,"443":2,"445":1}}],["supports",{"2":{"75":1,"139":1,"163":1,"175":1,"196":1,"328":1,"385":1,"401":1,"407":1,"420":1,"424":1,"428":1,"441":1,"448":1,"452":1,"465":1}}],["supported",{"0":{"202":1,"245":1,"466":1},"2":{"18":1,"26":2,"110":1,"113":2,"124":2,"183":1,"196":1,"202":1,"209":1,"210":1,"245":1,"272":1,"310":1,"351":1,"367":1,"394":1,"432":1,"439":1,"441":2,"452":1,"461":1,"473":1}}],["support",{"0":{"16":1,"26":1,"130":1,"365":1,"380":1,"385":1,"387":1,"388":1},"1":{"19":1,"131":1,"132":1,"133":1,"134":1},"2":{"12":1,"16":1,"19":1,"27":1,"29":1,"50":1,"72":1,"95":1,"99":1,"110":1,"132":2,"145":1,"160":1,"163":1,"172":2,"174":1,"175":2,"194":1,"212":1,"233":1,"276":1,"311":1,"313":1,"330":1,"385":3,"387":1,"388":1,"399":1,"400":2,"401":1,"420":1,"421":2,"442":1,"446":1}}],["subclassed",{"2":{"397":1}}],["subclasses",{"2":{"188":1,"441":1}}],["subcommand",{"2":{"348":1}}],["subchannels",{"2":{"330":1}}],["subchannel",{"2":{"324":1}}],["subfiles",{"2":{"302":1}}],["subfolders",{"2":{"112":1}}],["subgraphs",{"2":{"269":2,"270":1}}],["subgraph",{"2":{"256":1,"269":2,"270":8}}],["subject",{"2":{"419":1}}],["subjectaltname",{"2":{"240":2}}],["subj",{"2":{"240":1}}],["subtract",{"2":{"326":1}}],["subtractions",{"2":{"108":1}}],["subtask",{"2":{"170":1}}],["subdivided",{"2":{"146":1}}],["subdirectory",{"2":{"37":4,"44":2,"451":1}}],["subsystem",{"2":{"326":1}}],["substrings",{"2":{"439":1}}],["substring",{"2":{"428":1,"437":4,"439":2}}],["substantial",{"2":{"312":1}}],["substitution",{"2":{"219":1}}],["substituted",{"2":{"183":1,"190":1}}],["substitute",{"2":{"75":1,"187":1}}],["substition",{"2":{"190":1}}],["subsort",{"2":{"310":1}}],["subscribe",{"2":{"265":1}}],["subsections",{"2":{"408":1}}],["subset",{"2":{"77":1,"92":1,"189":1,"195":1,"312":1,"460":1,"476":1,"496":1}}],["subsequent",{"2":{"75":1,"145":1,"154":1,"161":1,"221":1,"318":1,"408":1}}],["subsequently",{"2":{"73":1,"156":1}}],["submit",{"2":{"103":2,"299":1,"446":1}}],["submitting",{"2":{"101":1,"198":1}}],["submitted",{"2":{"61":1,"133":1,"264":4}}],["submission",{"0":{"107":1},"1":{"108":1,"109":1,"110":1},"2":{"102":1,"108":3,"198":1}}],["submissions",{"2":{"101":1,"107":1,"108":1,"109":1,"198":1,"276":1}}],["submodule",{"2":{"23":1,"108":1}}],["submodules",{"2":{"17":1}}],["sub",{"0":{"455":1},"1":{"456":1,"457":1,"458":1},"2":{"23":1,"70":1,"149":1,"217":2,"233":1,"268":1,"320":1,"455":2}}],["sudo",{"2":{"3":1,"4":1,"7":1,"8":1,"10":1,"11":1,"12":1,"15":2,"16":3,"22":2,"122":2}}],["socs",{"2":{"403":1}}],["social",{"2":{"339":1,"403":1,"441":1}}],["sockets",{"0":{"327":1}}],["socket",{"0":{"352":1},"2":{"318":1,"327":1}}],["sooner",{"2":{"324":1}}],["soon",{"2":{"288":1,"312":1,"318":1,"332":2}}],["software",{"0":{"277":1},"1":{"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"217":1,"278":1,"280":1}}],["sortlist",{"2":{"310":4}}],["sorted",{"2":{"308":1,"309":3,"310":1,"313":5,"321":1}}],["sorts",{"2":{"75":1,"76":2,"249":1,"308":1}}],["sort",{"2":{"58":1,"69":1,"76":5,"81":2,"88":1,"249":3,"269":1,"272":1,"284":1,"308":6,"309":3,"310":6,"312":1,"313":1,"337":1}}],["sorting",{"2":{"28":1,"81":1,"249":1,"269":1,"315":1}}],["soap=",{"2":{"486":4,"487":4,"488":4}}],["soapcall",{"2":{"310":1,"327":3}}],["soap",{"2":{"32":1,"260":1,"261":2,"274":3,"479":2,"486":8,"487":8,"488":8}}],["sole",{"2":{"172":1}}],["solely",{"2":{"108":1}}],["solving",{"2":{"170":1}}],["solves",{"2":{"103":1,"278":1}}],["solve",{"2":{"24":1,"103":1,"106":1,"170":1,"310":1,"327":1,"337":1}}],["solution",{"2":{"123":2,"129":1,"132":1,"327":1,"340":1}}],["solutions",{"2":{"24":1,"170":1,"297":1}}],["somewhat",{"2":{"329":1}}],["somebody",{"2":{"310":1}}],["someone",{"2":{"109":3}}],["sometimes",{"2":{"56":1,"67":1,"70":2,"73":1,"76":1,"77":1,"90":1,"93":1,"103":2,"106":1,"181":1,"221":1,"222":1,"254":1,"267":1,"308":1,"310":1,"323":1,"325":1}}],["something",{"2":{"50":1,"84":1,"106":4,"223":1,"233":1,"241":1,"307":2,"308":1,"309":2,"310":3,"342":1,"435":1}}],["some",{"0":{"40":1,"329":1,"330":1},"1":{"41":1,"42":1,"43":1,"44":1},"2":{"12":1,"18":1,"23":2,"33":2,"49":1,"51":2,"54":1,"58":4,"59":2,"61":2,"63":1,"64":2,"65":2,"69":2,"70":3,"72":2,"73":3,"75":1,"76":1,"77":1,"80":1,"83":2,"87":5,"88":1,"91":1,"92":2,"95":1,"99":1,"100":1,"101":1,"103":1,"104":1,"105":1,"106":3,"107":1,"108":2,"111":1,"113":1,"123":1,"126":1,"132":1,"134":1,"145":1,"149":1,"159":1,"160":1,"167":1,"169":2,"170":2,"174":1,"178":1,"181":1,"192":2,"195":2,"196":2,"204":1,"211":1,"217":2,"221":1,"234":1,"245":1,"249":1,"251":1,"263":1,"264":1,"265":1,"269":2,"270":3,"271":1,"279":1,"281":2,"283":1,"284":1,"308":5,"309":4,"310":6,"311":4,"312":1,"313":1,"318":3,"319":2,"321":2,"324":1,"326":3,"328":2,"329":2,"339":1,"342":1,"349":1,"397":1,"400":1,"402":1,"431":1,"432":2,"441":1,"460":1,"470":1,"473":1,"474":1,"494":1}}],["so",{"0":{"433":1},"1":{"434":1,"435":1,"436":1,"437":1,"438":1,"439":1,"440":1,"441":1,"442":1,"443":1,"444":1,"445":1},"2":{"5":2,"21":1,"49":1,"50":1,"51":2,"53":1,"58":2,"61":1,"64":2,"65":1,"69":1,"72":1,"73":1,"79":1,"82":1,"83":1,"87":1,"99":1,"106":1,"109":1,"124":1,"129":1,"141":1,"145":1,"155":1,"160":1,"161":1,"162":1,"172":2,"187":1,"191":2,"192":2,"194":1,"195":2,"196":2,"211":2,"212":1,"217":1,"221":1,"238":1,"245":1,"249":1,"260":1,"266":2,"270":1,"271":1,"273":1,"276":1,"277":1,"280":1,"283":1,"299":1,"301":1,"308":4,"309":1,"310":5,"311":1,"313":2,"317":1,"318":1,"319":1,"320":1,"321":1,"322":1,"324":4,"327":1,"328":1,"330":2,"399":1,"451":1,"495":1}}],["sourceactivity",{"2":{"269":1}}],["source=",{"2":{"256":1,"269":4,"274":1}}],["sources",{"0":{"17":1,"120":1,"121":1},"1":{"122":1,"123":1},"2":{"23":2,"27":1,"72":1,"120":1,"123":3,"140":1,"166":1,"216":1,"339":2}}],["source",{"0":{"43":1,"96":1,"119":1,"218":1,"236":1,"343":1},"1":{"97":1,"344":1,"345":1,"346":1,"347":1,"348":1},"2":{"5":1,"18":1,"24":1,"31":1,"37":2,"43":2,"53":2,"56":1,"61":1,"72":3,"91":1,"104":1,"122":1,"123":2,"129":1,"139":1,"196":1,"198":1,"201":1,"218":2,"233":1,"269":6,"270":1,"308":1,"340":1,"346":1,"419":2,"494":1}}],["111517",{"2":{"460":2}}],["111509",{"2":{"460":2}}],["111507",{"2":{"460":2}}],["111504",{"2":{"460":2}}],["111502",{"2":{"460":2}}],["111459",{"2":{"460":2}}],["111457",{"2":{"460":2}}],["111455",{"2":{"460":2}}],["111453",{"2":{"460":2}}],["111450",{"2":{"460":2}}],["111447",{"2":{"460":2}}],["111442",{"2":{"460":2}}],["111438",{"2":{"460":2}}],["111434",{"2":{"460":2}}],["111429",{"2":{"460":2}}],["11",{"2":{"458":2,"460":4}}],["1c",{"2":{"412":1}}],["1b",{"2":{"412":1}}],["1a",{"2":{"412":1}}],["1st",{"2":{"353":1}}],["1k",{"2":{"330":1}}],["13",{"2":{"460":3}}],["13434",{"2":{"310":1}}],["13260",{"2":{"308":1}}],["13016",{"2":{"308":1}}],["15",{"2":{"448":1,"460":6}}],["159",{"2":{"274":1}}],["157",{"2":{"274":1}}],["1u",{"2":{"255":1,"266":2,"275":2}}],["1+1",{"2":{"233":1}}],["1mb",{"2":{"146":1}}],["1gb",{"2":{"58":1}}],["1428933081084000",{"2":{"262":1,"274":1}}],["14",{"2":{"16":2,"460":4}}],["123",{"2":{"359":1}}],["1234",{"2":{"351":1,"367":1,"382":1}}],["12345",{"2":{"242":2}}],["12267",{"2":{"308":1}}],["121",{"2":{"274":1}}],["120u",{"2":{"269":2,"275":2}}],["120",{"2":{"269":3,"270":3,"274":2,"275":2,"349":1}}],["127",{"2":{"240":1,"458":5,"460":15,"474":1,"487":1,"488":1,"491":1}}],["12",{"2":{"16":4,"26":1,"196":2,"242":1,"366":1,"460":3,"495":1}}],["1",{"0":{"308":1},"2":{"5":1,"7":1,"16":2,"23":1,"28":1,"29":1,"68":1,"75":1,"90":3,"128":1,"149":1,"187":2,"190":2,"191":2,"193":2,"196":9,"204":1,"217":1,"233":1,"240":4,"254":2,"255":1,"256":3,"261":3,"262":1,"266":4,"269":9,"270":1,"274":13,"308":1,"309":1,"310":1,"311":2,"328":1,"330":5,"335":1,"340":1,"351":1,"381":6,"385":3,"399":1,"441":3,"443":2,"446":1,"449":4,"458":9,"460":20,"474":1,"486":3,"487":5,"488":3,"491":1}}],["168",{"2":{"204":1}}],["16",{"0":{"4":1},"1":{"5":1},"2":{"196":1,"269":1,"274":1}}],["18010",{"2":{"340":4}}],["18",{"0":{"3":1,"5":1}}],["1024",{"2":{"363":2}}],["10m",{"2":{"58":1}}],["10gb",{"2":{"53":1}}],["10s",{"2":{"48":1}}],["100000",{"2":{"362":1}}],["1000s",{"2":{"28":1,"29":1}}],["100m",{"2":{"58":1}}],["100",{"0":{"360":1},"2":{"44":1,"64":1,"103":1,"193":2,"308":1,"360":4,"362":1,"378":1,"381":1}}],["10",{"0":{"3":1},"2":{"26":3,"204":1,"256":2,"261":1,"269":5,"274":7,"327":1,"337":1,"342":1,"381":4,"460":3,"462":1,"486":1,"487":1,"488":1,"497":1}}],["199",{"2":{"381":2}}],["19991116",{"2":{"482":1}}],["1999",{"2":{"274":1,"482":1,"486":1,"487":1,"488":1}}],["192",{"2":{"204":1}}],["19",{"0":{"3":2,"10":1}}],["urn",{"2":{"473":1,"474":1,"476":1,"486":6,"487":4,"488":4}}],["url=",{"2":{"474":1,"487":1,"488":1}}],["urlencoded",{"2":{"369":1}}],["url",{"2":{"114":2,"115":1,"190":1,"313":1,"378":1,"428":1,"451":3,"458":5,"460":15,"479":1}}],["uint32",{"2":{"466":1}}],["uint16",{"2":{"466":1}}],["uint8",{"2":{"466":1}}],["uint64",{"2":{"187":1,"378":2,"466":1}}],["u",{"2":{"403":1,"441":1}}],["udpoutqspriority",{"2":{"330":1}}],["udpreceiverentry",{"2":{"330":4}}],["udplib",{"2":{"330":1}}],["udp",{"0":{"330":1},"2":{"330":1}}],["utf16",{"2":{"196":2}}],["utf8",{"2":{"196":2}}],["utf",{"2":{"196":1}}],["utility",{"0":{"471":1},"1":{"472":1,"473":1,"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"484":1,"485":1,"486":1,"487":1,"488":1},"2":{"301":1,"471":1}}],["utilities",{"2":{"36":1,"397":1}}],["utilizing",{"2":{"242":1}}],["utilize",{"2":{"238":1,"296":1}}],["utils",{"2":{"36":1}}],["util",{"2":{"7":1,"8":1,"10":1,"11":1,"13":1}}],["ultimately",{"2":{"79":1,"87":1}}],["unwanted",{"2":{"497":1}}],["unwieldy",{"2":{"58":1,"69":1}}],["unupmerge",{"2":{"495":1}}],["unused",{"2":{"162":1,"163":2,"310":2,"318":1,"480":1}}],["unusedxxx",{"2":{"59":1}}],["unusually",{"2":{"85":1}}],["unusual",{"2":{"58":1,"63":1,"72":1,"106":1,"309":1}}],["unescaped",{"2":{"482":1}}],["unexpected",{"2":{"106":1,"319":1}}],["unbind",{"2":{"471":3}}],["unblock",{"2":{"342":1}}],["unchanged",{"2":{"442":2}}],["unconditional",{"2":{"419":3,"420":1,"421":3,"423":1,"427":1,"441":1}}],["unconditionally",{"2":{"400":1}}],["uncommon",{"2":{"192":1}}],["uncompressed",{"2":{"191":2,"460":2,"467":1}}],["unobfuscated",{"2":{"421":1}}],["unknown",{"2":{"421":1,"441":1}}],["unnamed",{"2":{"412":8,"416":9}}],["unnecessary",{"2":{"73":1,"324":1}}],["unpredictably",{"2":{"327":1}}],["unpacks",{"2":{"36":1}}],["unpack",{"2":{"5":1,"448":1}}],["unset",{"2":{"480":1}}],["unselected",{"2":{"420":1,"421":3,"441":1}}],["unselect",{"2":{"335":1}}],["unstructured",{"2":{"400":1}}],["unstable",{"2":{"308":3,"309":1}}],["unsigned4",{"2":{"325":2}}],["unsigned",{"2":{"59":1,"164":1,"187":1,"190":3,"191":1,"255":1,"269":4,"275":6,"325":1}}],["undesirables",{"2":{"349":2}}],["undefined",{"2":{"245":1,"421":2}}],["under",{"0":{"332":1},"2":{"238":1,"239":1,"330":1,"451":1}}],["underscore",{"2":{"220":1,"349":2}}],["understandable",{"2":{"105":1}}],["understanding",{"0":{"248":1},"1":{"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"261":1,"262":1,"263":1,"264":1,"265":1,"266":1,"267":1,"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1},"2":{"103":1,"221":1,"249":3,"269":1,"270":1,"280":1,"339":1,"432":1}}],["understand",{"2":{"84":2,"106":1,"216":1,"232":1,"249":1,"263":1,"271":1,"276":1,"337":2,"339":1}}],["underlying",{"2":{"69":1,"174":1,"180":1,"184":1,"187":2,"323":1,"401":1}}],["unfamiliar",{"2":{"216":1}}],["unfortunately",{"2":{"53":1,"66":1,"160":1,"326":1,"328":1,"446":1}}],["unauthorized",{"2":{"492":1}}],["unavoidable",{"2":{"431":1}}],["unavailable",{"2":{"323":1,"327":1}}],["unable",{"2":{"143":1}}],["unannotated",{"2":{"70":1}}],["unrecognized",{"2":{"419":1}}],["unrelated",{"2":{"108":1}}],["unresponsive",{"2":{"103":1}}],["unlock",{"2":{"336":1}}],["unlike",{"2":{"217":1,"416":1,"432":1}}],["unlikely",{"2":{"106":1,"196":1}}],["unless",{"2":{"45":1,"84":1,"106":2,"109":1,"221":1,"242":1,"245":1,"327":1,"476":1}}],["until",{"2":{"102":1,"103":1,"124":1,"141":1,"143":1,"144":1,"157":1,"163":1,"211":1,"270":1,"313":2,"319":1,"326":1,"330":1,"446":1}}],["untranslated",{"2":{"90":1}}],["unicode",{"2":{"464":1}}],["unidentified",{"2":{"432":1}}],["uninstall",{"2":{"347":1}}],["uninitialized",{"2":{"143":1}}],["unimplemented",{"2":{"329":1}}],["unimportant",{"2":{"308":1}}],["universally",{"2":{"106":1}}],["unique",{"0":{"153":1},"2":{"56":1,"58":2,"64":1,"66":2,"81":1,"136":1,"153":3,"170":1,"254":1,"269":1,"302":1,"328":2,"399":2,"401":1,"436":1,"441":2,"443":3,"451":1}}],["unit=",{"2":{"262":1,"274":1}}],["units",{"0":{"189":1},"2":{"182":2,"189":4,"254":1}}],["unit",{"0":{"126":1},"2":{"47":1,"96":2,"126":1,"273":1,"319":1}}],["usually",{"2":{"156":1,"302":1,"319":1}}],["usual",{"2":{"123":1,"129":1}}],["us",{"2":{"58":1,"240":1,"307":1}}],["usage",{"0":{"35":1,"304":1,"485":1,"491":1},"1":{"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1},"2":{"58":1,"145":1,"166":1,"169":1,"277":1,"279":2,"283":1,"300":2,"301":1,"321":1,"348":1,"407":1,"411":1,"415":1,"419":2,"432":2,"474":2,"478":1,"479":1,"480":1,"481":1,"482":1,"485":2}}],["using",{"0":{"44":1},"2":{"19":1,"20":2,"23":1,"24":1,"30":1,"32":1,"38":2,"49":1,"53":3,"63":1,"66":1,"103":1,"104":1,"108":1,"120":1,"123":1,"128":3,"131":1,"133":1,"136":1,"139":1,"143":1,"144":1,"149":1,"150":1,"170":1,"180":1,"181":1,"182":1,"187":4,"189":1,"193":3,"194":4,"196":5,"198":1,"202":1,"204":2,"211":1,"212":1,"219":2,"234":1,"241":1,"245":1,"254":1,"270":2,"274":1,"278":1,"279":1,"297":1,"299":1,"308":1,"317":2,"318":1,"320":1,"321":4,"325":1,"326":1,"327":1,"328":1,"329":1,"330":1,"333":1,"346":3,"347":1,"351":1,"400":1,"401":1,"403":1,"404":1,"406":1,"407":1,"408":2,"412":1,"420":1,"424":1,"425":2,"428":1,"432":2,"438":1,"441":1,"442":1,"445":5,"473":1,"480":1,"482":2,"488":1,"491":1,"492":1,"494":1}}],["useful",{"2":{"53":2,"56":3,"67":1,"69":2,"166":1,"167":2,"169":1,"170":2,"186":1,"191":2,"196":4,"254":1,"288":3,"308":6,"309":3,"310":1,"313":1,"325":1,"326":1,"327":1,"332":3,"339":1,"432":1,"494":1}}],["usesproperty",{"2":{"411":1,"415":1}}],["usesselector",{"2":{"69":1}}],["uses",{"2":{"36":1,"38":3,"64":3,"69":1,"74":1,"77":1,"81":2,"85":1,"95":2,"117":1,"135":1,"147":1,"148":4,"155":2,"162":1,"174":2,"195":1,"211":1,"214":1,"219":1,"234":1,"251":1,"256":1,"265":1,"266":1,"270":1,"278":1,"308":1,"309":1,"310":1,"321":1,"327":1,"347":1,"432":3,"448":1,"451":1,"470":1}}],["usercontent",{"2":{"486":1,"487":1,"488":1}}],["usercontext",{"2":{"486":1,"487":1,"488":1}}],["userid",{"2":{"480":1}}],["userdoc",{"2":{"288":6,"332":5}}],["username=ghalliday",{"2":{"134":1}}],["username",{"2":{"133":1,"134":2,"140":1,"204":2,"211":2,"213":2,"239":3,"302":1,"446":1,"448":3,"449":3,"492":1}}],["user",{"0":{"207":1,"209":1,"210":1,"213":1,"214":1,"294":1,"331":1},"1":{"210":1,"211":2,"212":2,"213":2,"214":1,"332":1,"333":1,"334":1},"2":{"27":1,"53":4,"105":1,"132":2,"133":1,"192":2,"204":6,"209":5,"210":3,"211":10,"212":2,"213":1,"214":10,"238":1,"239":1,"250":1,"263":1,"277":1,"278":3,"279":1,"280":1,"288":1,"299":2,"302":3,"308":1,"334":1,"353":1,"379":2,"380":2,"397":2,"400":1,"401":1,"402":1,"438":3,"443":2,"446":2,"448":6,"449":2,"450":1,"451":1,"453":6,"454":6,"455":1,"460":1,"485":2}}],["usersbasedn",{"2":{"204":1}}],["users",{"2":{"18":1,"48":1,"109":1,"135":1,"140":2,"196":1,"204":5,"211":1,"212":1,"246":1,"277":2,"278":4,"279":1,"280":3,"285":1,"288":1,"297":1,"331":1,"342":1,"446":2,"448":1,"490":1,"492":1}}],["used",{"0":{"40":1,"251":1,"285":1},"1":{"41":1,"42":1,"43":1,"44":1},"2":{"15":1,"29":1,"31":1,"33":1,"36":4,"37":2,"38":1,"39":1,"45":1,"50":1,"53":1,"56":1,"58":1,"59":5,"60":1,"61":3,"63":1,"64":2,"65":1,"69":1,"70":1,"72":1,"73":2,"74":1,"75":1,"76":2,"77":1,"79":1,"80":1,"84":3,"87":2,"88":1,"89":1,"90":2,"92":1,"93":1,"95":1,"96":2,"99":1,"105":2,"106":1,"114":1,"131":3,"141":2,"143":1,"144":1,"145":4,"149":1,"150":1,"153":1,"154":1,"160":2,"162":1,"163":3,"164":1,"166":1,"172":2,"179":1,"186":1,"189":1,"190":1,"191":1,"192":3,"193":1,"194":1,"196":4,"199":1,"203":1,"204":5,"211":1,"217":2,"218":2,"220":2,"222":1,"224":1,"233":2,"234":2,"238":1,"239":1,"241":1,"249":6,"250":1,"251":2,"252":1,"254":1,"255":2,"264":1,"267":4,"268":1,"269":6,"270":3,"272":7,"274":1,"282":2,"290":1,"301":1,"302":1,"303":1,"306":2,"308":6,"309":1,"310":9,"311":4,"312":7,"313":1,"317":1,"318":1,"319":1,"320":3,"321":2,"322":2,"323":1,"326":3,"327":1,"330":1,"349":1,"365":1,"397":3,"399":1,"400":1,"402":2,"403":1,"405":1,"407":6,"408":2,"410":2,"411":3,"412":3,"414":2,"415":3,"416":2,"419":1,"420":3,"424":2,"428":2,"432":3,"433":2,"436":1,"439":1,"440":1,"441":4,"444":1,"447":1,"448":2,"451":2,"461":1,"470":1,"473":1,"474":1,"475":1,"476":3,"478":2,"479":1,"482":1,"488":1,"494":1}}],["use",{"0":{"168":1,"171":1,"384":1},"1":{"169":1,"170":1,"171":1},"2":{"13":2,"16":3,"19":1,"21":2,"23":1,"32":1,"41":1,"44":2,"45":4,"56":1,"58":1,"64":1,"65":1,"66":1,"73":1,"74":2,"81":2,"83":2,"84":1,"85":1,"103":2,"105":1,"109":1,"114":1,"124":1,"128":1,"129":1,"131":3,"132":1,"133":1,"139":1,"140":1,"143":1,"145":1,"148":1,"155":1,"159":2,"163":4,"168":1,"170":6,"174":1,"175":2,"182":1,"185":1,"186":2,"187":2,"194":2,"195":2,"196":4,"202":1,"205":1,"209":1,"217":3,"218":1,"219":1,"220":1,"221":7,"222":1,"225":1,"230":1,"232":1,"233":2,"234":2,"240":1,"241":1,"242":2,"246":2,"269":1,"271":1,"278":2,"280":3,"291":1,"294":1,"299":7,"300":2,"301":2,"303":1,"308":1,"310":1,"311":3,"317":1,"318":1,"321":1,"325":1,"327":2,"328":1,"329":2,"337":2,"339":1,"340":11,"349":1,"397":1,"405":2,"407":2,"408":1,"411":2,"412":3,"415":2,"416":1,"420":1,"427":1,"428":2,"431":2,"438":1,"441":3,"450":1,"451":1,"452":2,"460":1,"471":2,"473":1,"474":1,"482":1,"485":2,"486":1,"497":1}}],["usr",{"2":{"5":3,"23":10}}],["ubuntu",{"0":{"2":1,"3":1,"4":1,"5":1},"1":{"3":1,"4":1,"5":2},"2":{"16":1,"18":1,"22":1,"124":1,"246":1}}],["upmerge",{"2":{"496":6}}],["upmerged",{"2":{"110":1,"496":1}}],["upgrade",{"2":{"246":1}}],["uploaded",{"2":{"242":1,"254":1}}],["upon",{"2":{"238":1,"492":1}}],["updating",{"2":{"160":1,"167":2,"172":1,"174":1,"180":1}}],["updatelogrequest",{"2":{"486":1,"487":1,"488":1}}],["updates",{"2":{"178":1,"187":2,"251":1}}],["updated",{"2":{"83":1,"108":1,"179":2,"190":1,"264":1,"286":1,"310":3}}],["update",{"2":{"23":1,"90":1,"112":1,"160":2,"172":3,"174":3,"179":1,"180":1,"187":1,"225":1,"264":1,"270":1,"280":2,"283":2}}],["upto",{"2":{"119":1}}],["upstream",{"2":{"64":1}}],["upper",{"2":{"44":1,"326":1,"349":3}}],["up",{"0":{"306":2},"2":{"0":1,"23":1,"36":1,"49":1,"51":1,"58":1,"61":1,"64":1,"68":1,"73":2,"80":1,"81":1,"86":1,"87":2,"89":1,"100":1,"103":1,"105":1,"106":3,"109":1,"120":2,"124":3,"133":1,"148":1,"152":2,"157":2,"161":1,"163":1,"169":1,"172":1,"194":1,"196":2,"204":1,"216":1,"217":1,"234":1,"243":1,"245":1,"254":1,"267":2,"270":2,"282":1,"285":1,"293":1,"299":1,"306":2,"312":2,"313":1,"319":1,"320":1,"324":1,"327":1,"330":2,"340":3,"347":1,"349":1}}],["p>",{"2":{"355":1}}],["p>error",{"2":{"355":1}}],["png",{"2":{"353":2}}],["pb",{"2":{"337":1}}],["pgp",{"2":{"241":2}}],["pfx",{"2":{"240":3}}],["pkcs12",{"2":{"240":2}}],["pks12",{"2":{"239":2}}],["pkg",{"2":{"3":1}}],["photosynthesis",{"2":{"339":2}}],["phrase",{"2":{"306":1}}],["philosophy",{"2":{"223":1}}],["phased",{"2":{"218":1}}],["phase",{"2":{"192":1,"328":1}}],["physical",{"2":{"95":1,"163":1}}],["pvc",{"2":{"190":1,"326":1}}],["pcustommetric",{"2":{"187":1}}],["pcount",{"2":{"187":1}}],["pcounter2",{"2":{"188":1}}],["pcounter1",{"2":{"188":1}}],["pcounter",{"2":{"187":2}}],["ptr",{"2":{"187":1,"188":2,"217":2,"234":2}}],["pthreads",{"2":{"18":1}}],["ping",{"2":{"487":1}}],["pii",{"2":{"321":1,"397":1}}],["piece",{"2":{"254":2,"267":1,"448":1}}],["pieces",{"2":{"69":1,"250":1,"317":1}}],["pipeline",{"2":{"246":1}}],["pimpl",{"2":{"233":3}}],["picture",{"2":{"166":1,"280":1}}],["picking",{"2":{"498":1}}],["pick",{"2":{"326":1}}],["picks",{"2":{"196":1,"347":1}}],["picked",{"2":{"103":1,"133":1}}],["picky",{"2":{"103":1,"106":1}}],["pseudo",{"2":{"90":1}}],["pem",{"2":{"385":2}}],["people",{"2":{"270":1,"349":2,"496":1}}],["peer",{"2":{"238":1}}],["peephole",{"2":{"87":1}}],["pending",{"2":{"169":1}}],["pertaining",{"2":{"403":1}}],["permissive",{"2":{"474":1}}],["permission",{"2":{"451":1,"452":2}}],["permissions",{"0":{"453":1,"454":1},"2":{"42":4,"135":1,"142":1,"204":1,"211":1,"451":2,"452":5}}],["permittosend",{"2":{"330":2}}],["perl",{"2":{"328":2}}],["perhaps",{"2":{"321":1,"326":1,"327":1,"328":1}}],["persists",{"2":{"270":1,"272":1,"310":1}}],["persist",{"2":{"254":2,"267":3,"326":1}}],["persistent",{"2":{"249":1,"264":1,"310":1,"340":1}}],["person",{"2":{"109":1,"252":1,"307":1,"369":1,"480":1}}],["personal",{"2":{"106":2,"131":2,"132":1,"133":1}}],["perf",{"2":{"328":2}}],["perftrace",{"0":{"328":1},"2":{"328":1}}],["perfectly",{"2":{"216":1}}],["performing",{"2":{"420":1,"424":1,"428":1}}],["performed",{"2":{"180":1,"209":1,"267":1,"310":1,"425":1,"428":1,"446":1}}],["performs",{"2":{"170":1,"210":1,"446":1}}],["perform",{"2":{"45":1,"80":1,"100":1,"140":1,"142":1,"209":1,"210":1,"214":1,"249":2,"255":2,"266":2,"267":1,"270":2,"275":1,"299":1,"309":1,"321":1,"428":1}}],["performance",{"2":{"29":1,"84":1,"142":1,"155":2,"170":1,"217":3,"221":1,"234":1,"245":2,"308":2,"326":1,"328":1,"425":1,"428":1,"461":2,"467":1}}],["period",{"2":{"183":1,"185":1,"221":1,"320":1,"324":1}}],["periodically",{"2":{"322":1,"326":1,"327":1}}],["periodic",{"2":{"175":1,"183":1,"328":1}}],["percentiles",{"2":{"308":1}}],["percentile",{"2":{"170":1}}],["percolation",{"2":{"77":1}}],["percolated",{"2":{"310":1}}],["percolate",{"2":{"75":1}}],["per",{"2":{"29":2,"58":1,"153":1,"192":1,"218":2,"219":1,"299":1,"317":1,"318":1,"321":1,"326":1,"327":1,"328":1,"402":2,"425":1,"432":1}}],["put",{"0":{"372":1},"2":{"351":1,"372":1}}],["punctuation",{"2":{"299":1}}],["publishing",{"2":{"480":3}}],["publish",{"2":{"246":1,"251":1,"340":1,"471":2}}],["publishedby",{"2":{"480":1}}],["published",{"2":{"114":1,"229":1,"246":1,"264":1,"318":1,"331":1,"334":1,"340":1,"473":1}}],["public",{"2":{"69":1,"83":1,"130":1,"219":2,"233":8,"234":1,"255":1,"257":2,"269":1,"270":1,"275":4,"349":1,"353":7,"366":2,"448":2}}],["pulled",{"2":{"318":1}}],["pulls",{"2":{"264":2,"270":1}}],["pull",{"0":{"108":1,"289":1},"2":{"104":1,"107":1,"109":1,"120":1,"128":1,"175":1,"196":2,"289":3,"309":1,"310":2}}],["pushing",{"2":{"196":1}}],["pushed",{"2":{"109":1,"196":1,"242":2}}],["push",{"2":{"103":1,"175":1,"196":2,"242":2,"357":1}}],["purely",{"2":{"58":1,"85":1}}],["pure",{"2":{"49":1,"219":2,"233":2}}],["purposes",{"2":{"163":1,"170":1,"254":1,"312":1,"322":1}}],["purpose",{"0":{"47":1,"345":1},"2":{"47":1,"77":1,"81":1,"108":1,"210":1,"278":1,"292":1,"338":1,"432":1,"446":1}}],["p",{"2":{"56":1,"261":1,"274":1,"432":8}}],["pdf",{"2":{"36":1,"38":2,"113":1,"200":1,"353":2}}],["py",{"2":{"391":1}}],["pyembed",{"2":{"18":1}}],["python34",{"2":{"7":2,"8":1}}],["python3",{"2":{"3":1,"4":1,"391":1}}],["python",{"2":{"3":1,"4":1,"7":1}}],["podname>",{"2":{"340":1}}],["pod",{"2":{"169":1,"322":1,"326":1}}],["popped",{"2":{"149":1}}],["populate",{"2":{"18":1,"128":1}}],["pools",{"2":{"328":1}}],["pool",{"0":{"143":1,"365":1,"366":1},"2":{"136":1,"139":1,"143":10,"149":1,"150":1,"169":1,"324":1,"327":1,"365":1,"366":4}}],["potentially",{"2":{"99":1,"152":1,"159":1,"162":1,"192":1,"321":1,"425":1}}],["potential",{"2":{"77":1,"85":1,"105":1,"106":2,"108":1,"140":1,"299":1,"319":1,"326":1,"327":1,"336":1,"461":1,"492":1}}],["positive",{"2":{"338":1,"437":1}}],["position",{"2":{"67":1,"68":1,"95":1,"191":1}}],["posts",{"2":{"308":1}}],["post",{"0":{"356":1,"369":1,"370":1,"371":1},"2":{"184":1,"186":3,"351":1,"356":1,"357":1,"369":3,"370":4,"371":1,"377":1,"389":1,"449":1}}],["possibilities",{"2":{"311":1}}],["possibility",{"2":{"66":1,"145":1,"326":1}}],["possibly",{"2":{"62":1,"65":1,"99":1,"105":1,"133":1,"192":2,"245":1,"308":1,"445":1,"446":1,"479":2}}],["possibleemptypages",{"2":{"149":1}}],["possible",{"0":{"314":1},"2":{"48":1,"50":1,"58":1,"70":1,"83":1,"108":1,"142":1,"145":1,"157":1,"170":1,"196":2,"219":1,"233":1,"263":1,"278":1,"287":1,"297":1,"299":2,"308":1,"311":1,"313":1,"314":1,"321":3,"326":1,"327":2,"340":1,"347":2,"397":1,"424":1,"448":1,"477":1,"481":1}}],["polish",{"2":{"396":1}}],["policies",{"2":{"233":1}}],["policy",{"0":{"26":1},"2":{"233":1,"307":1}}],["pollution",{"2":{"233":1}}],["polled",{"2":{"175":1}}],["polymorphism",{"2":{"58":1}}],["powerful",{"2":{"28":1,"29":1,"30":1,"336":1}}],["power",{"2":{"28":1,"330":1}}],["points",{"2":{"87":1,"106":1,"191":1,"192":3,"195":1,"196":3,"308":1,"309":2,"312":1,"337":1,"339":1}}],["pointers",{"0":{"221":1},"2":{"84":1,"93":1,"217":1,"221":11,"234":1}}],["pointer",{"2":{"58":1,"149":1,"160":3,"187":5,"217":1,"221":4,"233":1,"234":7}}],["point",{"0":{"444":1,"498":1},"1":{"445":1},"2":{"26":1,"56":1,"69":1,"73":1,"77":1,"80":1,"110":1,"217":1,"246":4,"309":1,"310":1,"321":1,"329":1,"339":1,"340":1,"353":6,"399":1,"406":2,"425":1,"440":1,"444":2,"496":1,"497":1}}],["port=9999",{"2":{"325":1}}],["portal",{"0":{"335":1},"2":{"288":1,"332":1,"333":1,"335":2}}],["portion",{"2":{"18":1,"302":1}}],["port",{"0":{"352":1},"2":{"12":1,"124":1,"204":2,"352":2,"367":1,"378":1,"380":1,"480":3,"491":2}}],["paging",{"2":{"326":1}}],["page",{"0":{"117":1,"147":1},"2":{"117":1,"147":1,"149":5,"150":1,"158":1,"160":2,"161":1,"321":3,"326":7,"334":2,"335":5}}],["pages",{"0":{"163":1},"2":{"15":1,"114":1,"146":3,"147":3,"149":3,"160":2,"161":3,"163":11,"263":1,"321":8,"326":6}}],["payload",{"0":{"363":1},"2":{"321":4,"363":1}}],["panel",{"2":{"277":1}}],["padding",{"2":{"190":1}}],["pair",{"2":{"186":1}}],["pairs",{"2":{"183":1,"188":1}}],["painful",{"2":{"195":1,"319":1}}],["pain",{"2":{"62":1,"327":1}}],["packetsqueued",{"2":{"330":1}}],["packets",{"2":{"324":1,"329":1,"330":6}}],["packet",{"0":{"330":1},"2":{"320":7,"324":1,"330":1}}],["packed",{"0":{"152":1},"2":{"145":1,"152":1,"153":1,"329":1}}],["pack",{"2":{"161":1,"329":1}}],["packaging",{"0":{"124":1},"2":{"18":2,"36":1}}],["packaged",{"2":{"345":1}}],["packagemap",{"2":{"340":3}}],["package",{"0":{"21":1,"22":1,"247":1},"2":{"15":1,"18":3,"20":1,"21":4,"22":2,"23":1,"36":1,"45":3,"124":6,"125":1,"128":1,"129":1,"130":1,"131":1,"246":1,"247":1,"302":1,"340":3}}],["packages",{"0":{"14":1},"1":{"15":1},"2":{"13":1,"18":1,"21":2,"23":1,"238":4,"240":1,"246":2}}],["packager",{"2":{"10":1,"11":1}}],["paste",{"2":{"241":1}}],["pasting",{"2":{"108":1}}],["past",{"2":{"100":1,"185":1,"299":2,"308":1}}],["passive",{"2":{"299":1}}],["passing",{"2":{"102":1,"106":1,"214":1,"221":1,"266":1,"330":1,"407":1,"432":1,"458":1,"460":1}}],["passes",{"2":{"180":1,"188":1,"249":1,"266":1,"270":1}}],["passed",{"2":{"61":1,"74":1,"90":1,"105":1,"157":1,"183":1,"191":1,"205":1,"266":1,"270":1,"290":1,"308":1,"311":1,"327":1,"328":1,"446":2,"456":4,"478":1}}],["passwords",{"2":{"397":1,"428":1}}],["password",{"2":{"131":1,"133":2,"134":2,"140":1,"204":3,"212":2,"213":3,"239":2,"240":1,"302":1,"400":5,"410":1,"427":1,"428":3,"441":1,"446":1,"448":3,"449":3,"492":1}}],["passcode",{"2":{"131":2}}],["pass",{"2":{"106":1,"132":2,"184":1,"266":1,"379":2,"380":3,"431":1,"432":1,"458":15,"460":48}}],["passphrases",{"2":{"132":1}}],["passphrase",{"2":{"18":1,"239":4,"240":1,"241":2}}],["patches",{"0":{"246":1}}],["patterns",{"0":{"231":1,"232":1},"1":{"232":1,"233":2,"234":2,"235":2},"2":{"106":1,"166":1,"232":1,"242":1,"408":1,"452":1}}],["pattern",{"2":{"73":1,"183":1,"187":1,"311":2,"312":1,"402":2,"408":3,"436":1,"452":2,"453":6,"454":6}}],["path>",{"2":{"340":1}}],["pathogenic",{"2":{"145":1}}],["pathological",{"2":{"99":1}}],["paths",{"2":{"38":2,"122":1,"170":1,"194":1}}],["path",{"0":{"55":1},"2":{"23":1,"45":1,"53":1,"54":7,"190":1,"194":2,"196":1,"348":1,"385":1,"447":2,"482":4,"485":5,"491":1,"495":1}}],["parcels",{"2":{"345":1}}],["parentheses",{"2":{"479":1}}],["parent",{"2":{"266":1,"270":2,"309":2,"310":1,"312":2,"326":2,"432":1,"474":1}}],["parquetio",{"2":{"468":2}}],["parquet",{"0":{"459":1,"466":1,"468":1},"1":{"460":1,"461":1},"2":{"191":1,"460":41,"461":2,"462":16,"465":1,"468":2,"469":1,"470":1}}],["paragraph",{"2":{"299":1}}],["paragraphs",{"2":{"299":1,"310":1}}],["params",{"2":{"370":8}}],["param",{"2":{"349":1,"351":3}}],["paramaters",{"2":{"195":1}}],["parameter",{"0":{"295":1,"455":1},"1":{"456":1,"457":1,"458":1},"2":{"90":1,"108":1,"188":3,"220":1,"221":3,"252":1,"260":1,"264":1,"266":1,"290":4,"310":1,"340":2,"419":4,"423":2,"428":1,"431":1,"432":1,"440":1,"455":1}}],["parameterised",{"2":{"88":1}}],["parameters",{"0":{"260":1,"370":1},"2":{"53":1,"56":1,"220":2,"221":2,"249":2,"250":1,"251":1,"260":1,"295":1,"349":1,"419":1,"420":1,"423":1,"424":1,"428":1,"441":1,"442":1,"443":1,"444":1,"479":1}}],["parallel",{"2":{"28":3,"29":2,"30":1,"84":2,"128":1,"195":1,"238":1,"271":1,"308":1,"309":1,"311":1,"313":1,"318":1,"326":1,"327":1,"425":2}}],["partmask",{"2":{"194":1}}],["partial",{"2":{"233":1,"400":1,"441":1,"482":2}}],["partially",{"2":{"103":1,"397":1,"403":1,"437":1}}],["partitioning",{"2":{"315":1,"462":1}}],["partitioned",{"2":{"309":1}}],["partitions",{"2":{"308":1,"309":1}}],["partition",{"2":{"192":1,"196":2,"308":3,"309":1,"310":1,"311":1,"312":1,"460":2,"462":1}}],["partitionoptions",{"2":{"192":2}}],["particilar",{"2":{"431":1}}],["participate",{"2":{"172":1}}],["particularly",{"2":{"53":1,"68":1,"155":1,"256":1,"319":1,"324":1}}],["particular",{"2":{"49":1,"56":2,"80":1,"81":1,"83":1,"85":1,"87":1,"88":1,"93":1,"100":1,"103":1,"106":1,"149":1,"155":1,"163":1,"193":1,"246":1,"254":1,"255":1,"257":1,"266":1,"269":3,"270":1,"272":1,"288":1,"310":1,"319":1,"321":1,"324":2,"326":2,"327":2,"328":1,"332":1,"348":1,"349":1,"403":1,"432":1,"498":1}}],["parts",{"0":{"199":1},"2":{"79":1,"184":1,"185":1,"191":2,"196":5,"234":1,"254":1,"308":2,"311":1,"318":1,"425":1,"451":1}}],["part",{"2":{"69":2,"70":1,"79":1,"104":1,"106":1,"108":1,"109":2,"128":1,"161":1,"172":2,"185":2,"190":1,"191":6,"193":1,"209":1,"217":1,"233":3,"246":1,"249":1,"269":1,"308":1,"309":2,"310":2,"317":1,"321":2,"326":1,"329":2,"416":1,"428":1,"432":2}}],["partly",{"2":{"56":1}}],["party",{"0":{"14":1},"1":{"15":1},"2":{"12":1,"23":1,"122":1,"192":1,"214":1,"250":1,"408":1,"446":2,"449":1,"450":1}}],["parsing",{"0":{"72":1},"2":{"37":1,"96":1,"348":2}}],["parsed",{"2":{"61":2,"72":3,"400":1}}],["parser",{"0":{"85":1,"100":1,"310":1},"2":{"61":1,"64":1,"65":1,"73":1,"84":1,"85":2,"100":2,"309":2,"310":2,"311":1}}],["parse",{"2":{"37":1,"51":1,"64":1,"72":2,"92":1,"346":1,"492":1}}],["pl",{"2":{"328":1}}],["plus",{"2":{"302":1,"448":1,"449":1,"486":1}}],["plural",{"2":{"185":2}}],["plug",{"2":{"301":1,"303":1,"321":1}}],["pluggable",{"2":{"172":1,"175":1}}],["plugin",{"0":{"205":1,"406":1,"446":1,"459":1,"466":1},"1":{"206":1,"207":1,"208":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"460":1,"461":1},"2":{"18":2,"37":4,"54":1,"202":1,"203":1,"205":3,"397":3,"406":3,"419":1,"420":1,"424":1,"428":1,"433":1,"446":3,"448":8,"449":2,"451":5,"452":1,"460":1,"461":3,"465":2}}],["plugins=on",{"2":{"18":1}}],["plugins",{"2":{"12":2,"18":2,"53":2,"54":1,"112":1,"203":1,"243":1,"397":2,"407":2,"425":1,"447":1}}],["please",{"2":{"103":2,"108":1,"263":1,"338":1,"364":1,"388":1}}],["plate",{"2":{"312":1}}],["platforms",{"0":{"9":1},"1":{"10":1,"11":1,"12":1,"13":1},"2":{"123":1,"129":1}}],["platform",{"0":{"276":1},"1":{"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"0":1,"5":1,"17":1,"18":1,"22":1,"24":3,"26":1,"32":1,"36":1,"37":1,"107":1,"114":2,"115":1,"120":1,"122":1,"123":2,"125":1,"130":1,"172":1,"187":1,"196":1,"198":1,"202":1,"217":1,"221":1,"238":3,"242":4,"243":2,"245":1,"250":1,"264":1,"276":1,"277":1,"286":1,"298":1,"300":1,"301":4,"309":1,"310":1,"311":1,"312":1,"313":2,"328":1,"331":1,"334":1,"350":1,"397":1,"407":1,"448":1,"451":1,"460":1,"461":1,"470":1,"474":1,"495":1}}],["play",{"2":{"326":1}}],["played",{"2":{"321":1}}],["playground",{"2":{"301":1}}],["plays",{"2":{"249":1}}],["plain",{"2":{"87":1,"351":3,"353":1,"357":1,"358":2,"369":1,"371":1,"372":1,"377":1,"389":1}}],["plants",{"2":{"339":1}}],["plane",{"2":{"190":4,"191":1,"192":1,"193":2,"195":1,"196":7}}],["planese",{"2":{"194":1}}],["planes",{"0":{"190":1},"2":{"190":2,"191":2,"193":3,"194":1,"196":7}}],["plans",{"2":{"171":1}}],["planning",{"2":{"120":1,"277":1}}],["planned",{"2":{"69":1}}],["plan",{"2":{"50":1,"196":1}}],["placeholders",{"2":{"310":1}}],["placeholder",{"2":{"310":1}}],["placement",{"0":{"287":1},"1":{"288":1,"289":1,"290":1}}],["places",{"2":{"91":1,"216":1,"310":2}}],["placed",{"2":{"61":1,"202":1,"222":1}}],["place",{"2":{"19":1,"70":1,"122":1,"188":1,"217":1,"242":1,"253":1,"271":1,"310":1,"313":1,"319":1,"448":1,"474":1,"482":1}}],["pr",{"2":{"103":4,"106":6,"108":8,"109":2,"194":1}}],["prs",{"2":{"101":1,"103":1,"106":2,"107":1,"108":1,"109":1,"198":2,"496":1}}],["practice",{"2":{"51":1,"123":1,"209":1,"298":1,"309":1,"313":1}}],["prep",{"2":{"474":5,"486":7,"487":6,"488":6}}],["prepareddata",{"2":{"474":1,"487":1,"488":1}}],["prepared",{"2":{"431":2}}],["prepare",{"2":{"108":1,"318":1,"358":1}}],["precisely",{"2":{"450":1}}],["precede",{"2":{"242":1,"439":1}}],["prematurely",{"2":{"482":1}}],["prem",{"2":{"340":1}}],["pretty",{"2":{"327":1}}],["pretend",{"2":{"226":1}}],["prelogging>",{"2":{"486":2,"487":2,"488":2}}],["prelogging",{"2":{"486":2,"487":2,"488":2}}],["preloaded",{"2":{"326":1}}],["preliminary",{"2":{"321":1}}],["prewarm",{"0":{"326":1},"2":{"326":6}}],["presumes",{"2":{"427":1}}],["press",{"2":{"299":1,"304":1,"307":1}}],["preservation",{"2":{"474":2}}],["preserving",{"2":{"311":1}}],["preserve",{"2":{"192":2,"310":1,"474":1}}],["preserved",{"2":{"66":1,"67":1,"482":1}}],["presently",{"2":{"321":1}}],["present",{"2":{"140":1,"170":1,"204":1,"249":1,"280":1,"299":1,"328":1,"337":2,"340":1,"448":2,"479":1,"480":2}}],["presence",{"2":{"67":1,"405":1,"432":9}}],["prefixed",{"2":{"220":4}}],["prefix",{"2":{"185":1,"187":1,"190":2,"193":7,"218":1,"476":2}}],["preference",{"2":{"401":1}}],["preferable",{"2":{"327":1}}],["preferred",{"2":{"321":1}}],["preferrable",{"2":{"131":1,"132":1}}],["prefer",{"2":{"19":1,"108":1,"196":1,"233":1,"307":1,"327":1,"431":1}}],["predict",{"2":{"310":1}}],["predictedcount",{"2":{"269":1,"274":1}}],["predictive",{"2":{"166":1}}],["predictable",{"2":{"145":1}}],["preallocated",{"2":{"163":5}}],["pre",{"0":{"495":1},"2":{"85":1,"279":1,"317":1,"402":2,"441":1,"492":1}}],["preventing",{"2":{"397":1}}],["prevented",{"2":{"342":1}}],["prevent",{"2":{"211":1,"441":1,"482":1}}],["prevents",{"2":{"37":1,"112":1,"245":1,"267":1}}],["preview",{"2":{"245":1,"277":1}}],["previews",{"2":{"26":1}}],["previously",{"2":{"245":2,"264":1,"266":1,"270":1,"313":1,"327":1,"444":1}}],["previous",{"2":{"26":2,"45":1,"53":1,"179":1,"181":1,"192":1,"244":1,"245":2,"310":1,"412":2,"421":1}}],["prerequisites",{"0":{"1":1},"1":{"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1},"2":{"16":1,"293":1}}],["printf",{"2":{"378":1}}],["prints",{"2":{"378":1}}],["principle",{"2":{"308":1}}],["prime",{"2":{"164":1}}],["primitive",{"2":{"159":1}}],["primarily",{"2":{"61":1,"65":1,"73":1,"220":1,"288":1,"313":1}}],["primary",{"2":{"47":1,"148":1,"194":1,"204":1,"339":1}}],["private",{"2":{"18":1,"53":1,"130":1,"162":1,"233":1,"239":2,"241":3,"349":1,"366":1}}],["priorty",{"2":{"330":1}}],["priority",{"2":{"103":1,"157":3,"169":1,"320":4,"324":1,"330":5}}],["prior",{"0":{"5":1},"2":{"166":1,"326":1,"495":1}}],["prog",{"2":{"485":1}}],["progress",{"0":{"378":1},"2":{"242":1,"250":1,"309":1,"310":1,"485":1}}],["progresses",{"2":{"170":1,"183":1,"309":1}}],["progression",{"2":{"51":1}}],["programmers",{"2":{"280":1,"337":1}}],["programmer",{"2":{"267":2,"276":1,"310":1}}],["programmatic",{"2":{"211":1}}],["programming",{"2":{"24":1,"28":1,"29":1,"30":2}}],["program",{"2":{"39":1,"53":2,"279":1}}],["programs",{"2":{"31":2,"42":1,"221":1}}],["prohibited",{"2":{"432":1}}],["proximity",{"2":{"428":1,"431":1}}],["proxy",{"0":{"380":1},"2":{"204":1,"380":4}}],["profiles",{"2":{"401":1,"406":3,"407":1,"421":1,"425":1,"440":2,"441":2,"444":1,"445":2}}],["profilestartup",{"2":{"328":1}}],["profile",{"0":{"401":1},"2":{"401":4,"402":4,"405":1,"407":4,"408":1,"412":4,"416":1,"419":5,"421":2,"432":1,"435":3,"440":7,"441":14,"443":3,"445":4}}],["profiling",{"2":{"166":2}}],["proc",{"2":{"326":1}}],["procedure",{"2":{"337":1}}],["procedurally",{"2":{"84":1}}],["procedural",{"2":{"30":1}}],["proceeded",{"2":{"311":1}}],["proceed",{"2":{"308":1,"320":1,"326":1}}],["processes",{"2":{"70":1,"107":1,"131":1,"161":1,"162":1,"163":1,"172":1,"245":1,"270":1,"407":1,"483":1,"484":1,"486":1}}],["processed",{"2":{"48":1,"58":1,"64":1,"85":1,"90":1,"254":1,"270":4,"272":1,"308":1,"310":1,"326":1}}],["processor",{"2":{"39":1,"85":1}}],["processing",{"0":{"51":1},"2":{"24":1,"28":3,"29":4,"49":1,"51":1,"58":1,"72":1,"166":1,"256":1,"270":1,"273":1,"313":3,"317":1,"320":1,"322":1,"324":1,"340":1,"402":1,"427":1,"474":3,"477":1}}],["process",{"2":{"18":1,"73":2,"84":1,"85":1,"95":1,"143":1,"157":1,"162":2,"163":1,"164":1,"167":3,"172":2,"185":1,"195":1,"196":3,"209":1,"213":1,"214":2,"238":1,"241":2,"242":1,"251":1,"265":1,"266":2,"270":1,"276":1,"291":1,"308":1,"310":2,"311":1,"312":2,"313":1,"317":1,"326":5,"328":1,"339":2,"358":2,"359":1,"407":1,"448":2,"449":3,"451":1,"480":3,"492":1,"494":1,"496":1}}],["promote",{"2":{"448":1}}],["prominent",{"2":{"301":3}}],["prompts",{"0":{"337":1,"338":1,"339":1},"2":{"336":3,"337":1,"338":1,"339":3,"492":1}}],["prompt",{"0":{"336":1},"1":{"337":1,"338":1,"339":1},"2":{"240":1,"339":1,"492":1}}],["prometheus",{"2":{"175":1,"181":1,"183":1}}],["proved",{"2":{"309":1,"317":2}}],["proves",{"2":{"159":1}}],["provisioning",{"2":{"247":1}}],["provisional",{"2":{"106":1}}],["providing",{"2":{"102":1,"130":1,"166":1,"175":1,"269":1,"400":1,"436":1,"438":1,"441":1,"461":1,"474":1,"490":1,"492":1}}],["providers",{"2":{"330":1}}],["provider",{"0":{"358":1,"377":1},"2":{"196":1,"340":1,"358":2,"359":1,"400":1}}],["provided",{"2":{"69":1,"148":1,"155":1,"180":1,"182":1,"266":1,"270":1,"311":1,"400":1,"425":1,"427":1}}],["provides",{"2":{"29":1,"32":1,"87":1,"130":1,"148":2,"162":1,"164":1,"172":3,"174":1,"175":1,"176":1,"180":1,"189":1,"190":1,"192":1,"270":1,"272":2,"311":3,"330":1,"402":2,"490":2}}],["provide",{"2":{"27":1,"69":1,"73":1,"89":1,"94":1,"103":2,"128":1,"166":5,"172":4,"174":1,"179":1,"204":1,"205":1,"211":1,"263":1,"278":3,"279":3,"282":1,"283":1,"290":3,"292":1,"294":1,"308":1,"309":2,"337":1,"339":5,"397":1,"407":1,"446":1,"451":1}}],["protocol",{"2":{"131":1,"135":1,"204":1,"446":1}}],["protected",{"2":{"105":1,"131":1}}],["productprice",{"2":{"486":1,"487":1,"488":1}}],["product",{"2":{"337":1}}],["productivity",{"2":{"336":1}}],["production",{"2":{"247":2,"264":1,"310":4}}],["productions",{"2":{"100":1,"310":4}}],["producing",{"2":{"99":1,"433":1}}],["produced",{"2":{"105":1,"308":1,"331":1,"402":1}}],["produce",{"2":{"18":1,"81":1,"246":1,"308":2,"400":2,"476":1}}],["probably",{"2":{"72":1,"100":2,"131":1,"133":1,"192":1,"225":1,"285":1,"312":1,"326":3,"330":1,"427":1}}],["problematic",{"2":{"132":1}}],["problem",{"0":{"342":1},"2":{"50":1,"64":1,"103":3,"106":6,"162":1,"163":1,"267":1,"278":1,"310":1,"337":1,"431":1}}],["problems",{"2":{"24":1,"48":1,"49":1,"53":1,"58":1,"73":1,"100":2,"102":1,"105":2,"166":3,"245":1,"247":1,"271":1,"310":2,"327":1}}],["proposal",{"2":{"337":1}}],["proposition",{"2":{"278":1}}],["proportion",{"2":{"163":1}}],["proper",{"2":{"166":1,"277":1,"474":1}}],["properly",{"2":{"100":1,"239":1,"319":1}}],["property",{"2":{"44":7,"63":1,"69":3,"133":1,"190":1,"402":2,"406":1,"408":3,"410":1,"411":6,"412":3,"414":1,"415":6,"416":3,"432":4,"440":1,"441":8,"476":2,"479":1}}],["properties",{"0":{"63":1,"69":1},"2":{"43":2,"310":2,"342":1,"402":2,"407":1,"408":2,"412":2,"416":1,"432":2,"435":1,"438":1,"441":1}}],["proprietary",{"2":{"53":1,"243":1,"414":1}}],["projectedmeta",{"2":{"192":1}}],["project",{"0":{"40":1,"77":1,"276":1,"461":1},"1":{"41":1,"42":1,"43":1,"44":1,"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1},"2":{"16":3,"36":1,"44":2,"62":1,"64":1,"75":2,"77":2,"95":1,"102":1,"104":1,"120":1,"238":1,"246":1,"276":1,"289":1,"312":2,"337":1,"461":1}}],["projects",{"2":{"13":1,"44":1,"75":1,"76":4,"92":1,"217":1,"496":3}}],["g++",{"0":{"393":1},"2":{"393":1}}],["gnupg",{"2":{"241":1}}],["globbing",{"2":{"452":1}}],["globally",{"2":{"70":1,"309":2}}],["global",{"0":{"164":1},"2":{"44":1,"50":1,"51":2,"74":2,"84":1,"147":2,"149":2,"150":1,"164":1,"183":2,"262":1,"272":2,"308":2,"312":1,"318":1,"327":1,"329":1,"349":1}}],["glossary",{"0":{"273":1,"398":1},"1":{"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":1,"406":1,"407":1}}],["glue",{"2":{"172":2}}],["gctx",{"2":{"255":1,"266":1,"275":3}}],["gch",{"2":{"130":1}}],["gcc4",{"2":{"23":1}}],["gcc",{"2":{"7":2,"8":2,"10":2,"11":2}}],["ghp",{"2":{"133":1,"134":1}}],["ghalliday",{"2":{"130":1,"133":2,"134":1,"309":1,"310":1,"311":1,"312":1,"313":2}}],["gh",{"2":{"109":1,"131":2}}],["guarantee",{"2":{"145":1,"221":1}}],["guaranteed",{"2":{"103":1,"221":2,"310":1}}],["guard",{"2":{"106":1}}],["guess",{"2":{"103":1,"404":1}}],["guidlines",{"2":{"111":1}}],["guides",{"2":{"336":1}}],["guide",{"0":{"118":1,"294":1,"298":1,"349":1},"1":{"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":1,"126":1,"127":1,"128":1,"129":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1},"2":{"102":1,"105":1,"106":2,"198":6,"225":1,"229":1,"276":1,"278":2,"280":1,"299":1,"300":1,"337":2,"419":1,"451":1,"452":1}}],["guidelines",{"0":{"101":1,"107":1},"1":{"102":1,"103":1,"104":1,"105":1,"106":1,"108":1,"109":1,"110":1},"2":{"198":4,"221":1,"225":1,"276":2,"280":1,"281":1,"288":1,"299":1,"300":1,"339":1,"349":1}}],["guidance",{"2":{"0":1,"280":1}}],["ganglia",{"2":{"495":1}}],["gantt",{"2":{"26":1}}],["gallery",{"2":{"335":1}}],["gauges",{"2":{"185":1}}],["gauge",{"0":{"179":1},"2":{"174":1,"179":4}}],["gateway",{"2":{"169":1,"185":1,"327":9}}],["gathers",{"2":{"491":1}}],["gatheroptions",{"2":{"192":2}}],["gatherfileinformation",{"2":{"192":1}}],["gathered",{"2":{"170":1,"171":1,"192":1}}],["gather",{"2":{"70":1,"192":1,"449":2}}],["gathertablesused",{"2":{"69":2}}],["gain",{"2":{"160":1,"496":1}}],["gdb",{"2":{"56":1}}],["gominor",{"2":{"499":1}}],["gogold",{"2":{"497":2}}],["gorc",{"2":{"496":3,"498":1}}],["gold",{"0":{"497":1},"2":{"245":1,"497":2}}],["golden",{"2":{"53":1,"128":7}}],["goes",{"2":{"240":1}}],["googlecode",{"2":{"225":1}}],["google",{"2":{"225":2,"388":1}}],["good",{"0":{"339":1},"2":{"48":1,"66":1,"73":1,"76":1,"106":1,"108":1,"123":1,"128":3,"276":1,"277":1,"284":1,"285":1,"309":1,"319":1,"327":1,"330":1,"448":2,"450":1}}],["got",{"2":{"217":1}}],["go",{"2":{"103":1,"108":1,"124":1,"196":1,"241":1,"273":1,"287":1,"309":1,"322":1,"327":1,"330":2,"335":2,"342":2,"497":1}}],["goal",{"2":{"102":1,"337":1,"473":1}}],["goals",{"0":{"102":1},"2":{"101":1,"102":1,"145":1,"249":1}}],["god",{"2":{"84":1}}],["going",{"2":{"72":1,"73":1,"74":1,"90":1,"239":1,"241":1,"308":2,"319":1,"324":1,"327":1,"330":1,"497":1}}],["gif",{"2":{"353":2}}],["gi",{"2":{"328":2}}],["giant",{"2":{"84":1}}],["gives",{"2":{"170":1,"245":1}}],["give",{"2":{"58":1,"106":1,"124":1,"170":1,"174":1,"308":1,"328":1,"339":1,"448":1}}],["given",{"2":{"36":1,"38":4,"39":2,"59":1,"70":1,"87":1,"170":1,"184":1,"196":1,"241":1,"254":1,"311":1,"322":1,"324":1,"330":2,"339":1,"340":1,"419":2,"420":1,"424":1,"427":1,"428":1,"432":3,"437":1,"441":1,"485":1}}],["gitusername",{"2":{"133":2}}],["git",{"0":{"130":1},"1":{"131":1,"132":1,"133":1,"134":1},"2":{"13":1,"17":2,"23":1,"120":1,"130":3,"131":4,"133":5,"134":5,"494":3,"495":29}}],["github",{"2":{"0":1,"13":1,"17":1,"103":4,"107":1,"108":1,"109":4,"120":2,"128":1,"131":12,"132":1,"198":3,"238":1,"239":2,"240":2,"241":3,"277":3,"308":1,"309":2,"310":1,"311":1,"312":1,"313":3,"336":1,"388":1,"495":8}}],["green",{"2":{"239":1,"303":2}}],["greatly",{"2":{"69":1}}],["great",{"2":{"58":1,"194":1,"396":1}}],["greater",{"2":{"24":1}}],["groundwork",{"2":{"425":1}}],["group",{"2":{"190":4,"196":1,"204":1,"313":2,"335":2,"402":2}}],["groups`",{"2":{"335":1}}],["groupsbasedn",{"2":{"204":1}}],["groups",{"2":{"135":1,"194":1,"196":3,"204":1,"313":1,"452":1}}],["grouped",{"2":{"69":1,"191":2,"308":1,"309":2,"310":1}}],["grouping",{"2":{"69":1}}],["groan",{"2":{"195":1}}],["grow",{"2":{"170":1}}],["grant",{"2":{"452":1}}],["grandchild",{"2":{"65":4}}],["grained",{"2":{"270":1}}],["gracefully",{"2":{"366":1}}],["graceful",{"2":{"145":1}}],["grammar",{"2":{"85":1,"100":1,"310":6}}],["graphviz",{"2":{"447":1}}],["graph>",{"2":{"256":4,"269":4,"274":4}}],["graph1",{"2":{"256":1,"266":2,"269":2,"274":1,"275":1}}],["graph",{"0":{"58":1,"81":1,"256":1,"268":1,"269":1,"270":1},"1":{"59":1,"60":1,"61":1,"62":1,"63":1,"269":1,"270":1},"2":{"49":8,"51":3,"56":2,"58":3,"59":2,"64":1,"65":2,"66":3,"67":2,"68":2,"69":1,"70":11,"72":1,"73":2,"75":1,"77":1,"80":1,"81":3,"83":1,"84":1,"85":1,"88":1,"90":1,"96":2,"256":8,"262":1,"266":3,"268":3,"269":8,"270":11,"272":1,"273":2,"274":2,"310":3,"311":1,"313":3,"318":3,"328":3}}],["graphs>",{"2":{"274":2}}],["graphs",{"2":{"31":1,"51":1,"58":2,"61":1,"70":1,"79":1,"81":1,"86":1,"249":1,"250":1,"251":1,"256":1,"265":1,"266":1,"267":1,"268":1,"269":5,"310":1,"328":1,"447":1}}],["gt",{"2":{"20":2,"23":3,"36":1,"39":1,"43":1,"44":2,"53":5,"64":2,"66":1,"70":1,"74":3,"75":6,"76":2,"80":1,"91":6,"103":1,"109":1,"161":1,"183":2,"185":9,"187":1,"190":10,"191":14,"192":3,"196":4,"200":1,"221":6,"234":4,"240":1,"241":1,"245":4,"259":1,"265":5,"266":1,"269":20,"270":1,"308":6,"311":2,"312":1,"327":1,"393":1,"400":2,"439":1,"474":11,"477":4,"481":2,"482":3,"483":15,"484":9,"485":6,"486":1,"487":1,"488":1}}],["geared",{"2":{"317":1}}],["generic",{"0":{"337":1},"2":{"249":2,"408":1}}],["generalised",{"2":{"269":1}}],["generally",{"2":{"56":1,"58":1,"59":1,"67":1,"70":1,"74":1,"83":1,"84":4,"93":1,"132":1,"135":1,"155":1,"174":1,"186":1,"209":2,"221":1,"223":1,"270":1,"308":1,"437":1,"470":1}}],["general",{"0":{"103":1,"198":1,"280":1,"299":1,"333":1,"494":1},"2":{"26":1,"49":1,"69":1,"77":1,"81":2,"103":1,"105":1,"187":1,"196":1,"210":1,"287":1,"288":1,"308":1}}],["generative",{"2":{"337":1}}],["generating",{"0":{"240":1,"241":1},"2":{"36":1,"48":1,"238":1,"275":1,"310":1,"339":1}}],["generation",{"2":{"18":1,"36":1,"43":1,"56":1,"84":1,"86":1,"87":2,"91":1,"310":1,"317":1,"492":1}}],["generator",{"0":{"52":1,"312":1},"1":{"53":1,"54":1,"55":1,"56":1},"2":{"33":1,"47":1,"48":2,"53":1,"62":1,"63":1,"68":1,"79":1,"86":1,"87":1,"93":1,"99":1,"199":1,"269":1,"271":1,"308":2,"309":1,"311":3,"312":3,"313":2,"317":1,"318":1}}],["generates",{"2":{"53":1,"56":1,"89":1,"92":1,"129":1,"195":1,"254":1,"261":1,"312":2,"328":1,"339":1,"486":1}}],["generated",{"0":{"43":1,"83":1,"86":1,"257":1,"275":1},"1":{"87":1,"88":1,"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":1},"2":{"43":2,"53":1,"56":1,"65":2,"70":1,"79":4,"80":2,"81":1,"83":3,"84":1,"86":1,"87":2,"88":3,"90":1,"91":1,"92":1,"96":1,"129":1,"167":1,"184":1,"195":1,"197":1,"200":1,"238":1,"249":3,"250":1,"252":1,"255":2,"257":1,"258":1,"262":1,"266":2,"269":4,"270":6,"271":5,"272":5,"274":1,"310":1,"311":4,"312":3,"328":2,"339":1,"447":1,"475":1,"480":1}}],["generate",{"2":{"21":1,"38":1,"39":1,"48":1,"51":2,"56":1,"82":1,"83":1,"92":1,"96":1,"99":1,"170":1,"238":1,"239":1,"240":6,"241":2,"242":2,"247":2,"274":1,"312":6,"328":2,"339":1,"471":9,"476":1,"478":1,"482":2,"492":1}}],["genrsa",{"2":{"240":1}}],["geosoft",{"2":{"217":1}}],["getmarkupvalueinfo",{"2":{"428":3}}],["getrange",{"2":{"311":1,"313":1}}],["getnumdivisions",{"2":{"311":1,"312":1}}],["getchilddatasettype",{"2":{"310":1}}],["getclear",{"2":{"269":1,"275":1}}],["getactivityversion",{"2":{"275":1}}],["getannotationkind",{"2":{"67":1}}],["getlayout5",{"2":{"269":1,"275":2}}],["getindexlayout",{"2":{"269":1,"275":1}}],["getinfoflags2",{"2":{"69":1}}],["getinfoflags",{"2":{"69":1}}],["getflags",{"2":{"311":2,"312":1,"313":1}}],["getfilename",{"2":{"269":2,"275":1}}],["getfileinfomation",{"2":{"192":1}}],["getformatcrc",{"2":{"269":1,"275":1}}],["getfoo",{"2":{"221":1}}],["getscore",{"2":{"311":2}}],["getskew",{"2":{"311":1}}],["getsequence",{"2":{"275":1}}],["getself",{"2":{"269":1,"275":1}}],["getstr",{"2":{"269":2,"275":2}}],["gets",{"2":{"136":1,"221":1,"491":1}}],["gettranslatedexpr",{"2":{"90":1}}],["getting",{"0":{"25":1,"120":1},"2":{"94":1,"196":1,"217":1,"320":1,"330":1}}],["getoperator",{"2":{"59":1}}],["getpackagerevisionarch",{"2":{"36":1}}],["get",{"0":{"17":1,"368":1},"2":{"3":1,"4":1,"5":1,"15":1,"16":1,"17":1,"22":1,"23":1,"44":3,"58":1,"84":1,"90":1,"122":3,"155":1,"174":1,"184":1,"186":3,"238":2,"310":1,"329":1,"330":1,"336":1,"339":1,"340":4,"342":1,"351":6,"356":1,"358":2,"359":1,"367":1,"368":2,"376":2,"378":1,"381":1,"382":3,"383":2,"390":1,"420":1,"471":4}}],["gpg",{"2":{"18":2,"241":4}}],["g",{"2":{"13":1,"53":1,"54":1,"58":2,"59":1,"62":3,"63":2,"64":2,"65":2,"67":1,"68":2,"69":5,"70":5,"73":3,"75":1,"76":2,"77":1,"81":3,"83":2,"84":1,"85":1,"87":2,"88":2,"89":1,"91":1,"92":2,"93":2,"94":1,"100":1,"104":1,"105":1,"106":3,"108":3,"109":1,"110":1,"130":1,"131":1,"134":1,"145":1,"149":1,"160":1,"163":2,"191":2,"192":3,"194":2,"195":2,"196":2,"220":1,"245":2,"249":1,"250":1,"254":2,"255":1,"258":1,"263":1,"264":2,"266":2,"269":10,"270":5,"272":3,"308":1,"309":3,"310":8,"311":2,"312":4,"313":2,"319":1,"321":1,"328":1,"397":1,"448":1,"496":1,"497":1}}],["gzip",{"2":{"368":2,"387":1,"390":1,"460":2,"467":1}}],["gz",{"2":{"5":1,"16":6,"345":1}}],["hf674dtrmd4z1s",{"2":{"449":1}}],["hh",{"2":{"353":1}}],["h>",{"2":{"351":1,"367":1,"394":4}}],["hubsextension",{"2":{"335":1}}],["hugeadm",{"2":{"163":1}}],["huge",{"0":{"150":1,"163":1},"2":{"150":1,"160":2,"163":11,"326":1}}],["hyphenated",{"2":{"302":1,"306":1}}],["hyphen",{"2":{"301":1,"303":1,"306":1}}],["htm",{"2":{"353":1}}],["html",{"2":{"113":1,"114":1,"115":1,"217":1,"353":2,"355":1,"446":1,"447":4,"449":4}}],["htpasswd",{"0":{"212":1},"2":{"212":3,"451":1}}],["hthor",{"0":{"458":1},"2":{"130":1,"194":1,"249":1,"251":1,"264":1,"265":1,"266":2,"268":2,"275":1,"301":1,"313":1,"314":1,"327":1,"458":3,"460":2}}],["httpbin",{"2":{"381":1}}],["httplib",{"0":{"350":1,"391":1},"1":{"351":1,"352":1,"353":1,"354":1,"355":1,"356":1,"357":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"367":1,"368":1,"369":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1,"385":1,"386":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"394":1,"395":1,"396":1},"2":{"350":1,"351":2,"367":6,"381":3,"385":1,"391":2,"394":3}}],["httpasswd",{"0":{"206":1}}],["http",{"0":{"368":1},"2":{"16":1,"32":1,"114":1,"115":1,"186":1,"217":1,"225":1,"274":1,"302":1,"350":1,"367":2,"400":1,"439":1,"449":1,"458":5,"460":15,"486":5,"487":5,"488":5,"491":1}}],["https",{"2":{"0":1,"5":1,"13":2,"15":1,"16":2,"17":1,"131":6,"308":3,"309":1,"310":2,"311":1,"312":1,"313":2,"335":1,"340":4,"350":1,"367":1,"388":1,"446":1,"447":2,"449":6,"451":2,"482":1}}],["hpp",{"0":{"272":1},"2":{"56":2,"58":1,"81":2,"187":1,"218":3,"250":1,"257":1,"258":1,"260":1,"269":3,"270":1,"272":1,"275":3,"310":2,"311":4,"397":3}}],["hpcc4j",{"2":{"495":1}}],["hpccdata",{"2":{"340":1}}],["hpccadmin",{"2":{"204":2}}],["hpccadmins",{"2":{"204":1}}],["hpccmetrics",{"2":{"187":2}}],["hpccsystems",{"2":{"22":1,"34":1,"123":1,"134":2,"193":7,"240":1,"302":2,"307":1,"308":3,"310":1,"334":2,"338":1,"451":1,"458":1,"460":1}}],["hpcc",{"0":{"17":1,"119":1,"130":1,"236":1,"276":1,"298":1,"301":1,"334":1,"451":1,"452":1},"1":{"131":1,"132":1,"133":1,"134":1,"277":1,"278":1,"279":1,"280":1,"281":1,"282":1,"283":1,"284":1,"285":1,"286":1,"287":1,"288":1,"289":1,"290":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1,"453":1,"454":1},"2":{"0":2,"17":2,"18":7,"21":1,"23":2,"24":2,"25":1,"26":1,"27":2,"29":1,"37":4,"44":1,"53":6,"54":1,"95":1,"107":1,"108":1,"114":2,"115":1,"119":1,"120":1,"122":2,"123":2,"125":1,"130":1,"133":1,"134":2,"163":1,"166":1,"191":1,"196":1,"197":1,"198":1,"205":1,"216":1,"217":5,"234":1,"238":6,"240":7,"242":6,"243":4,"249":3,"250":1,"254":1,"280":1,"298":1,"300":3,"301":3,"302":1,"308":5,"309":1,"310":2,"311":2,"312":1,"313":2,"328":1,"331":3,"334":1,"340":4,"446":5,"448":3,"449":1,"451":2,"452":1,"460":2,"461":2,"470":1,"471":1,"473":1,"474":1,"476":1,"486":6,"487":4,"488":4,"490":1,"495":12}}],["hqlgram2",{"2":{"310":1}}],["hqlgram",{"2":{"310":1}}],["hqllex",{"2":{"310":1}}],["hqllookupcontext",{"2":{"61":1}}],["hqlmeta",{"2":{"310":1}}],["hqlfold",{"2":{"310":1}}],["hqlatoms",{"2":{"310":2}}],["hqlattr",{"2":{"69":1,"310":2}}],["hqlcpp",{"2":{"96":1}}],["hqlcpptranslator",{"2":{"84":1}}],["hqltcppc",{"2":{"94":1}}],["hqlthql",{"2":{"56":1}}],["hqlexpr",{"2":{"56":2,"58":1,"310":2}}],["hqlir",{"2":{"56":2,"310":1}}],["hql",{"2":{"56":5,"96":1}}],["hot",{"2":{"326":2}}],["hole",{"2":{"317":1}}],["hold",{"2":{"299":1}}],["holding",{"2":{"159":1,"317":1}}],["holds",{"2":{"51":1}}],["hoares",{"2":{"314":2}}],["hope",{"2":{"308":1,"347":1}}],["hopefully",{"2":{"308":1}}],["hour",{"2":{"254":1}}],["house",{"2":{"234":1}}],["hostname",{"2":{"340":1}}],["hostgroup",{"2":{"190":2,"193":1}}],["hostgroups",{"2":{"190":1,"192":1}}],["host",{"2":{"139":1,"143":1,"144":4,"190":6,"193":1,"326":1,"340":1,"367":1,"380":1,"384":1,"407":2}}],["hosts",{"0":{"139":1,"144":1},"2":{"143":1,"144":1,"190":5,"193":7}}],["hosted",{"2":{"120":1}}],["how",{"0":{"251":1,"318":1,"320":1,"339":1,"341":1},"1":{"342":1},"2":{"56":1,"59":1,"81":1,"94":1,"102":2,"103":1,"114":1,"120":1,"131":1,"143":1,"148":1,"149":1,"166":1,"168":1,"169":1,"170":1,"172":1,"175":1,"184":1,"185":1,"186":1,"190":1,"191":1,"192":1,"196":4,"198":2,"201":1,"206":1,"210":2,"223":1,"234":1,"249":6,"251":1,"256":1,"269":2,"270":2,"277":1,"278":4,"282":1,"294":1,"296":1,"299":1,"308":5,"310":6,"311":1,"318":2,"319":1,"320":2,"322":1,"326":4,"328":1,"330":2,"333":1,"335":2,"336":1,"337":2,"340":9,"400":1,"402":1,"404":1,"428":1,"437":1,"439":1,"474":1,"477":1}}],["however",{"2":{"51":1,"66":1,"67":1,"68":1,"69":1,"74":1,"92":1,"106":1,"149":1,"169":1,"170":2,"188":1,"189":1,"204":1,"210":1,"219":1,"264":1,"266":1,"276":1,"281":1,"282":1,"287":1,"307":1,"308":3,"310":1,"318":1,"320":1,"321":1,"326":2,"327":1,"400":1,"446":1,"470":1}}],["home",{"2":{"53":4,"117":1,"460":1}}],["h",{"0":{"391":2},"2":{"23":1,"36":2,"189":1,"194":2,"196":3,"218":3,"350":1,"353":1,"371":1,"391":1,"394":3,"397":1}}],["hirose",{"2":{"395":1}}],["hiredis",{"2":{"7":1,"8":1}}],["hi",{"2":{"351":1,"367":1,"368":2}}],["him",{"2":{"307":1}}],["history",{"2":{"199":1}}],["historically",{"2":{"95":1,"330":1}}],["historical",{"2":{"31":1,"166":1,"312":1,"324":1}}],["histogram",{"0":{"181":1,"182":1},"2":{"170":3,"182":3}}],["hit",{"2":{"100":1,"217":1,"234":1,"310":1,"326":1}}],["hiddenoption",{"2":{"486":2,"487":2,"488":1}}],["hidden",{"2":{"72":1,"262":1,"486":1,"487":1}}],["hides",{"2":{"250":1,"258":1}}],["hide",{"2":{"58":1,"93":1,"94":1,"172":1}}],["hierarchy",{"2":{"58":1,"91":1,"92":1,"234":1,"481":1}}],["highest",{"2":{"254":1,"346":1,"441":2}}],["higher",{"2":{"143":1,"320":2,"330":1}}],["highlights",{"2":{"474":1}}],["highlighted",{"2":{"308":1}}],["highlight",{"2":{"269":1,"308":1,"310":1}}],["highlighting",{"2":{"128":1,"270":1,"292":1,"312":1}}],["highly",{"2":{"66":1}}],["high",{"2":{"29":1,"149":1,"169":1,"221":1,"330":1,"397":1}}],["hints",{"0":{"56":1},"2":{"81":1,"195":1,"269":1,"270":1,"327":2,"333":1}}],["hint",{"2":{"22":1,"327":1,"419":1,"423":1,"476":1}}],["half",{"2":{"466":1}}],["hallucinations",{"0":{"339":1},"2":{"339":5}}],["harm",{"2":{"327":1}}],["hardware",{"2":{"365":1,"461":1}}],["hardened",{"2":{"246":1}}],["harder",{"2":{"56":1,"104":1,"106":1}}],["hardcoded",{"2":{"140":1}}],["hard",{"2":{"48":1,"56":1,"69":1,"84":1,"103":1,"106":5,"277":1}}],["hairy",{"2":{"319":1}}],["handy",{"2":{"325":1}}],["hand",{"2":{"249":2,"425":1}}],["handler",{"0":{"355":1,"360":1},"2":{"355":1,"360":2}}],["handle",{"2":{"187":2,"323":2,"330":1,"450":2}}],["handles",{"2":{"142":1,"172":2,"187":1,"210":1,"321":1,"323":2,"450":1}}],["handled",{"2":{"85":1,"95":1,"172":1,"187":1,"234":1,"435":1,"452":1,"474":1}}],["handling",{"0":{"144":1},"2":{"45":3,"49":1,"143":1,"172":1,"176":1,"204":1,"278":1,"329":1,"461":1,"462":2,"469":1}}],["happen",{"2":{"326":1}}],["happens",{"2":{"266":1,"278":1,"307":1,"309":1,"310":1,"319":1,"320":1,"326":1,"448":1,"498":1}}],["happened",{"2":{"217":1}}],["happy",{"2":{"128":1}}],["had",{"2":{"145":1,"158":1,"245":1,"299":1,"317":3,"320":1,"324":1}}],["hadoop",{"2":{"24":1}}],["having",{"2":{"103":1,"131":1,"153":1,"233":1,"310":1,"325":1,"327":1,"347":1}}],["haven",{"2":{"217":1,"266":1}}],["have",{"2":{"18":2,"22":1,"44":2,"49":1,"58":1,"59":3,"65":4,"69":1,"70":1,"72":1,"73":2,"74":1,"75":2,"85":2,"88":2,"91":1,"100":1,"102":4,"103":4,"105":1,"106":4,"108":2,"109":1,"112":1,"115":1,"126":2,"128":2,"130":1,"131":1,"139":1,"149":1,"155":5,"157":1,"160":1,"161":1,"163":2,"174":1,"183":1,"184":2,"186":2,"187":1,"192":1,"195":1,"196":4,"214":1,"217":1,"221":1,"233":2,"242":2,"245":2,"246":1,"249":1,"263":1,"266":2,"269":2,"270":1,"271":1,"272":1,"284":1,"299":1,"307":1,"308":4,"309":2,"310":3,"311":1,"312":2,"313":1,"319":1,"320":2,"321":2,"325":1,"327":1,"329":1,"330":5,"335":1,"340":3,"349":2,"432":1,"447":1,"448":2,"451":2,"453":3,"454":3,"480":1,"482":1,"497":1}}],["hasrule",{"2":{"424":1}}],["hasvaluetype",{"2":{"420":1}}],["hasproperty",{"2":{"408":1}}],["hasproperties",{"2":{"408":1}}],["haslocalcopy",{"2":{"220":4}}],["hasunknowntransform",{"2":{"69":1}}],["hasattribute",{"2":{"63":1}}],["hash=",{"2":{"274":1}}],["hashed",{"2":{"212":2,"213":1}}],["hashes",{"2":{"190":1,"310":1}}],["hash",{"2":{"58":1,"190":1,"191":1,"320":2,"327":1,"448":2}}],["has",{"2":{"13":1,"21":1,"23":1,"48":1,"49":1,"53":1,"56":1,"58":3,"59":1,"61":1,"62":2,"63":1,"64":1,"69":1,"81":3,"82":1,"87":1,"88":1,"90":3,"91":1,"103":6,"106":8,"108":3,"109":1,"114":1,"128":1,"143":3,"147":1,"149":3,"157":1,"161":1,"162":1,"163":1,"164":1,"169":1,"175":1,"184":1,"190":2,"192":3,"196":4,"205":1,"216":1,"217":1,"219":1,"221":2,"233":1,"234":1,"245":1,"254":3,"257":1,"264":2,"266":5,"267":2,"269":3,"270":2,"272":1,"288":1,"308":3,"309":1,"310":6,"311":2,"312":1,"313":4,"318":1,"320":2,"324":1,"326":1,"330":3,"351":2,"356":1,"412":1,"432":1,"441":1,"453":3,"454":3,"455":1,"498":1}}],["hence",{"2":{"313":1}}],["her",{"2":{"307":1}}],["here",{"2":{"15":1,"18":1,"20":1,"22":1,"41":1,"53":1,"64":3,"106":1,"221":1,"238":1,"240":1,"273":1,"276":1,"277":1,"281":1,"310":1,"321":1,"326":1,"337":1,"338":2,"339":1,"416":1,"474":1,"479":1}}],["heat",{"2":{"326":1}}],["heavily",{"2":{"321":1}}],["healthcare",{"2":{"339":1}}],["health",{"2":{"166":1,"170":2,"339":1}}],["heaplets",{"2":{"149":1,"155":1}}],["heaplet",{"2":{"149":6,"153":1,"154":1,"155":1,"156":3,"161":2}}],["heap",{"0":{"150":1},"2":{"148":1,"149":6,"150":2,"152":1,"153":3,"154":1,"155":2,"161":2,"330":1}}],["heaps",{"0":{"149":1,"151":1,"161":1},"1":{"152":1,"153":1,"154":1,"155":1,"156":1},"2":{"145":1,"148":1,"149":1,"152":2,"153":2,"155":1,"159":2}}],["heading",{"2":{"99":1}}],["header",{"2":{"83":1,"153":1,"155":1,"187":1,"218":4,"233":1,"320":2,"330":2,"350":1,"351":3,"360":1,"381":4,"400":1}}],["headers",{"0":{"368":1},"2":{"12":1,"23":1,"36":1,"45":2,"96":1,"128":2,"218":1,"368":4,"376":1,"439":1}}],["hello",{"2":{"351":1,"371":1,"382":1}}],["helm",{"2":{"133":1,"183":1,"190":2,"196":1,"202":1,"340":1,"495":2}}],["held",{"2":{"58":1,"106":1,"149":1,"150":1,"161":1}}],["helpful",{"2":{"108":1,"333":2}}],["helpers",{"0":{"257":1},"2":{"83":2,"88":1,"269":1,"271":1}}],["helper",{"0":{"88":1,"89":1},"2":{"69":2,"81":1,"83":1,"84":2,"88":4,"92":1,"192":1,"195":2,"250":1,"257":3,"266":1,"269":6,"270":8,"272":1,"311":4,"312":5,"313":5,"318":1}}],["helps",{"2":{"58":2,"232":1,"310":1,"339":1}}],["help",{"2":{"0":1,"65":1,"83":1,"84":1,"106":1,"145":1,"147":1,"148":1,"163":1,"201":1,"233":1,"245":1,"276":3,"277":1,"281":1,"299":1,"309":1,"311":1,"312":1,"328":1,"333":1,"336":1,"337":2,"339":1,"485":1}}]],"serializationVersion":2}';export{e as default}; diff --git a/assets/chunks/VPLocalSearchBox.CrIhCP-G.js b/assets/chunks/VPLocalSearchBox.CrIhCP-G.js new file mode 100644 index 00000000000..77c861701d2 --- /dev/null +++ b/assets/chunks/VPLocalSearchBox.CrIhCP-G.js @@ -0,0 +1,7 @@ +var Nt=Object.defineProperty;var Ft=(a,e,t)=>e in a?Nt(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var Ce=(a,e,t)=>Ft(a,typeof e!="symbol"?e+"":e,t);import{V as Ot,p as ne,h as ve,ah as Xe,ai as Rt,aj as Ct,q as Ve,ak as Mt,d as At,D as we,al as et,am as Lt,an as Dt,s as zt,ao as Pt,v as Me,P as de,O as xe,ap as jt,aq as Vt,W as $t,R as Bt,$ as Wt,o as q,b as Kt,j as S,a0 as Jt,k as D,ar as Ut,as as qt,at as Gt,c as Y,n as tt,e as Se,C as st,F as nt,a as he,t as fe,au as Ht,av as it,aw as Qt,a7 as Yt,ad as Zt,ax as Xt,_ as es}from"./framework.DkhCEVKm.js";import{u as ts,c as ss}from"./theme.B_cCIxVx.js";const ns={root:()=>Ot(()=>import("./@localSearchIndexroot.h6lM-Azh.js"),[])};/*! +* tabbable 6.2.0 +* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE +*/var vt=["input:not([inert])","select:not([inert])","textarea:not([inert])","a[href]:not([inert])","button:not([inert])","[tabindex]:not(slot):not([inert])","audio[controls]:not([inert])","video[controls]:not([inert])",'[contenteditable]:not([contenteditable="false"]):not([inert])',"details>summary:first-of-type:not([inert])","details:not([inert])"],ke=vt.join(","),mt=typeof Element>"u",re=mt?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,Ne=!mt&&Element.prototype.getRootNode?function(a){var e;return a==null||(e=a.getRootNode)===null||e===void 0?void 0:e.call(a)}:function(a){return a==null?void 0:a.ownerDocument},Fe=function a(e,t){var s;t===void 0&&(t=!0);var n=e==null||(s=e.getAttribute)===null||s===void 0?void 0:s.call(e,"inert"),r=n===""||n==="true",i=r||t&&e&&a(e.parentNode);return i},is=function(e){var t,s=e==null||(t=e.getAttribute)===null||t===void 0?void 0:t.call(e,"contenteditable");return s===""||s==="true"},gt=function(e,t,s){if(Fe(e))return[];var n=Array.prototype.slice.apply(e.querySelectorAll(ke));return t&&re.call(e,ke)&&n.unshift(e),n=n.filter(s),n},bt=function a(e,t,s){for(var n=[],r=Array.from(e);r.length;){var i=r.shift();if(!Fe(i,!1))if(i.tagName==="SLOT"){var o=i.assignedElements(),l=o.length?o:i.children,c=a(l,!0,s);s.flatten?n.push.apply(n,c):n.push({scopeParent:i,candidates:c})}else{var h=re.call(i,ke);h&&s.filter(i)&&(t||!e.includes(i))&&n.push(i);var v=i.shadowRoot||typeof s.getShadowRoot=="function"&&s.getShadowRoot(i),p=!Fe(v,!1)&&(!s.shadowRootFilter||s.shadowRootFilter(i));if(v&&p){var b=a(v===!0?i.children:v.children,!0,s);s.flatten?n.push.apply(n,b):n.push({scopeParent:i,candidates:b})}else r.unshift.apply(r,i.children)}}return n},yt=function(e){return!isNaN(parseInt(e.getAttribute("tabindex"),10))},ie=function(e){if(!e)throw new Error("No node provided");return e.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(e.tagName)||is(e))&&!yt(e)?0:e.tabIndex},rs=function(e,t){var s=ie(e);return s<0&&t&&!yt(e)?0:s},as=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},wt=function(e){return e.tagName==="INPUT"},os=function(e){return wt(e)&&e.type==="hidden"},ls=function(e){var t=e.tagName==="DETAILS"&&Array.prototype.slice.apply(e.children).some(function(s){return s.tagName==="SUMMARY"});return t},cs=function(e,t){for(var s=0;ssummary:first-of-type"),i=r?e.parentElement:e;if(re.call(i,"details:not([open]) *"))return!0;if(!s||s==="full"||s==="legacy-full"){if(typeof n=="function"){for(var o=e;e;){var l=e.parentElement,c=Ne(e);if(l&&!l.shadowRoot&&n(l)===!0)return rt(e);e.assignedSlot?e=e.assignedSlot:!l&&c!==e.ownerDocument?e=c.host:e=l}e=o}if(fs(e))return!e.getClientRects().length;if(s!=="legacy-full")return!0}else if(s==="non-zero-area")return rt(e);return!1},vs=function(e){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(e.tagName))for(var t=e.parentElement;t;){if(t.tagName==="FIELDSET"&&t.disabled){for(var s=0;s=0)},gs=function a(e){var t=[],s=[];return e.forEach(function(n,r){var i=!!n.scopeParent,o=i?n.scopeParent:n,l=rs(o,i),c=i?a(n.candidates):o;l===0?i?t.push.apply(t,c):t.push(o):s.push({documentOrder:r,tabIndex:l,item:n,isScope:i,content:c})}),s.sort(as).reduce(function(n,r){return r.isScope?n.push.apply(n,r.content):n.push(r.content),n},[]).concat(t)},bs=function(e,t){t=t||{};var s;return t.getShadowRoot?s=bt([e],t.includeContainer,{filter:$e.bind(null,t),flatten:!1,getShadowRoot:t.getShadowRoot,shadowRootFilter:ms}):s=gt(e,t.includeContainer,$e.bind(null,t)),gs(s)},ys=function(e,t){t=t||{};var s;return t.getShadowRoot?s=bt([e],t.includeContainer,{filter:Oe.bind(null,t),flatten:!0,getShadowRoot:t.getShadowRoot}):s=gt(e,t.includeContainer,Oe.bind(null,t)),s},ae=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return re.call(e,ke)===!1?!1:$e(t,e)},ws=vt.concat("iframe").join(","),Ae=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return re.call(e,ws)===!1?!1:Oe(t,e)};/*! +* focus-trap 7.6.0 +* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE +*/function xs(a,e,t){return(e=_s(e))in a?Object.defineProperty(a,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):a[e]=t,a}function at(a,e){var t=Object.keys(a);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(a);e&&(s=s.filter(function(n){return Object.getOwnPropertyDescriptor(a,n).enumerable})),t.push.apply(t,s)}return t}function ot(a){for(var e=1;e0){var s=e[e.length-1];s!==t&&s.pause()}var n=e.indexOf(t);n===-1||e.splice(n,1),e.push(t)},deactivateTrap:function(e,t){var s=e.indexOf(t);s!==-1&&e.splice(s,1),e.length>0&&e[e.length-1].unpause()}},Es=function(e){return e.tagName&&e.tagName.toLowerCase()==="input"&&typeof e.select=="function"},Ts=function(e){return(e==null?void 0:e.key)==="Escape"||(e==null?void 0:e.key)==="Esc"||(e==null?void 0:e.keyCode)===27},me=function(e){return(e==null?void 0:e.key)==="Tab"||(e==null?void 0:e.keyCode)===9},Is=function(e){return me(e)&&!e.shiftKey},ks=function(e){return me(e)&&e.shiftKey},ct=function(e){return setTimeout(e,0)},ut=function(e,t){var s=-1;return e.every(function(n,r){return t(n)?(s=r,!1):!0}),s},pe=function(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n1?g-1:0),E=1;E=0)d=s.activeElement;else{var u=i.tabbableGroups[0],g=u&&u.firstTabbableNode;d=g||h("fallbackFocus")}if(!d)throw new Error("Your focus-trap needs to have at least one focusable element");return d},p=function(){if(i.containerGroups=i.containers.map(function(d){var u=bs(d,r.tabbableOptions),g=ys(d,r.tabbableOptions),_=u.length>0?u[0]:void 0,E=u.length>0?u[u.length-1]:void 0,N=g.find(function(f){return ae(f)}),F=g.slice().reverse().find(function(f){return ae(f)}),m=!!u.find(function(f){return ie(f)>0});return{container:d,tabbableNodes:u,focusableNodes:g,posTabIndexesFound:m,firstTabbableNode:_,lastTabbableNode:E,firstDomTabbableNode:N,lastDomTabbableNode:F,nextTabbableNode:function(I){var A=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,C=u.indexOf(I);return C<0?A?g.slice(g.indexOf(I)+1).find(function(M){return ae(M)}):g.slice(0,g.indexOf(I)).reverse().find(function(M){return ae(M)}):u[C+(A?1:-1)]}}}),i.tabbableGroups=i.containerGroups.filter(function(d){return d.tabbableNodes.length>0}),i.tabbableGroups.length<=0&&!h("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times");if(i.containerGroups.find(function(d){return d.posTabIndexesFound})&&i.containerGroups.length>1)throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.")},b=function(d){var u=d.activeElement;if(u)return u.shadowRoot&&u.shadowRoot.activeElement!==null?b(u.shadowRoot):u},y=function(d){if(d!==!1&&d!==b(document)){if(!d||!d.focus){y(v());return}d.focus({preventScroll:!!r.preventScroll}),i.mostRecentlyFocusedNode=d,Es(d)&&d.select()}},x=function(d){var u=h("setReturnFocus",d);return u||(u===!1?!1:d)},w=function(d){var u=d.target,g=d.event,_=d.isBackward,E=_===void 0?!1:_;u=u||_e(g),p();var N=null;if(i.tabbableGroups.length>0){var F=c(u,g),m=F>=0?i.containerGroups[F]:void 0;if(F<0)E?N=i.tabbableGroups[i.tabbableGroups.length-1].lastTabbableNode:N=i.tabbableGroups[0].firstTabbableNode;else if(E){var f=ut(i.tabbableGroups,function(T){var P=T.firstTabbableNode;return u===P});if(f<0&&(m.container===u||Ae(u,r.tabbableOptions)&&!ae(u,r.tabbableOptions)&&!m.nextTabbableNode(u,!1))&&(f=F),f>=0){var I=f===0?i.tabbableGroups.length-1:f-1,A=i.tabbableGroups[I];N=ie(u)>=0?A.lastTabbableNode:A.lastDomTabbableNode}else me(g)||(N=m.nextTabbableNode(u,!1))}else{var C=ut(i.tabbableGroups,function(T){var P=T.lastTabbableNode;return u===P});if(C<0&&(m.container===u||Ae(u,r.tabbableOptions)&&!ae(u,r.tabbableOptions)&&!m.nextTabbableNode(u))&&(C=F),C>=0){var M=C===i.tabbableGroups.length-1?0:C+1,j=i.tabbableGroups[M];N=ie(u)>=0?j.firstTabbableNode:j.firstDomTabbableNode}else me(g)||(N=m.nextTabbableNode(u))}}else N=h("fallbackFocus");return N},O=function(d){var u=_e(d);if(!(c(u,d)>=0)){if(pe(r.clickOutsideDeactivates,d)){o.deactivate({returnFocus:r.returnFocusOnDeactivate});return}pe(r.allowOutsideClick,d)||d.preventDefault()}},R=function(d){var u=_e(d),g=c(u,d)>=0;if(g||u instanceof Document)g&&(i.mostRecentlyFocusedNode=u);else{d.stopImmediatePropagation();var _,E=!0;if(i.mostRecentlyFocusedNode)if(ie(i.mostRecentlyFocusedNode)>0){var N=c(i.mostRecentlyFocusedNode),F=i.containerGroups[N].tabbableNodes;if(F.length>0){var m=F.findIndex(function(f){return f===i.mostRecentlyFocusedNode});m>=0&&(r.isKeyForward(i.recentNavEvent)?m+1=0&&(_=F[m-1],E=!1))}}else i.containerGroups.some(function(f){return f.tabbableNodes.some(function(I){return ie(I)>0})})||(E=!1);else E=!1;E&&(_=w({target:i.mostRecentlyFocusedNode,isBackward:r.isKeyBackward(i.recentNavEvent)})),y(_||i.mostRecentlyFocusedNode||v())}i.recentNavEvent=void 0},K=function(d){var u=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;i.recentNavEvent=d;var g=w({event:d,isBackward:u});g&&(me(d)&&d.preventDefault(),y(g))},G=function(d){(r.isKeyForward(d)||r.isKeyBackward(d))&&K(d,r.isKeyBackward(d))},W=function(d){Ts(d)&&pe(r.escapeDeactivates,d)!==!1&&(d.preventDefault(),o.deactivate())},V=function(d){var u=_e(d);c(u,d)>=0||pe(r.clickOutsideDeactivates,d)||pe(r.allowOutsideClick,d)||(d.preventDefault(),d.stopImmediatePropagation())},$=function(){if(i.active)return lt.activateTrap(n,o),i.delayInitialFocusTimer=r.delayInitialFocus?ct(function(){y(v())}):y(v()),s.addEventListener("focusin",R,!0),s.addEventListener("mousedown",O,{capture:!0,passive:!1}),s.addEventListener("touchstart",O,{capture:!0,passive:!1}),s.addEventListener("click",V,{capture:!0,passive:!1}),s.addEventListener("keydown",G,{capture:!0,passive:!1}),s.addEventListener("keydown",W),o},ge=function(){if(i.active)return s.removeEventListener("focusin",R,!0),s.removeEventListener("mousedown",O,!0),s.removeEventListener("touchstart",O,!0),s.removeEventListener("click",V,!0),s.removeEventListener("keydown",G,!0),s.removeEventListener("keydown",W),o},L=function(d){var u=d.some(function(g){var _=Array.from(g.removedNodes);return _.some(function(E){return E===i.mostRecentlyFocusedNode})});u&&y(v())},H=typeof window<"u"&&"MutationObserver"in window?new MutationObserver(L):void 0,J=function(){H&&(H.disconnect(),i.active&&!i.paused&&i.containers.map(function(d){H.observe(d,{subtree:!0,childList:!0})}))};return o={get active(){return i.active},get paused(){return i.paused},activate:function(d){if(i.active)return this;var u=l(d,"onActivate"),g=l(d,"onPostActivate"),_=l(d,"checkCanFocusTrap");_||p(),i.active=!0,i.paused=!1,i.nodeFocusedBeforeActivation=s.activeElement,u==null||u();var E=function(){_&&p(),$(),J(),g==null||g()};return _?(_(i.containers.concat()).then(E,E),this):(E(),this)},deactivate:function(d){if(!i.active)return this;var u=ot({onDeactivate:r.onDeactivate,onPostDeactivate:r.onPostDeactivate,checkCanReturnFocus:r.checkCanReturnFocus},d);clearTimeout(i.delayInitialFocusTimer),i.delayInitialFocusTimer=void 0,ge(),i.active=!1,i.paused=!1,J(),lt.deactivateTrap(n,o);var g=l(u,"onDeactivate"),_=l(u,"onPostDeactivate"),E=l(u,"checkCanReturnFocus"),N=l(u,"returnFocus","returnFocusOnDeactivate");g==null||g();var F=function(){ct(function(){N&&y(x(i.nodeFocusedBeforeActivation)),_==null||_()})};return N&&E?(E(x(i.nodeFocusedBeforeActivation)).then(F,F),this):(F(),this)},pause:function(d){if(i.paused||!i.active)return this;var u=l(d,"onPause"),g=l(d,"onPostPause");return i.paused=!0,u==null||u(),ge(),J(),g==null||g(),this},unpause:function(d){if(!i.paused||!i.active)return this;var u=l(d,"onUnpause"),g=l(d,"onPostUnpause");return i.paused=!1,u==null||u(),p(),$(),J(),g==null||g(),this},updateContainerElements:function(d){var u=[].concat(d).filter(Boolean);return i.containers=u.map(function(g){return typeof g=="string"?s.querySelector(g):g}),i.active&&p(),J(),this}},o.updateContainerElements(e),o};function Os(a,e={}){let t;const{immediate:s,...n}=e,r=ne(!1),i=ne(!1),o=p=>t&&t.activate(p),l=p=>t&&t.deactivate(p),c=()=>{t&&(t.pause(),i.value=!0)},h=()=>{t&&(t.unpause(),i.value=!1)},v=ve(()=>{const p=Xe(a);return(Array.isArray(p)?p:[p]).map(b=>{const y=Xe(b);return typeof y=="string"?y:Rt(y)}).filter(Ct)});return Ve(v,p=>{p.length&&(t=Fs(p,{...n,onActivate(){r.value=!0,e.onActivate&&e.onActivate()},onDeactivate(){r.value=!1,e.onDeactivate&&e.onDeactivate()}}),s&&o())},{flush:"post"}),Mt(()=>l()),{hasFocus:r,isPaused:i,activate:o,deactivate:l,pause:c,unpause:h}}class le{constructor(e,t=!0,s=[],n=5e3){this.ctx=e,this.iframes=t,this.exclude=s,this.iframesTimeout=n}static matches(e,t){const s=typeof t=="string"?[t]:t,n=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(n){let r=!1;return s.every(i=>n.call(e,i)?(r=!0,!1):!0),r}else return!1}getContexts(){let e,t=[];return typeof this.ctx>"u"||!this.ctx?e=[]:NodeList.prototype.isPrototypeOf(this.ctx)?e=Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?e=this.ctx:typeof this.ctx=="string"?e=Array.prototype.slice.call(document.querySelectorAll(this.ctx)):e=[this.ctx],e.forEach(s=>{const n=t.filter(r=>r.contains(s)).length>0;t.indexOf(s)===-1&&!n&&t.push(s)}),t}getIframeContents(e,t,s=()=>{}){let n;try{const r=e.contentWindow;if(n=r.document,!r||!n)throw new Error("iframe inaccessible")}catch{s()}n&&t(n)}isIframeBlank(e){const t="about:blank",s=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&s!==t&&s}observeIframeLoad(e,t,s){let n=!1,r=null;const i=()=>{if(!n){n=!0,clearTimeout(r);try{this.isIframeBlank(e)||(e.removeEventListener("load",i),this.getIframeContents(e,t,s))}catch{s()}}};e.addEventListener("load",i),r=setTimeout(i,this.iframesTimeout)}onIframeReady(e,t,s){try{e.contentWindow.document.readyState==="complete"?this.isIframeBlank(e)?this.observeIframeLoad(e,t,s):this.getIframeContents(e,t,s):this.observeIframeLoad(e,t,s)}catch{s()}}waitForIframes(e,t){let s=0;this.forEachIframe(e,()=>!0,n=>{s++,this.waitForIframes(n.querySelector("html"),()=>{--s||t()})},n=>{n||t()})}forEachIframe(e,t,s,n=()=>{}){let r=e.querySelectorAll("iframe"),i=r.length,o=0;r=Array.prototype.slice.call(r);const l=()=>{--i<=0&&n(o)};i||l(),r.forEach(c=>{le.matches(c,this.exclude)?l():this.onIframeReady(c,h=>{t(c)&&(o++,s(h)),l()},l)})}createIterator(e,t,s){return document.createNodeIterator(e,t,s,!1)}createInstanceOnIframe(e){return new le(e.querySelector("html"),this.iframes)}compareNodeIframe(e,t,s){const n=e.compareDocumentPosition(s),r=Node.DOCUMENT_POSITION_PRECEDING;if(n&r)if(t!==null){const i=t.compareDocumentPosition(s),o=Node.DOCUMENT_POSITION_FOLLOWING;if(i&o)return!0}else return!0;return!1}getIteratorNode(e){const t=e.previousNode();let s;return t===null?s=e.nextNode():s=e.nextNode()&&e.nextNode(),{prevNode:t,node:s}}checkIframeFilter(e,t,s,n){let r=!1,i=!1;return n.forEach((o,l)=>{o.val===s&&(r=l,i=o.handled)}),this.compareNodeIframe(e,t,s)?(r===!1&&!i?n.push({val:s,handled:!0}):r!==!1&&!i&&(n[r].handled=!0),!0):(r===!1&&n.push({val:s,handled:!1}),!1)}handleOpenIframes(e,t,s,n){e.forEach(r=>{r.handled||this.getIframeContents(r.val,i=>{this.createInstanceOnIframe(i).forEachNode(t,s,n)})})}iterateThroughNodes(e,t,s,n,r){const i=this.createIterator(t,e,n);let o=[],l=[],c,h,v=()=>({prevNode:h,node:c}=this.getIteratorNode(i),c);for(;v();)this.iframes&&this.forEachIframe(t,p=>this.checkIframeFilter(c,h,p,o),p=>{this.createInstanceOnIframe(p).forEachNode(e,b=>l.push(b),n)}),l.push(c);l.forEach(p=>{s(p)}),this.iframes&&this.handleOpenIframes(o,e,s,n),r()}forEachNode(e,t,s,n=()=>{}){const r=this.getContexts();let i=r.length;i||n(),r.forEach(o=>{const l=()=>{this.iterateThroughNodes(e,o,t,s,()=>{--i<=0&&n()})};this.iframes?this.waitForIframes(o,l):l()})}}let Rs=class{constructor(e){this.ctx=e,this.ie=!1;const t=window.navigator.userAgent;(t.indexOf("MSIE")>-1||t.indexOf("Trident")>-1)&&(this.ie=!0)}set opt(e){this._opt=Object.assign({},{element:"",className:"",exclude:[],iframes:!1,iframesTimeout:5e3,separateWordSearch:!0,diacritics:!0,synonyms:{},accuracy:"partially",acrossElements:!1,caseSensitive:!1,ignoreJoiners:!1,ignoreGroups:0,ignorePunctuation:[],wildcards:"disabled",each:()=>{},noMatch:()=>{},filter:()=>!0,done:()=>{},debug:!1,log:window.console},e)}get opt(){return this._opt}get iterator(){return new le(this.ctx,this.opt.iframes,this.opt.exclude,this.opt.iframesTimeout)}log(e,t="debug"){const s=this.opt.log;this.opt.debug&&typeof s=="object"&&typeof s[t]=="function"&&s[t](`mark.js: ${e}`)}escapeStr(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}createRegExp(e){return this.opt.wildcards!=="disabled"&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),this.opt.wildcards!=="disabled"&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),e}createSynonymsRegExp(e){const t=this.opt.synonyms,s=this.opt.caseSensitive?"":"i",n=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(let r in t)if(t.hasOwnProperty(r)){const i=t[r],o=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(r):this.escapeStr(r),l=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(i):this.escapeStr(i);o!==""&&l!==""&&(e=e.replace(new RegExp(`(${this.escapeStr(o)}|${this.escapeStr(l)})`,`gm${s}`),n+`(${this.processSynomyms(o)}|${this.processSynomyms(l)})`+n))}return e}processSynomyms(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}setupWildcardsRegExp(e){return e=e.replace(/(?:\\)*\?/g,t=>t.charAt(0)==="\\"?"?":""),e.replace(/(?:\\)*\*/g,t=>t.charAt(0)==="\\"?"*":"")}createWildcardsRegExp(e){let t=this.opt.wildcards==="withSpaces";return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}setupIgnoreJoinersRegExp(e){return e.replace(/[^(|)\\]/g,(t,s,n)=>{let r=n.charAt(s+1);return/[(|)\\]/.test(r)||r===""?t:t+"\0"})}createJoinersRegExp(e){let t=[];const s=this.opt.ignorePunctuation;return Array.isArray(s)&&s.length&&t.push(this.escapeStr(s.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join(`[${t.join("")}]*`):e}createDiacriticsRegExp(e){const t=this.opt.caseSensitive?"":"i",s=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"];let n=[];return e.split("").forEach(r=>{s.every(i=>{if(i.indexOf(r)!==-1){if(n.indexOf(i)>-1)return!1;e=e.replace(new RegExp(`[${i}]`,`gm${t}`),`[${i}]`),n.push(i)}return!0})}),e}createMergedBlanksRegExp(e){return e.replace(/[\s]+/gmi,"[\\s]+")}createAccuracyRegExp(e){const t="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿";let s=this.opt.accuracy,n=typeof s=="string"?s:s.value,r=typeof s=="string"?[]:s.limiters,i="";switch(r.forEach(o=>{i+=`|${this.escapeStr(o)}`}),n){case"partially":default:return`()(${e})`;case"complementary":return i="\\s"+(i||this.escapeStr(t)),`()([^${i}]*${e}[^${i}]*)`;case"exactly":return`(^|\\s${i})(${e})(?=$|\\s${i})`}}getSeparatedKeywords(e){let t=[];return e.forEach(s=>{this.opt.separateWordSearch?s.split(" ").forEach(n=>{n.trim()&&t.indexOf(n)===-1&&t.push(n)}):s.trim()&&t.indexOf(s)===-1&&t.push(s)}),{keywords:t.sort((s,n)=>n.length-s.length),length:t.length}}isNumeric(e){return Number(parseFloat(e))==e}checkRanges(e){if(!Array.isArray(e)||Object.prototype.toString.call(e[0])!=="[object Object]")return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];const t=[];let s=0;return e.sort((n,r)=>n.start-r.start).forEach(n=>{let{start:r,end:i,valid:o}=this.callNoMatchOnInvalidRanges(n,s);o&&(n.start=r,n.length=i-r,t.push(n),s=i)}),t}callNoMatchOnInvalidRanges(e,t){let s,n,r=!1;return e&&typeof e.start<"u"?(s=parseInt(e.start,10),n=s+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&n-t>0&&n-s>0?r=!0:(this.log(`Ignoring invalid or overlapping range: ${JSON.stringify(e)}`),this.opt.noMatch(e))):(this.log(`Ignoring invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)),{start:s,end:n,valid:r}}checkWhitespaceRanges(e,t,s){let n,r=!0,i=s.length,o=t-i,l=parseInt(e.start,10)-o;return l=l>i?i:l,n=l+parseInt(e.length,10),n>i&&(n=i,this.log(`End range automatically set to the max value of ${i}`)),l<0||n-l<0||l>i||n>i?(r=!1,this.log(`Invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)):s.substring(l,n).replace(/\s+/g,"")===""&&(r=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:l,end:n,valid:r}}getTextNodes(e){let t="",s=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,n=>{s.push({start:t.length,end:(t+=n.textContent).length,node:n})},n=>this.matchesExclude(n.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT,()=>{e({value:t,nodes:s})})}matchesExclude(e){return le.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}wrapRangeInTextNode(e,t,s){const n=this.opt.element?this.opt.element:"mark",r=e.splitText(t),i=r.splitText(s-t);let o=document.createElement(n);return o.setAttribute("data-markjs","true"),this.opt.className&&o.setAttribute("class",this.opt.className),o.textContent=r.textContent,r.parentNode.replaceChild(o,r),i}wrapRangeInMappedTextNode(e,t,s,n,r){e.nodes.every((i,o)=>{const l=e.nodes[o+1];if(typeof l>"u"||l.start>t){if(!n(i.node))return!1;const c=t-i.start,h=(s>i.end?i.end:s)-i.start,v=e.value.substr(0,i.start),p=e.value.substr(h+i.start);if(i.node=this.wrapRangeInTextNode(i.node,c,h),e.value=v+p,e.nodes.forEach((b,y)=>{y>=o&&(e.nodes[y].start>0&&y!==o&&(e.nodes[y].start-=h),e.nodes[y].end-=h)}),s-=h,r(i.node.previousSibling,i.start),s>i.end)t=i.end;else return!1}return!0})}wrapMatches(e,t,s,n,r){const i=t===0?0:t+1;this.getTextNodes(o=>{o.nodes.forEach(l=>{l=l.node;let c;for(;(c=e.exec(l.textContent))!==null&&c[i]!=="";){if(!s(c[i],l))continue;let h=c.index;if(i!==0)for(let v=1;v{let l;for(;(l=e.exec(o.value))!==null&&l[i]!=="";){let c=l.index;if(i!==0)for(let v=1;vs(l[i],v),(v,p)=>{e.lastIndex=p,n(v)})}r()})}wrapRangeFromIndex(e,t,s,n){this.getTextNodes(r=>{const i=r.value.length;e.forEach((o,l)=>{let{start:c,end:h,valid:v}=this.checkWhitespaceRanges(o,i,r.value);v&&this.wrapRangeInMappedTextNode(r,c,h,p=>t(p,o,r.value.substring(c,h),l),p=>{s(p,o)})}),n()})}unwrapMatches(e){const t=e.parentNode;let s=document.createDocumentFragment();for(;e.firstChild;)s.appendChild(e.removeChild(e.firstChild));t.replaceChild(s,e),this.ie?this.normalizeTextNode(t):t.normalize()}normalizeTextNode(e){if(e){if(e.nodeType===3)for(;e.nextSibling&&e.nextSibling.nodeType===3;)e.nodeValue+=e.nextSibling.nodeValue,e.parentNode.removeChild(e.nextSibling);else this.normalizeTextNode(e.firstChild);this.normalizeTextNode(e.nextSibling)}}markRegExp(e,t){this.opt=t,this.log(`Searching with expression "${e}"`);let s=0,n="wrapMatches";const r=i=>{s++,this.opt.each(i)};this.opt.acrossElements&&(n="wrapMatchesAcrossElements"),this[n](e,this.opt.ignoreGroups,(i,o)=>this.opt.filter(o,i,s),r,()=>{s===0&&this.opt.noMatch(e),this.opt.done(s)})}mark(e,t){this.opt=t;let s=0,n="wrapMatches";const{keywords:r,length:i}=this.getSeparatedKeywords(typeof e=="string"?[e]:e),o=this.opt.caseSensitive?"":"i",l=c=>{let h=new RegExp(this.createRegExp(c),`gm${o}`),v=0;this.log(`Searching with expression "${h}"`),this[n](h,1,(p,b)=>this.opt.filter(b,c,s,v),p=>{v++,s++,this.opt.each(p)},()=>{v===0&&this.opt.noMatch(c),r[i-1]===c?this.opt.done(s):l(r[r.indexOf(c)+1])})};this.opt.acrossElements&&(n="wrapMatchesAcrossElements"),i===0?this.opt.done(s):l(r[0])}markRanges(e,t){this.opt=t;let s=0,n=this.checkRanges(e);n&&n.length?(this.log("Starting to mark with the following ranges: "+JSON.stringify(n)),this.wrapRangeFromIndex(n,(r,i,o,l)=>this.opt.filter(r,i,o,l),(r,i)=>{s++,this.opt.each(r,i)},()=>{this.opt.done(s)})):this.opt.done(s)}unmark(e){this.opt=e;let t=this.opt.element?this.opt.element:"*";t+="[data-markjs]",this.opt.className&&(t+=`.${this.opt.className}`),this.log(`Removal selector "${t}"`),this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT,s=>{this.unwrapMatches(s)},s=>{const n=le.matches(s,t),r=this.matchesExclude(s);return!n||r?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},this.opt.done)}};function Cs(a){const e=new Rs(a);return this.mark=(t,s)=>(e.mark(t,s),this),this.markRegExp=(t,s)=>(e.markRegExp(t,s),this),this.markRanges=(t,s)=>(e.markRanges(t,s),this),this.unmark=t=>(e.unmark(t),this),this}function Ie(a,e,t,s){function n(r){return r instanceof t?r:new t(function(i){i(r)})}return new(t||(t=Promise))(function(r,i){function o(h){try{c(s.next(h))}catch(v){i(v)}}function l(h){try{c(s.throw(h))}catch(v){i(v)}}function c(h){h.done?r(h.value):n(h.value).then(o,l)}c((s=s.apply(a,[])).next())})}const Ms="ENTRIES",xt="KEYS",St="VALUES",z="";class Le{constructor(e,t){const s=e._tree,n=Array.from(s.keys());this.set=e,this._type=t,this._path=n.length>0?[{node:s,keys:n}]:[]}next(){const e=this.dive();return this.backtrack(),e}dive(){if(this._path.length===0)return{done:!0,value:void 0};const{node:e,keys:t}=oe(this._path);if(oe(t)===z)return{done:!1,value:this.result()};const s=e.get(oe(t));return this._path.push({node:s,keys:Array.from(s.keys())}),this.dive()}backtrack(){if(this._path.length===0)return;const e=oe(this._path).keys;e.pop(),!(e.length>0)&&(this._path.pop(),this.backtrack())}key(){return this.set._prefix+this._path.map(({keys:e})=>oe(e)).filter(e=>e!==z).join("")}value(){return oe(this._path).node.get(z)}result(){switch(this._type){case St:return this.value();case xt:return this.key();default:return[this.key(),this.value()]}}[Symbol.iterator](){return this}}const oe=a=>a[a.length-1],As=(a,e,t)=>{const s=new Map;if(e===void 0)return s;const n=e.length+1,r=n+t,i=new Uint8Array(r*n).fill(t+1);for(let o=0;o{const l=r*i;e:for(const c of a.keys())if(c===z){const h=n[l-1];h<=t&&s.set(o,[a.get(c),h])}else{let h=r;for(let v=0;vt)continue e}_t(a.get(c),e,t,s,n,h,i,o+c)}};class Z{constructor(e=new Map,t=""){this._size=void 0,this._tree=e,this._prefix=t}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");const[t,s]=Re(this._tree,e.slice(this._prefix.length));if(t===void 0){const[n,r]=Je(s);for(const i of n.keys())if(i!==z&&i.startsWith(r)){const o=new Map;return o.set(i.slice(r.length),n.get(i)),new Z(o,e)}}return new Z(t,e)}clear(){this._size=void 0,this._tree.clear()}delete(e){return this._size=void 0,Ls(this._tree,e)}entries(){return new Le(this,Ms)}forEach(e){for(const[t,s]of this)e(t,s,this)}fuzzyGet(e,t){return As(this._tree,e,t)}get(e){const t=Be(this._tree,e);return t!==void 0?t.get(z):void 0}has(e){const t=Be(this._tree,e);return t!==void 0&&t.has(z)}keys(){return new Le(this,xt)}set(e,t){if(typeof e!="string")throw new Error("key must be a string");return this._size=void 0,De(this._tree,e).set(z,t),this}get size(){if(this._size)return this._size;this._size=0;const e=this.entries();for(;!e.next().done;)this._size+=1;return this._size}update(e,t){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const s=De(this._tree,e);return s.set(z,t(s.get(z))),this}fetch(e,t){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const s=De(this._tree,e);let n=s.get(z);return n===void 0&&s.set(z,n=t()),n}values(){return new Le(this,St)}[Symbol.iterator](){return this.entries()}static from(e){const t=new Z;for(const[s,n]of e)t.set(s,n);return t}static fromObject(e){return Z.from(Object.entries(e))}}const Re=(a,e,t=[])=>{if(e.length===0||a==null)return[a,t];for(const s of a.keys())if(s!==z&&e.startsWith(s))return t.push([a,s]),Re(a.get(s),e.slice(s.length),t);return t.push([a,e]),Re(void 0,"",t)},Be=(a,e)=>{if(e.length===0||a==null)return a;for(const t of a.keys())if(t!==z&&e.startsWith(t))return Be(a.get(t),e.slice(t.length))},De=(a,e)=>{const t=e.length;e:for(let s=0;a&&s{const[t,s]=Re(a,e);if(t!==void 0){if(t.delete(z),t.size===0)Et(s);else if(t.size===1){const[n,r]=t.entries().next().value;Tt(s,n,r)}}},Et=a=>{if(a.length===0)return;const[e,t]=Je(a);if(e.delete(t),e.size===0)Et(a.slice(0,-1));else if(e.size===1){const[s,n]=e.entries().next().value;s!==z&&Tt(a.slice(0,-1),s,n)}},Tt=(a,e,t)=>{if(a.length===0)return;const[s,n]=Je(a);s.set(n+e,t),s.delete(n)},Je=a=>a[a.length-1],Ue="or",It="and",Ds="and_not";class ce{constructor(e){if((e==null?void 0:e.fields)==null)throw new Error('MiniSearch: option "fields" must be provided');const t=e.autoVacuum==null||e.autoVacuum===!0?je:e.autoVacuum;this._options=Object.assign(Object.assign(Object.assign({},Pe),e),{autoVacuum:t,searchOptions:Object.assign(Object.assign({},dt),e.searchOptions||{}),autoSuggestOptions:Object.assign(Object.assign({},$s),e.autoSuggestOptions||{})}),this._index=new Z,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=Ke,this.addFields(this._options.fields)}add(e){const{extractField:t,tokenize:s,processTerm:n,fields:r,idField:i}=this._options,o=t(e,i);if(o==null)throw new Error(`MiniSearch: document does not have ID field "${i}"`);if(this._idToShortId.has(o))throw new Error(`MiniSearch: duplicate ID ${o}`);const l=this.addDocumentId(o);this.saveStoredFields(l,e);for(const c of r){const h=t(e,c);if(h==null)continue;const v=s(h.toString(),c),p=this._fieldIds[c],b=new Set(v).size;this.addFieldLength(l,p,this._documentCount-1,b);for(const y of v){const x=n(y,c);if(Array.isArray(x))for(const w of x)this.addTerm(p,l,w);else x&&this.addTerm(p,l,x)}}}addAll(e){for(const t of e)this.add(t)}addAllAsync(e,t={}){const{chunkSize:s=10}=t,n={chunk:[],promise:Promise.resolve()},{chunk:r,promise:i}=e.reduce(({chunk:o,promise:l},c,h)=>(o.push(c),(h+1)%s===0?{chunk:[],promise:l.then(()=>new Promise(v=>setTimeout(v,0))).then(()=>this.addAll(o))}:{chunk:o,promise:l}),n);return i.then(()=>this.addAll(r))}remove(e){const{tokenize:t,processTerm:s,extractField:n,fields:r,idField:i}=this._options,o=n(e,i);if(o==null)throw new Error(`MiniSearch: document does not have ID field "${i}"`);const l=this._idToShortId.get(o);if(l==null)throw new Error(`MiniSearch: cannot remove document with ID ${o}: it is not in the index`);for(const c of r){const h=n(e,c);if(h==null)continue;const v=t(h.toString(),c),p=this._fieldIds[c],b=new Set(v).size;this.removeFieldLength(l,p,this._documentCount,b);for(const y of v){const x=s(y,c);if(Array.isArray(x))for(const w of x)this.removeTerm(p,l,w);else x&&this.removeTerm(p,l,x)}}this._storedFields.delete(l),this._documentIds.delete(l),this._idToShortId.delete(o),this._fieldLength.delete(l),this._documentCount-=1}removeAll(e){if(e)for(const t of e)this.remove(t);else{if(arguments.length>0)throw new Error("Expected documents to be present. Omit the argument to remove all documents.");this._index=new Z,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldLength=new Map,this._avgFieldLength=[],this._storedFields=new Map,this._nextId=0}}discard(e){const t=this._idToShortId.get(e);if(t==null)throw new Error(`MiniSearch: cannot discard document with ID ${e}: it is not in the index`);this._idToShortId.delete(e),this._documentIds.delete(t),this._storedFields.delete(t),(this._fieldLength.get(t)||[]).forEach((s,n)=>{this.removeFieldLength(t,n,this._documentCount,s)}),this._fieldLength.delete(t),this._documentCount-=1,this._dirtCount+=1,this.maybeAutoVacuum()}maybeAutoVacuum(){if(this._options.autoVacuum===!1)return;const{minDirtFactor:e,minDirtCount:t,batchSize:s,batchWait:n}=this._options.autoVacuum;this.conditionalVacuum({batchSize:s,batchWait:n},{minDirtCount:t,minDirtFactor:e})}discardAll(e){const t=this._options.autoVacuum;try{this._options.autoVacuum=!1;for(const s of e)this.discard(s)}finally{this._options.autoVacuum=t}this.maybeAutoVacuum()}replace(e){const{idField:t,extractField:s}=this._options,n=s(e,t);this.discard(n),this.add(e)}vacuum(e={}){return this.conditionalVacuum(e)}conditionalVacuum(e,t){return this._currentVacuum?(this._enqueuedVacuumConditions=this._enqueuedVacuumConditions&&t,this._enqueuedVacuum!=null?this._enqueuedVacuum:(this._enqueuedVacuum=this._currentVacuum.then(()=>{const s=this._enqueuedVacuumConditions;return this._enqueuedVacuumConditions=Ke,this.performVacuuming(e,s)}),this._enqueuedVacuum)):this.vacuumConditionsMet(t)===!1?Promise.resolve():(this._currentVacuum=this.performVacuuming(e),this._currentVacuum)}performVacuuming(e,t){return Ie(this,void 0,void 0,function*(){const s=this._dirtCount;if(this.vacuumConditionsMet(t)){const n=e.batchSize||We.batchSize,r=e.batchWait||We.batchWait;let i=1;for(const[o,l]of this._index){for(const[c,h]of l)for(const[v]of h)this._documentIds.has(v)||(h.size<=1?l.delete(c):h.delete(v));this._index.get(o).size===0&&this._index.delete(o),i%n===0&&(yield new Promise(c=>setTimeout(c,r))),i+=1}this._dirtCount-=s}yield null,this._currentVacuum=this._enqueuedVacuum,this._enqueuedVacuum=null})}vacuumConditionsMet(e){if(e==null)return!0;let{minDirtCount:t,minDirtFactor:s}=e;return t=t||je.minDirtCount,s=s||je.minDirtFactor,this.dirtCount>=t&&this.dirtFactor>=s}get isVacuuming(){return this._currentVacuum!=null}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}has(e){return this._idToShortId.has(e)}getStoredFields(e){const t=this._idToShortId.get(e);if(t!=null)return this._storedFields.get(t)}search(e,t={}){const s=this.executeQuery(e,t),n=[];for(const[r,{score:i,terms:o,match:l}]of s){const c=o.length||1,h={id:this._documentIds.get(r),score:i*c,terms:Object.keys(l),queryTerms:o,match:l};Object.assign(h,this._storedFields.get(r)),(t.filter==null||t.filter(h))&&n.push(h)}return e===ce.wildcard&&t.boostDocument==null&&this._options.searchOptions.boostDocument==null||n.sort(ft),n}autoSuggest(e,t={}){t=Object.assign(Object.assign({},this._options.autoSuggestOptions),t);const s=new Map;for(const{score:r,terms:i}of this.search(e,t)){const o=i.join(" "),l=s.get(o);l!=null?(l.score+=r,l.count+=1):s.set(o,{score:r,terms:i,count:1})}const n=[];for(const[r,{score:i,terms:o,count:l}]of s)n.push({suggestion:r,terms:o,score:i/l});return n.sort(ft),n}get documentCount(){return this._documentCount}get termCount(){return this._index.size}static loadJSON(e,t){if(t==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJS(JSON.parse(e),t)}static loadJSONAsync(e,t){return Ie(this,void 0,void 0,function*(){if(t==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJSAsync(JSON.parse(e),t)})}static getDefault(e){if(Pe.hasOwnProperty(e))return ze(Pe,e);throw new Error(`MiniSearch: unknown option "${e}"`)}static loadJS(e,t){const{index:s,documentIds:n,fieldLength:r,storedFields:i,serializationVersion:o}=e,l=this.instantiateMiniSearch(e,t);l._documentIds=Ee(n),l._fieldLength=Ee(r),l._storedFields=Ee(i);for(const[c,h]of l._documentIds)l._idToShortId.set(h,c);for(const[c,h]of s){const v=new Map;for(const p of Object.keys(h)){let b=h[p];o===1&&(b=b.ds),v.set(parseInt(p,10),Ee(b))}l._index.set(c,v)}return l}static loadJSAsync(e,t){return Ie(this,void 0,void 0,function*(){const{index:s,documentIds:n,fieldLength:r,storedFields:i,serializationVersion:o}=e,l=this.instantiateMiniSearch(e,t);l._documentIds=yield Te(n),l._fieldLength=yield Te(r),l._storedFields=yield Te(i);for(const[h,v]of l._documentIds)l._idToShortId.set(v,h);let c=0;for(const[h,v]of s){const p=new Map;for(const b of Object.keys(v)){let y=v[b];o===1&&(y=y.ds),p.set(parseInt(b,10),yield Te(y))}++c%1e3===0&&(yield kt(0)),l._index.set(h,p)}return l})}static instantiateMiniSearch(e,t){const{documentCount:s,nextId:n,fieldIds:r,averageFieldLength:i,dirtCount:o,serializationVersion:l}=e;if(l!==1&&l!==2)throw new Error("MiniSearch: cannot deserialize an index created with an incompatible version");const c=new ce(t);return c._documentCount=s,c._nextId=n,c._idToShortId=new Map,c._fieldIds=r,c._avgFieldLength=i,c._dirtCount=o||0,c._index=new Z,c}executeQuery(e,t={}){if(e===ce.wildcard)return this.executeWildcardQuery(t);if(typeof e!="string"){const p=Object.assign(Object.assign(Object.assign({},t),e),{queries:void 0}),b=e.queries.map(y=>this.executeQuery(y,p));return this.combineResults(b,p.combineWith)}const{tokenize:s,processTerm:n,searchOptions:r}=this._options,i=Object.assign(Object.assign({tokenize:s,processTerm:n},r),t),{tokenize:o,processTerm:l}=i,v=o(e).flatMap(p=>l(p)).filter(p=>!!p).map(Vs(i)).map(p=>this.executeQuerySpec(p,i));return this.combineResults(v,i.combineWith)}executeQuerySpec(e,t){const s=Object.assign(Object.assign({},this._options.searchOptions),t),n=(s.fields||this._options.fields).reduce((x,w)=>Object.assign(Object.assign({},x),{[w]:ze(s.boost,w)||1}),{}),{boostDocument:r,weights:i,maxFuzzy:o,bm25:l}=s,{fuzzy:c,prefix:h}=Object.assign(Object.assign({},dt.weights),i),v=this._index.get(e.term),p=this.termResults(e.term,e.term,1,e.termBoost,v,n,r,l);let b,y;if(e.prefix&&(b=this._index.atPrefix(e.term)),e.fuzzy){const x=e.fuzzy===!0?.2:e.fuzzy,w=x<1?Math.min(o,Math.round(e.term.length*x)):x;w&&(y=this._index.fuzzyGet(e.term,w))}if(b)for(const[x,w]of b){const O=x.length-e.term.length;if(!O)continue;y==null||y.delete(x);const R=h*x.length/(x.length+.3*O);this.termResults(e.term,x,R,e.termBoost,w,n,r,l,p)}if(y)for(const x of y.keys()){const[w,O]=y.get(x);if(!O)continue;const R=c*x.length/(x.length+O);this.termResults(e.term,x,R,e.termBoost,w,n,r,l,p)}return p}executeWildcardQuery(e){const t=new Map,s=Object.assign(Object.assign({},this._options.searchOptions),e);for(const[n,r]of this._documentIds){const i=s.boostDocument?s.boostDocument(r,"",this._storedFields.get(n)):1;t.set(n,{score:i,terms:[],match:{}})}return t}combineResults(e,t=Ue){if(e.length===0)return new Map;const s=t.toLowerCase(),n=zs[s];if(!n)throw new Error(`Invalid combination operator: ${t}`);return e.reduce(n)||new Map}toJSON(){const e=[];for(const[t,s]of this._index){const n={};for(const[r,i]of s)n[r]=Object.fromEntries(i);e.push([t,n])}return{documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:e,serializationVersion:2}}termResults(e,t,s,n,r,i,o,l,c=new Map){if(r==null)return c;for(const h of Object.keys(i)){const v=i[h],p=this._fieldIds[h],b=r.get(p);if(b==null)continue;let y=b.size;const x=this._avgFieldLength[p];for(const w of b.keys()){if(!this._documentIds.has(w)){this.removeTerm(p,w,t),y-=1;continue}const O=o?o(this._documentIds.get(w),t,this._storedFields.get(w)):1;if(!O)continue;const R=b.get(w),K=this._fieldLength.get(w)[p],G=js(R,y,this._documentCount,K,x,l),W=s*n*v*O*G,V=c.get(w);if(V){V.score+=W,Bs(V.terms,e);const $=ze(V.match,t);$?$.push(h):V.match[t]=[h]}else c.set(w,{score:W,terms:[e],match:{[t]:[h]}})}}return c}addTerm(e,t,s){const n=this._index.fetch(s,pt);let r=n.get(e);if(r==null)r=new Map,r.set(t,1),n.set(e,r);else{const i=r.get(t);r.set(t,(i||0)+1)}}removeTerm(e,t,s){if(!this._index.has(s)){this.warnDocumentChanged(t,e,s);return}const n=this._index.fetch(s,pt),r=n.get(e);r==null||r.get(t)==null?this.warnDocumentChanged(t,e,s):r.get(t)<=1?r.size<=1?n.delete(e):r.delete(t):r.set(t,r.get(t)-1),this._index.get(s).size===0&&this._index.delete(s)}warnDocumentChanged(e,t,s){for(const n of Object.keys(this._fieldIds))if(this._fieldIds[n]===t){this._options.logger("warn",`MiniSearch: document with ID ${this._documentIds.get(e)} has changed before removal: term "${s}" was not present in field "${n}". Removing a document after it has changed can corrupt the index!`,"version_conflict");return}}addDocumentId(e){const t=this._nextId;return this._idToShortId.set(e,t),this._documentIds.set(t,e),this._documentCount+=1,this._nextId+=1,t}addFields(e){for(let t=0;tObject.prototype.hasOwnProperty.call(a,e)?a[e]:void 0,zs={[Ue]:(a,e)=>{for(const t of e.keys()){const s=a.get(t);if(s==null)a.set(t,e.get(t));else{const{score:n,terms:r,match:i}=e.get(t);s.score=s.score+n,s.match=Object.assign(s.match,i),ht(s.terms,r)}}return a},[It]:(a,e)=>{const t=new Map;for(const s of e.keys()){const n=a.get(s);if(n==null)continue;const{score:r,terms:i,match:o}=e.get(s);ht(n.terms,i),t.set(s,{score:n.score+r,terms:n.terms,match:Object.assign(n.match,o)})}return t},[Ds]:(a,e)=>{for(const t of e.keys())a.delete(t);return a}},Ps={k:1.2,b:.7,d:.5},js=(a,e,t,s,n,r)=>{const{k:i,b:o,d:l}=r;return Math.log(1+(t-e+.5)/(e+.5))*(l+a*(i+1)/(a+i*(1-o+o*s/n)))},Vs=a=>(e,t,s)=>{const n=typeof a.fuzzy=="function"?a.fuzzy(e,t,s):a.fuzzy||!1,r=typeof a.prefix=="function"?a.prefix(e,t,s):a.prefix===!0,i=typeof a.boostTerm=="function"?a.boostTerm(e,t,s):1;return{term:e,fuzzy:n,prefix:r,termBoost:i}},Pe={idField:"id",extractField:(a,e)=>a[e],tokenize:a=>a.split(Ws),processTerm:a=>a.toLowerCase(),fields:void 0,searchOptions:void 0,storeFields:[],logger:(a,e)=>{typeof(console==null?void 0:console[a])=="function"&&console[a](e)},autoVacuum:!0},dt={combineWith:Ue,prefix:!1,fuzzy:!1,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:Ps},$s={combineWith:It,prefix:(a,e,t)=>e===t.length-1},We={batchSize:1e3,batchWait:10},Ke={minDirtFactor:.1,minDirtCount:20},je=Object.assign(Object.assign({},We),Ke),Bs=(a,e)=>{a.includes(e)||a.push(e)},ht=(a,e)=>{for(const t of e)a.includes(t)||a.push(t)},ft=({score:a},{score:e})=>e-a,pt=()=>new Map,Ee=a=>{const e=new Map;for(const t of Object.keys(a))e.set(parseInt(t,10),a[t]);return e},Te=a=>Ie(void 0,void 0,void 0,function*(){const e=new Map;let t=0;for(const s of Object.keys(a))e.set(parseInt(s,10),a[s]),++t%1e3===0&&(yield kt(0));return e}),kt=a=>new Promise(e=>setTimeout(e,a)),Ws=/[\n\r\p{Z}\p{P}]+/u;class Ks{constructor(e=10){Ce(this,"max");Ce(this,"cache");this.max=e,this.cache=new Map}get(e){let t=this.cache.get(e);return t!==void 0&&(this.cache.delete(e),this.cache.set(e,t)),t}set(e,t){this.cache.has(e)?this.cache.delete(e):this.cache.size===this.max&&this.cache.delete(this.first()),this.cache.set(e,t)}first(){return this.cache.keys().next().value}clear(){this.cache.clear()}}const Js=["aria-owns"],Us={class:"shell"},qs=["title"],Gs={class:"search-actions before"},Hs=["title"],Qs=["aria-activedescendant","aria-controls","placeholder"],Ys={class:"search-actions"},Zs=["title"],Xs=["disabled","title"],en=["id","role","aria-labelledby"],tn=["id","aria-selected"],sn=["href","aria-label","onMouseenter","onFocusin"],nn={class:"titles"},rn=["innerHTML"],an={class:"title main"},on=["innerHTML"],ln={key:0,class:"excerpt-wrapper"},cn={key:0,class:"excerpt",inert:""},un=["innerHTML"],dn={key:0,class:"no-results"},hn={class:"search-keyboard-shortcuts"},fn=["aria-label"],pn=["aria-label"],vn=["aria-label"],mn=["aria-label"],gn=At({__name:"VPLocalSearchBox",emits:["close"],setup(a,{emit:e}){var N,F;const t=e,s=we(),n=we(),r=we(ns),i=ts(),{activate:o}=Os(s,{immediate:!0,allowOutsideClick:!0,clickOutsideDeactivates:!0,escapeDeactivates:!0}),{localeIndex:l,theme:c}=i,h=et(async()=>{var m,f,I,A,C,M,j,T,P;return it(ce.loadJSON((I=await((f=(m=r.value)[l.value])==null?void 0:f.call(m)))==null?void 0:I.default,{fields:["title","titles","text"],storeFields:["title","titles"],searchOptions:{fuzzy:.2,prefix:!0,boost:{title:4,text:2,titles:1},...((A=c.value.search)==null?void 0:A.provider)==="local"&&((M=(C=c.value.search.options)==null?void 0:C.miniSearch)==null?void 0:M.searchOptions)},...((j=c.value.search)==null?void 0:j.provider)==="local"&&((P=(T=c.value.search.options)==null?void 0:T.miniSearch)==null?void 0:P.options)}))}),p=ve(()=>{var m,f;return((m=c.value.search)==null?void 0:m.provider)==="local"&&((f=c.value.search.options)==null?void 0:f.disableQueryPersistence)===!0}).value?ne(""):Lt("vitepress:local-search-filter",""),b=Dt("vitepress:local-search-detailed-list",((N=c.value.search)==null?void 0:N.provider)==="local"&&((F=c.value.search.options)==null?void 0:F.detailedView)===!0),y=ve(()=>{var m,f,I;return((m=c.value.search)==null?void 0:m.provider)==="local"&&(((f=c.value.search.options)==null?void 0:f.disableDetailedView)===!0||((I=c.value.search.options)==null?void 0:I.detailedView)===!1)}),x=ve(()=>{var f,I,A,C,M,j,T;const m=((f=c.value.search)==null?void 0:f.options)??c.value.algolia;return((M=(C=(A=(I=m==null?void 0:m.locales)==null?void 0:I[l.value])==null?void 0:A.translations)==null?void 0:C.button)==null?void 0:M.buttonText)||((T=(j=m==null?void 0:m.translations)==null?void 0:j.button)==null?void 0:T.buttonText)||"Search"});zt(()=>{y.value&&(b.value=!1)});const w=we([]),O=ne(!1);Ve(p,()=>{O.value=!1});const R=et(async()=>{if(n.value)return it(new Cs(n.value))},null),K=new Ks(16);Pt(()=>[h.value,p.value,b.value],async([m,f,I],A,C)=>{var X,be,qe,Ge;(A==null?void 0:A[0])!==m&&K.clear();let M=!1;if(C(()=>{M=!0}),!m)return;w.value=m.search(f).slice(0,16),O.value=!0;const j=I?await Promise.all(w.value.map(B=>G(B.id))):[];if(M)return;for(const{id:B,mod:ee}of j){const te=B.slice(0,B.indexOf("#"));let Q=K.get(te);if(Q)continue;Q=new Map,K.set(te,Q);const U=ee.default??ee;if(U!=null&&U.render||U!=null&&U.setup){const se=Qt(U);se.config.warnHandler=()=>{},se.provide(Yt,i),Object.defineProperties(se.config.globalProperties,{$frontmatter:{get(){return i.frontmatter.value}},$params:{get(){return i.page.value.params}}});const He=document.createElement("div");se.mount(He),He.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach(ue=>{var Ze;const ye=(Ze=ue.querySelector("a"))==null?void 0:Ze.getAttribute("href"),Qe=(ye==null?void 0:ye.startsWith("#"))&&ye.slice(1);if(!Qe)return;let Ye="";for(;(ue=ue.nextElementSibling)&&!/^h[1-6]$/i.test(ue.tagName);)Ye+=ue.outerHTML;Q.set(Qe,Ye)}),se.unmount()}if(M)return}const T=new Set;if(w.value=w.value.map(B=>{const[ee,te]=B.id.split("#"),Q=K.get(ee),U=(Q==null?void 0:Q.get(te))??"";for(const se in B.match)T.add(se);return{...B,text:U}}),await de(),M)return;await new Promise(B=>{var ee;(ee=R.value)==null||ee.unmark({done:()=>{var te;(te=R.value)==null||te.markRegExp(E(T),{done:B})}})});const P=((X=s.value)==null?void 0:X.querySelectorAll(".result .excerpt"))??[];for(const B of P)(be=B.querySelector('mark[data-markjs="true"]'))==null||be.scrollIntoView({block:"center"});(Ge=(qe=n.value)==null?void 0:qe.firstElementChild)==null||Ge.scrollIntoView({block:"start"})},{debounce:200,immediate:!0});async function G(m){const f=Zt(m.slice(0,m.indexOf("#")));try{if(!f)throw new Error(`Cannot find file for id: ${m}`);return{id:m,mod:await import(f)}}catch(I){return console.error(I),{id:m,mod:{}}}}const W=ne(),V=ve(()=>{var m;return((m=p.value)==null?void 0:m.length)<=0});function $(m=!0){var f,I;(f=W.value)==null||f.focus(),m&&((I=W.value)==null||I.select())}Me(()=>{$()});function ge(m){m.pointerType==="mouse"&&$()}const L=ne(-1),H=ne(!1);Ve(w,m=>{L.value=m.length?0:-1,J()});function J(){de(()=>{const m=document.querySelector(".result.selected");m==null||m.scrollIntoView({block:"nearest"})})}xe("ArrowUp",m=>{m.preventDefault(),L.value--,L.value<0&&(L.value=w.value.length-1),H.value=!0,J()}),xe("ArrowDown",m=>{m.preventDefault(),L.value++,L.value>=w.value.length&&(L.value=0),H.value=!0,J()});const k=jt();xe("Enter",m=>{if(m.isComposing||m.target instanceof HTMLButtonElement&&m.target.type!=="submit")return;const f=w.value[L.value];if(m.target instanceof HTMLInputElement&&!f){m.preventDefault();return}f&&(k.go(f.id),t("close"))}),xe("Escape",()=>{t("close")});const u=ss({modal:{displayDetails:"Display detailed list",resetButtonTitle:"Reset search",backButtonTitle:"Close search",noResultsText:"No results for",footer:{selectText:"to select",selectKeyAriaLabel:"enter",navigateText:"to navigate",navigateUpKeyAriaLabel:"up arrow",navigateDownKeyAriaLabel:"down arrow",closeText:"to close",closeKeyAriaLabel:"escape"}}});Me(()=>{window.history.pushState(null,"",null)}),Vt("popstate",m=>{m.preventDefault(),t("close")});const g=$t(Bt?document.body:null);Me(()=>{de(()=>{g.value=!0,de().then(()=>o())})}),Wt(()=>{g.value=!1});function _(){p.value="",de().then(()=>$(!1))}function E(m){return new RegExp([...m].sort((f,I)=>I.length-f.length).map(f=>`(${Xt(f)})`).join("|"),"gi")}return(m,f)=>{var I,A,C,M,j;return q(),Kt(Ht,{to:"body"},[S("div",{ref_key:"el",ref:s,role:"button","aria-owns":(I=w.value)!=null&&I.length?"localsearch-list":void 0,"aria-expanded":"true","aria-haspopup":"listbox","aria-labelledby":"localsearch-label",class:"VPLocalSearchBox"},[S("div",{class:"backdrop",onClick:f[0]||(f[0]=T=>m.$emit("close"))}),S("div",Us,[S("form",{class:"search-bar",onPointerup:f[4]||(f[4]=T=>ge(T)),onSubmit:f[5]||(f[5]=Jt(()=>{},["prevent"]))},[S("label",{title:x.value,id:"localsearch-label",for:"localsearch-input"},f[8]||(f[8]=[S("span",{"aria-hidden":"true",class:"vpi-search search-icon local-search-icon"},null,-1)]),8,qs),S("div",Gs,[S("button",{class:"back-button",title:D(u)("modal.backButtonTitle"),onClick:f[1]||(f[1]=T=>m.$emit("close"))},f[9]||(f[9]=[S("span",{class:"vpi-arrow-left local-search-icon"},null,-1)]),8,Hs)]),Ut(S("input",{ref_key:"searchInput",ref:W,"onUpdate:modelValue":f[2]||(f[2]=T=>Gt(p)?p.value=T:null),"aria-activedescendant":L.value>-1?"localsearch-item-"+L.value:void 0,"aria-autocomplete":"both","aria-controls":(A=w.value)!=null&&A.length?"localsearch-list":void 0,"aria-labelledby":"localsearch-label",autocapitalize:"off",autocomplete:"off",autocorrect:"off",class:"search-input",id:"localsearch-input",enterkeyhint:"go",maxlength:"64",placeholder:x.value,spellcheck:"false",type:"search"},null,8,Qs),[[qt,D(p)]]),S("div",Ys,[y.value?Se("",!0):(q(),Y("button",{key:0,class:tt(["toggle-layout-button",{"detailed-list":D(b)}]),type:"button",title:D(u)("modal.displayDetails"),onClick:f[3]||(f[3]=T=>L.value>-1&&(b.value=!D(b)))},f[10]||(f[10]=[S("span",{class:"vpi-layout-list local-search-icon"},null,-1)]),10,Zs)),S("button",{class:"clear-button",type:"reset",disabled:V.value,title:D(u)("modal.resetButtonTitle"),onClick:_},f[11]||(f[11]=[S("span",{class:"vpi-delete local-search-icon"},null,-1)]),8,Xs)])],32),S("ul",{ref_key:"resultsEl",ref:n,id:(C=w.value)!=null&&C.length?"localsearch-list":void 0,role:(M=w.value)!=null&&M.length?"listbox":void 0,"aria-labelledby":(j=w.value)!=null&&j.length?"localsearch-label":void 0,class:"results",onMousemove:f[7]||(f[7]=T=>H.value=!1)},[(q(!0),Y(nt,null,st(w.value,(T,P)=>(q(),Y("li",{key:T.id,id:"localsearch-item-"+P,"aria-selected":L.value===P?"true":"false",role:"option"},[S("a",{href:T.id,class:tt(["result",{selected:L.value===P}]),"aria-label":[...T.titles,T.title].join(" > "),onMouseenter:X=>!H.value&&(L.value=P),onFocusin:X=>L.value=P,onClick:f[6]||(f[6]=X=>m.$emit("close"))},[S("div",null,[S("div",nn,[f[13]||(f[13]=S("span",{class:"title-icon"},"#",-1)),(q(!0),Y(nt,null,st(T.titles,(X,be)=>(q(),Y("span",{key:be,class:"title"},[S("span",{class:"text",innerHTML:X},null,8,rn),f[12]||(f[12]=S("span",{class:"vpi-chevron-right local-search-icon"},null,-1))]))),128)),S("span",an,[S("span",{class:"text",innerHTML:T.title},null,8,on)])]),D(b)?(q(),Y("div",ln,[T.text?(q(),Y("div",cn,[S("div",{class:"vp-doc",innerHTML:T.text},null,8,un)])):Se("",!0),f[14]||(f[14]=S("div",{class:"excerpt-gradient-bottom"},null,-1)),f[15]||(f[15]=S("div",{class:"excerpt-gradient-top"},null,-1))])):Se("",!0)])],42,sn)],8,tn))),128)),D(p)&&!w.value.length&&O.value?(q(),Y("li",dn,[he(fe(D(u)("modal.noResultsText"))+' "',1),S("strong",null,fe(D(p)),1),f[16]||(f[16]=he('" '))])):Se("",!0)],40,en),S("div",hn,[S("span",null,[S("kbd",{"aria-label":D(u)("modal.footer.navigateUpKeyAriaLabel")},f[17]||(f[17]=[S("span",{class:"vpi-arrow-up navigate-icon"},null,-1)]),8,fn),S("kbd",{"aria-label":D(u)("modal.footer.navigateDownKeyAriaLabel")},f[18]||(f[18]=[S("span",{class:"vpi-arrow-down navigate-icon"},null,-1)]),8,pn),he(" "+fe(D(u)("modal.footer.navigateText")),1)]),S("span",null,[S("kbd",{"aria-label":D(u)("modal.footer.selectKeyAriaLabel")},f[19]||(f[19]=[S("span",{class:"vpi-corner-down-left navigate-icon"},null,-1)]),8,vn),he(" "+fe(D(u)("modal.footer.selectText")),1)]),S("span",null,[S("kbd",{"aria-label":D(u)("modal.footer.closeKeyAriaLabel")},"esc",8,mn),he(" "+fe(D(u)("modal.footer.closeText")),1)])])])],8,Js)])}}}),_n=es(gn,[["__scopeId","data-v-ce12919d"]]);export{_n as default}; diff --git a/assets/chunks/framework.DkhCEVKm.js b/assets/chunks/framework.DkhCEVKm.js new file mode 100644 index 00000000000..fa9c4b94570 --- /dev/null +++ b/assets/chunks/framework.DkhCEVKm.js @@ -0,0 +1,18 @@ +/** +* @vue/shared v3.5.12 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function Ns(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const Z={},Et=[],ke=()=>{},Uo=()=>!1,Zt=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Fs=e=>e.startsWith("onUpdate:"),ce=Object.assign,Hs=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},ko=Object.prototype.hasOwnProperty,z=(e,t)=>ko.call(e,t),K=Array.isArray,Ct=e=>In(e)==="[object Map]",si=e=>In(e)==="[object Set]",q=e=>typeof e=="function",re=e=>typeof e=="string",Ye=e=>typeof e=="symbol",ne=e=>e!==null&&typeof e=="object",ri=e=>(ne(e)||q(e))&&q(e.then)&&q(e.catch),ii=Object.prototype.toString,In=e=>ii.call(e),Bo=e=>In(e).slice(8,-1),oi=e=>In(e)==="[object Object]",$s=e=>re(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Tt=Ns(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Nn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Wo=/-(\w)/g,Le=Nn(e=>e.replace(Wo,(t,n)=>n?n.toUpperCase():"")),Ko=/\B([A-Z])/g,st=Nn(e=>e.replace(Ko,"-$1").toLowerCase()),Fn=Nn(e=>e.charAt(0).toUpperCase()+e.slice(1)),vn=Nn(e=>e?`on${Fn(e)}`:""),tt=(e,t)=>!Object.is(e,t),bn=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},vs=e=>{const t=parseFloat(e);return isNaN(t)?e:t},qo=e=>{const t=re(e)?Number(e):NaN;return isNaN(t)?e:t};let ar;const Hn=()=>ar||(ar=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Ds(e){if(K(e)){const t={};for(let n=0;n{if(n){const s=n.split(Yo);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function js(e){let t="";if(re(e))t=e;else if(K(e))for(let n=0;n!!(e&&e.__v_isRef===!0),Zo=e=>re(e)?e:e==null?"":K(e)||ne(e)&&(e.toString===ii||!q(e.toString))?ai(e)?Zo(e.value):JSON.stringify(e,fi,2):String(e),fi=(e,t)=>ai(t)?fi(e,t.value):Ct(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r],i)=>(n[zn(s,i)+" =>"]=r,n),{})}:si(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>zn(n))}:Ye(t)?zn(t):ne(t)&&!K(t)&&!oi(t)?String(t):t,zn=(e,t="")=>{var n;return Ye(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/** +* @vue/reactivity v3.5.12 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let _e;class el{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=_e,!t&&_e&&(this.index=(_e.scopes||(_e.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t0)return;if(jt){let t=jt;for(jt=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Dt;){let t=Dt;for(Dt=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(s){e||(e=s)}t=n}}if(e)throw e}function gi(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function mi(e){let t,n=e.depsTail,s=n;for(;s;){const r=s.prevDep;s.version===-1?(s===n&&(n=r),ks(s),nl(s)):t=s,s.dep.activeLink=s.prevActiveLink,s.prevActiveLink=void 0,s=r}e.deps=t,e.depsTail=n}function bs(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(yi(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function yi(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Kt))return;e.globalVersion=Kt;const t=e.dep;if(e.flags|=2,t.version>0&&!e.isSSR&&e.deps&&!bs(e)){e.flags&=-3;return}const n=te,s=Ne;te=e,Ne=!0;try{gi(e);const r=e.fn(e._value);(t.version===0||tt(r,e._value))&&(e._value=r,t.version++)}catch(r){throw t.version++,r}finally{te=n,Ne=s,mi(e),e.flags&=-3}}function ks(e,t=!1){const{dep:n,prevSub:s,nextSub:r}=e;if(s&&(s.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=s,e.nextSub=void 0),n.subs===e&&(n.subs=s,!s&&n.computed)){n.computed.flags&=-5;for(let i=n.computed.deps;i;i=i.nextDep)ks(i,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function nl(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let Ne=!0;const vi=[];function rt(){vi.push(Ne),Ne=!1}function it(){const e=vi.pop();Ne=e===void 0?!0:e}function fr(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=te;te=void 0;try{t()}finally{te=n}}}let Kt=0;class sl{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class $n{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0}track(t){if(!te||!Ne||te===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==te)n=this.activeLink=new sl(te,this),te.deps?(n.prevDep=te.depsTail,te.depsTail.nextDep=n,te.depsTail=n):te.deps=te.depsTail=n,bi(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const s=n.nextDep;s.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=s),n.prevDep=te.depsTail,n.nextDep=void 0,te.depsTail.nextDep=n,te.depsTail=n,te.deps===n&&(te.deps=s)}return n}trigger(t){this.version++,Kt++,this.notify(t)}notify(t){Vs();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{Us()}}}function bi(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let s=t.deps;s;s=s.nextDep)bi(s)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const Cn=new WeakMap,dt=Symbol(""),_s=Symbol(""),qt=Symbol("");function me(e,t,n){if(Ne&&te){let s=Cn.get(e);s||Cn.set(e,s=new Map);let r=s.get(n);r||(s.set(n,r=new $n),r.map=s,r.key=n),r.track()}}function qe(e,t,n,s,r,i){const o=Cn.get(e);if(!o){Kt++;return}const l=c=>{c&&c.trigger()};if(Vs(),t==="clear")o.forEach(l);else{const c=K(e),f=c&&$s(n);if(c&&n==="length"){const a=Number(s);o.forEach((d,y)=>{(y==="length"||y===qt||!Ye(y)&&y>=a)&&l(d)})}else switch((n!==void 0||o.has(void 0))&&l(o.get(n)),f&&l(o.get(qt)),t){case"add":c?f&&l(o.get("length")):(l(o.get(dt)),Ct(e)&&l(o.get(_s)));break;case"delete":c||(l(o.get(dt)),Ct(e)&&l(o.get(_s)));break;case"set":Ct(e)&&l(o.get(dt));break}}Us()}function rl(e,t){const n=Cn.get(e);return n&&n.get(t)}function bt(e){const t=J(e);return t===e?t:(me(t,"iterate",qt),Me(e)?t:t.map(ye))}function Dn(e){return me(e=J(e),"iterate",qt),e}const il={__proto__:null,[Symbol.iterator](){return Zn(this,Symbol.iterator,ye)},concat(...e){return bt(this).concat(...e.map(t=>K(t)?bt(t):t))},entries(){return Zn(this,"entries",e=>(e[1]=ye(e[1]),e))},every(e,t){return We(this,"every",e,t,void 0,arguments)},filter(e,t){return We(this,"filter",e,t,n=>n.map(ye),arguments)},find(e,t){return We(this,"find",e,t,ye,arguments)},findIndex(e,t){return We(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return We(this,"findLast",e,t,ye,arguments)},findLastIndex(e,t){return We(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return We(this,"forEach",e,t,void 0,arguments)},includes(...e){return es(this,"includes",e)},indexOf(...e){return es(this,"indexOf",e)},join(e){return bt(this).join(e)},lastIndexOf(...e){return es(this,"lastIndexOf",e)},map(e,t){return We(this,"map",e,t,void 0,arguments)},pop(){return Ft(this,"pop")},push(...e){return Ft(this,"push",e)},reduce(e,...t){return ur(this,"reduce",e,t)},reduceRight(e,...t){return ur(this,"reduceRight",e,t)},shift(){return Ft(this,"shift")},some(e,t){return We(this,"some",e,t,void 0,arguments)},splice(...e){return Ft(this,"splice",e)},toReversed(){return bt(this).toReversed()},toSorted(e){return bt(this).toSorted(e)},toSpliced(...e){return bt(this).toSpliced(...e)},unshift(...e){return Ft(this,"unshift",e)},values(){return Zn(this,"values",ye)}};function Zn(e,t,n){const s=Dn(e),r=s[t]();return s!==e&&!Me(e)&&(r._next=r.next,r.next=()=>{const i=r._next();return i.value&&(i.value=n(i.value)),i}),r}const ol=Array.prototype;function We(e,t,n,s,r,i){const o=Dn(e),l=o!==e&&!Me(e),c=o[t];if(c!==ol[t]){const d=c.apply(e,i);return l?ye(d):d}let f=n;o!==e&&(l?f=function(d,y){return n.call(this,ye(d),y,e)}:n.length>2&&(f=function(d,y){return n.call(this,d,y,e)}));const a=c.call(o,f,s);return l&&r?r(a):a}function ur(e,t,n,s){const r=Dn(e);let i=n;return r!==e&&(Me(e)?n.length>3&&(i=function(o,l,c){return n.call(this,o,l,c,e)}):i=function(o,l,c){return n.call(this,o,ye(l),c,e)}),r[t](i,...s)}function es(e,t,n){const s=J(e);me(s,"iterate",qt);const r=s[t](...n);return(r===-1||r===!1)&&Ks(n[0])?(n[0]=J(n[0]),s[t](...n)):r}function Ft(e,t,n=[]){rt(),Vs();const s=J(e)[t].apply(e,n);return Us(),it(),s}const ll=Ns("__proto__,__v_isRef,__isVue"),_i=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ye));function cl(e){Ye(e)||(e=String(e));const t=J(this);return me(t,"has",e),t.hasOwnProperty(e)}class wi{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){const r=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!r;if(n==="__v_isReadonly")return r;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(r?i?vl:Ci:i?Ei:xi).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const o=K(t);if(!r){let c;if(o&&(c=il[n]))return c;if(n==="hasOwnProperty")return cl}const l=Reflect.get(t,n,fe(t)?t:s);return(Ye(n)?_i.has(n):ll(n))||(r||me(t,"get",n),i)?l:fe(l)?o&&$s(n)?l:l.value:ne(l)?r?Vn(l):jn(l):l}}class Si extends wi{constructor(t=!1){super(!1,t)}set(t,n,s,r){let i=t[n];if(!this._isShallow){const c=yt(i);if(!Me(s)&&!yt(s)&&(i=J(i),s=J(s)),!K(t)&&fe(i)&&!fe(s))return c?!1:(i.value=s,!0)}const o=K(t)&&$s(n)?Number(n)e,ln=e=>Reflect.getPrototypeOf(e);function hl(e,t,n){return function(...s){const r=this.__v_raw,i=J(r),o=Ct(i),l=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,f=r[e](...s),a=n?ws:t?Ss:ye;return!t&&me(i,"iterate",c?_s:dt),{next(){const{value:d,done:y}=f.next();return y?{value:d,done:y}:{value:l?[a(d[0]),a(d[1])]:a(d),done:y}},[Symbol.iterator](){return this}}}}function cn(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function pl(e,t){const n={get(r){const i=this.__v_raw,o=J(i),l=J(r);e||(tt(r,l)&&me(o,"get",r),me(o,"get",l));const{has:c}=ln(o),f=t?ws:e?Ss:ye;if(c.call(o,r))return f(i.get(r));if(c.call(o,l))return f(i.get(l));i!==o&&i.get(r)},get size(){const r=this.__v_raw;return!e&&me(J(r),"iterate",dt),Reflect.get(r,"size",r)},has(r){const i=this.__v_raw,o=J(i),l=J(r);return e||(tt(r,l)&&me(o,"has",r),me(o,"has",l)),r===l?i.has(r):i.has(r)||i.has(l)},forEach(r,i){const o=this,l=o.__v_raw,c=J(l),f=t?ws:e?Ss:ye;return!e&&me(c,"iterate",dt),l.forEach((a,d)=>r.call(i,f(a),f(d),o))}};return ce(n,e?{add:cn("add"),set:cn("set"),delete:cn("delete"),clear:cn("clear")}:{add(r){!t&&!Me(r)&&!yt(r)&&(r=J(r));const i=J(this);return ln(i).has.call(i,r)||(i.add(r),qe(i,"add",r,r)),this},set(r,i){!t&&!Me(i)&&!yt(i)&&(i=J(i));const o=J(this),{has:l,get:c}=ln(o);let f=l.call(o,r);f||(r=J(r),f=l.call(o,r));const a=c.call(o,r);return o.set(r,i),f?tt(i,a)&&qe(o,"set",r,i):qe(o,"add",r,i),this},delete(r){const i=J(this),{has:o,get:l}=ln(i);let c=o.call(i,r);c||(r=J(r),c=o.call(i,r)),l&&l.call(i,r);const f=i.delete(r);return c&&qe(i,"delete",r,void 0),f},clear(){const r=J(this),i=r.size!==0,o=r.clear();return i&&qe(r,"clear",void 0,void 0),o}}),["keys","values","entries",Symbol.iterator].forEach(r=>{n[r]=hl(r,e,t)}),n}function Bs(e,t){const n=pl(e,t);return(s,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?s:Reflect.get(z(n,r)&&r in s?n:s,r,i)}const gl={get:Bs(!1,!1)},ml={get:Bs(!1,!0)},yl={get:Bs(!0,!1)};const xi=new WeakMap,Ei=new WeakMap,Ci=new WeakMap,vl=new WeakMap;function bl(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function _l(e){return e.__v_skip||!Object.isExtensible(e)?0:bl(Bo(e))}function jn(e){return yt(e)?e:Ws(e,!1,fl,gl,xi)}function wl(e){return Ws(e,!1,dl,ml,Ei)}function Vn(e){return Ws(e,!0,ul,yl,Ci)}function Ws(e,t,n,s,r){if(!ne(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=r.get(e);if(i)return i;const o=_l(e);if(o===0)return e;const l=new Proxy(e,o===2?s:n);return r.set(e,l),l}function ht(e){return yt(e)?ht(e.__v_raw):!!(e&&e.__v_isReactive)}function yt(e){return!!(e&&e.__v_isReadonly)}function Me(e){return!!(e&&e.__v_isShallow)}function Ks(e){return e?!!e.__v_raw:!1}function J(e){const t=e&&e.__v_raw;return t?J(t):e}function _n(e){return!z(e,"__v_skip")&&Object.isExtensible(e)&&li(e,"__v_skip",!0),e}const ye=e=>ne(e)?jn(e):e,Ss=e=>ne(e)?Vn(e):e;function fe(e){return e?e.__v_isRef===!0:!1}function oe(e){return Ti(e,!1)}function qs(e){return Ti(e,!0)}function Ti(e,t){return fe(e)?e:new Sl(e,t)}class Sl{constructor(t,n){this.dep=new $n,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:J(t),this._value=n?t:ye(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,s=this.__v_isShallow||Me(t)||yt(t);t=s?t:J(t),tt(t,n)&&(this._rawValue=t,this._value=s?t:ye(t),this.dep.trigger())}}function Ai(e){return fe(e)?e.value:e}const xl={get:(e,t,n)=>t==="__v_raw"?e:Ai(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const r=e[t];return fe(r)&&!fe(n)?(r.value=n,!0):Reflect.set(e,t,n,s)}};function Ri(e){return ht(e)?e:new Proxy(e,xl)}class El{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=new $n,{get:s,set:r}=t(n.track.bind(n),n.trigger.bind(n));this._get=s,this._set=r}get value(){return this._value=this._get()}set value(t){this._set(t)}}function Cl(e){return new El(e)}class Tl{constructor(t,n,s){this._object=t,this._key=n,this._defaultValue=s,this.__v_isRef=!0,this._value=void 0}get value(){const t=this._object[this._key];return this._value=t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return rl(J(this._object),this._key)}}class Al{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function Rl(e,t,n){return fe(e)?e:q(e)?new Al(e):ne(e)&&arguments.length>1?Ol(e,t,n):oe(e)}function Ol(e,t,n){const s=e[t];return fe(s)?s:new Tl(e,t,n)}class Pl{constructor(t,n,s){this.fn=t,this.setter=n,this._value=void 0,this.dep=new $n(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Kt-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=s}notify(){if(this.flags|=16,!(this.flags&8)&&te!==this)return pi(this,!0),!0}get value(){const t=this.dep.track();return yi(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Ml(e,t,n=!1){let s,r;return q(e)?s=e:(s=e.get,r=e.set),new Pl(s,r,n)}const an={},Tn=new WeakMap;let ft;function Ll(e,t=!1,n=ft){if(n){let s=Tn.get(n);s||Tn.set(n,s=[]),s.push(e)}}function Il(e,t,n=Z){const{immediate:s,deep:r,once:i,scheduler:o,augmentJob:l,call:c}=n,f=g=>r?g:Me(g)||r===!1||r===0?Ge(g,1):Ge(g);let a,d,y,v,S=!1,b=!1;if(fe(e)?(d=()=>e.value,S=Me(e)):ht(e)?(d=()=>f(e),S=!0):K(e)?(b=!0,S=e.some(g=>ht(g)||Me(g)),d=()=>e.map(g=>{if(fe(g))return g.value;if(ht(g))return f(g);if(q(g))return c?c(g,2):g()})):q(e)?t?d=c?()=>c(e,2):e:d=()=>{if(y){rt();try{y()}finally{it()}}const g=ft;ft=a;try{return c?c(e,3,[v]):e(v)}finally{ft=g}}:d=ke,t&&r){const g=d,P=r===!0?1/0:r;d=()=>Ge(g(),P)}const B=ui(),N=()=>{a.stop(),B&&Hs(B.effects,a)};if(i&&t){const g=t;t=(...P)=>{g(...P),N()}}let j=b?new Array(e.length).fill(an):an;const p=g=>{if(!(!(a.flags&1)||!a.dirty&&!g))if(t){const P=a.run();if(r||S||(b?P.some((F,$)=>tt(F,j[$])):tt(P,j))){y&&y();const F=ft;ft=a;try{const $=[P,j===an?void 0:b&&j[0]===an?[]:j,v];c?c(t,3,$):t(...$),j=P}finally{ft=F}}}else a.run()};return l&&l(p),a=new di(d),a.scheduler=o?()=>o(p,!1):p,v=g=>Ll(g,!1,a),y=a.onStop=()=>{const g=Tn.get(a);if(g){if(c)c(g,4);else for(const P of g)P();Tn.delete(a)}},t?s?p(!0):j=a.run():o?o(p.bind(null,!0),!0):a.run(),N.pause=a.pause.bind(a),N.resume=a.resume.bind(a),N.stop=N,N}function Ge(e,t=1/0,n){if(t<=0||!ne(e)||e.__v_skip||(n=n||new Set,n.has(e)))return e;if(n.add(e),t--,fe(e))Ge(e.value,t,n);else if(K(e))for(let s=0;s{Ge(s,t,n)});else if(oi(e)){for(const s in e)Ge(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&Ge(e[s],t,n)}return e}/** +* @vue/runtime-core v3.5.12 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function en(e,t,n,s){try{return s?e(...s):e()}catch(r){tn(r,t,n)}}function He(e,t,n,s){if(q(e)){const r=en(e,t,n,s);return r&&ri(r)&&r.catch(i=>{tn(i,t,n)}),r}if(K(e)){const r=[];for(let i=0;i>>1,r=we[s],i=Gt(r);i=Gt(n)?we.push(e):we.splice(Fl(t),0,e),e.flags|=1,Pi()}}function Pi(){An||(An=Oi.then(Mi))}function Hl(e){K(e)?At.push(...e):Qe&&e.id===-1?Qe.splice(wt+1,0,e):e.flags&1||(At.push(e),e.flags|=1),Pi()}function dr(e,t,n=Ve+1){for(;nGt(n)-Gt(s));if(At.length=0,Qe){Qe.push(...t);return}for(Qe=t,wt=0;wte.id==null?e.flags&2?-1:1/0:e.id;function Mi(e){try{for(Ve=0;Ve{s._d&&Tr(-1);const i=On(t);let o;try{o=e(...r)}finally{On(i),s._d&&Tr(1)}return o};return s._n=!0,s._c=!0,s._d=!0,s}function bf(e,t){if(de===null)return e;const n=Gn(de),s=e.dirs||(e.dirs=[]);for(let r=0;re.__isTeleport,Vt=e=>e&&(e.disabled||e.disabled===""),Dl=e=>e&&(e.defer||e.defer===""),hr=e=>typeof SVGElement<"u"&&e instanceof SVGElement,pr=e=>typeof MathMLElement=="function"&&e instanceof MathMLElement,xs=(e,t)=>{const n=e&&e.to;return re(n)?t?t(n):null:n},jl={name:"Teleport",__isTeleport:!0,process(e,t,n,s,r,i,o,l,c,f){const{mc:a,pc:d,pbc:y,o:{insert:v,querySelector:S,createText:b,createComment:B}}=f,N=Vt(t.props);let{shapeFlag:j,children:p,dynamicChildren:g}=t;if(e==null){const P=t.el=b(""),F=t.anchor=b("");v(P,n,s),v(F,n,s);const $=(R,_)=>{j&16&&(r&&r.isCE&&(r.ce._teleportTarget=R),a(p,R,_,r,i,o,l,c))},V=()=>{const R=t.target=xs(t.props,S),_=Fi(R,t,b,v);R&&(o!=="svg"&&hr(R)?o="svg":o!=="mathml"&&pr(R)&&(o="mathml"),N||($(R,_),wn(t,!1)))};N&&($(n,F),wn(t,!0)),Dl(t.props)?xe(V,i):V()}else{t.el=e.el,t.targetStart=e.targetStart;const P=t.anchor=e.anchor,F=t.target=e.target,$=t.targetAnchor=e.targetAnchor,V=Vt(e.props),R=V?n:F,_=V?P:$;if(o==="svg"||hr(F)?o="svg":(o==="mathml"||pr(F))&&(o="mathml"),g?(y(e.dynamicChildren,g,R,r,i,o,l),Qs(e,t,!0)):c||d(e,t,R,_,r,i,o,l,!1),N)V?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):fn(t,n,P,f,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const I=t.target=xs(t.props,S);I&&fn(t,I,null,f,0)}else V&&fn(t,F,$,f,1);wn(t,N)}},remove(e,t,n,{um:s,o:{remove:r}},i){const{shapeFlag:o,children:l,anchor:c,targetStart:f,targetAnchor:a,target:d,props:y}=e;if(d&&(r(f),r(a)),i&&r(c),o&16){const v=i||!Vt(y);for(let S=0;S{e.isMounted=!0}),ki(()=>{e.isUnmounting=!0}),e}const Re=[Function,Array],Hi={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Re,onEnter:Re,onAfterEnter:Re,onEnterCancelled:Re,onBeforeLeave:Re,onLeave:Re,onAfterLeave:Re,onLeaveCancelled:Re,onBeforeAppear:Re,onAppear:Re,onAfterAppear:Re,onAppearCancelled:Re},$i=e=>{const t=e.subTree;return t.component?$i(t.component):t},kl={name:"BaseTransition",props:Hi,setup(e,{slots:t}){const n=qn(),s=Ul();return()=>{const r=t.default&&Vi(t.default(),!0);if(!r||!r.length)return;const i=Di(r),o=J(e),{mode:l}=o;if(s.isLeaving)return ts(i);const c=gr(i);if(!c)return ts(i);let f=Es(c,o,s,n,y=>f=y);c.type!==ve&&Yt(c,f);const a=n.subTree,d=a&&gr(a);if(d&&d.type!==ve&&!ut(c,d)&&$i(n).type!==ve){const y=Es(d,o,s,n);if(Yt(d,y),l==="out-in"&&c.type!==ve)return s.isLeaving=!0,y.afterLeave=()=>{s.isLeaving=!1,n.job.flags&8||n.update(),delete y.afterLeave},ts(i);l==="in-out"&&c.type!==ve&&(y.delayLeave=(v,S,b)=>{const B=ji(s,d);B[String(d.key)]=d,v[Ze]=()=>{S(),v[Ze]=void 0,delete f.delayedLeave},f.delayedLeave=b})}return i}}};function Di(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==ve){t=n;break}}return t}const Bl=kl;function ji(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function Es(e,t,n,s,r){const{appear:i,mode:o,persisted:l=!1,onBeforeEnter:c,onEnter:f,onAfterEnter:a,onEnterCancelled:d,onBeforeLeave:y,onLeave:v,onAfterLeave:S,onLeaveCancelled:b,onBeforeAppear:B,onAppear:N,onAfterAppear:j,onAppearCancelled:p}=t,g=String(e.key),P=ji(n,e),F=(R,_)=>{R&&He(R,s,9,_)},$=(R,_)=>{const I=_[1];F(R,_),K(R)?R.every(E=>E.length<=1)&&I():R.length<=1&&I()},V={mode:o,persisted:l,beforeEnter(R){let _=c;if(!n.isMounted)if(i)_=B||c;else return;R[Ze]&&R[Ze](!0);const I=P[g];I&&ut(e,I)&&I.el[Ze]&&I.el[Ze](),F(_,[R])},enter(R){let _=f,I=a,E=d;if(!n.isMounted)if(i)_=N||f,I=j||a,E=p||d;else return;let W=!1;const se=R[un]=ae=>{W||(W=!0,ae?F(E,[R]):F(I,[R]),V.delayedLeave&&V.delayedLeave(),R[un]=void 0)};_?$(_,[R,se]):se()},leave(R,_){const I=String(e.key);if(R[un]&&R[un](!0),n.isUnmounting)return _();F(y,[R]);let E=!1;const W=R[Ze]=se=>{E||(E=!0,_(),se?F(b,[R]):F(S,[R]),R[Ze]=void 0,P[I]===e&&delete P[I])};P[I]=e,v?$(v,[R,W]):W()},clone(R){const _=Es(R,t,n,s,r);return r&&r(_),_}};return V}function ts(e){if(nn(e))return e=nt(e),e.children=null,e}function gr(e){if(!nn(e))return Ni(e.type)&&e.children?Di(e.children):e;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&q(n.default))return n.default()}}function Yt(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Yt(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Vi(e,t=!1,n){let s=[],r=0;for(let i=0;i1)for(let i=0;iPn(S,t&&(K(t)?t[b]:t),n,s,r));return}if(pt(s)&&!r)return;const i=s.shapeFlag&4?Gn(s.component):s.el,o=r?null:i,{i:l,r:c}=e,f=t&&t.r,a=l.refs===Z?l.refs={}:l.refs,d=l.setupState,y=J(d),v=d===Z?()=>!1:S=>z(y,S);if(f!=null&&f!==c&&(re(f)?(a[f]=null,v(f)&&(d[f]=null)):fe(f)&&(f.value=null)),q(c))en(c,l,12,[o,a]);else{const S=re(c),b=fe(c);if(S||b){const B=()=>{if(e.f){const N=S?v(c)?d[c]:a[c]:c.value;r?K(N)&&Hs(N,i):K(N)?N.includes(i)||N.push(i):S?(a[c]=[i],v(c)&&(d[c]=a[c])):(c.value=[i],e.k&&(a[e.k]=c.value))}else S?(a[c]=o,v(c)&&(d[c]=o)):b&&(c.value=o,e.k&&(a[e.k]=o))};o?(B.id=-1,xe(B,n)):B()}}}let mr=!1;const _t=()=>{mr||(console.error("Hydration completed but contains mismatches."),mr=!0)},Wl=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",Kl=e=>e.namespaceURI.includes("MathML"),dn=e=>{if(e.nodeType===1){if(Wl(e))return"svg";if(Kl(e))return"mathml"}},xt=e=>e.nodeType===8;function ql(e){const{mt:t,p:n,o:{patchProp:s,createText:r,nextSibling:i,parentNode:o,remove:l,insert:c,createComment:f}}=e,a=(p,g)=>{if(!g.hasChildNodes()){n(null,p,g),Rn(),g._vnode=p;return}d(g.firstChild,p,null,null,null),Rn(),g._vnode=p},d=(p,g,P,F,$,V=!1)=>{V=V||!!g.dynamicChildren;const R=xt(p)&&p.data==="[",_=()=>b(p,g,P,F,$,R),{type:I,ref:E,shapeFlag:W,patchFlag:se}=g;let ae=p.nodeType;g.el=p,se===-2&&(V=!1,g.dynamicChildren=null);let U=null;switch(I){case gt:ae!==3?g.children===""?(c(g.el=r(""),o(p),p),U=p):U=_():(p.data!==g.children&&(_t(),p.data=g.children),U=i(p));break;case ve:j(p)?(U=i(p),N(g.el=p.content.firstChild,p,P)):ae!==8||R?U=_():U=i(p);break;case kt:if(R&&(p=i(p),ae=p.nodeType),ae===1||ae===3){U=p;const Y=!g.children.length;for(let D=0;D{V=V||!!g.dynamicChildren;const{type:R,props:_,patchFlag:I,shapeFlag:E,dirs:W,transition:se}=g,ae=R==="input"||R==="option";if(ae||I!==-1){W&&Ue(g,null,P,"created");let U=!1;if(j(p)){U=io(null,se)&&P&&P.vnode.props&&P.vnode.props.appear;const D=p.content.firstChild;U&&se.beforeEnter(D),N(D,p,P),g.el=p=D}if(E&16&&!(_&&(_.innerHTML||_.textContent))){let D=v(p.firstChild,g,p,P,F,$,V);for(;D;){hn(p,1)||_t();const he=D;D=D.nextSibling,l(he)}}else if(E&8){let D=g.children;D[0]===` +`&&(p.tagName==="PRE"||p.tagName==="TEXTAREA")&&(D=D.slice(1)),p.textContent!==D&&(hn(p,0)||_t(),p.textContent=g.children)}if(_){if(ae||!V||I&48){const D=p.tagName.includes("-");for(const he in _)(ae&&(he.endsWith("value")||he==="indeterminate")||Zt(he)&&!Tt(he)||he[0]==="."||D)&&s(p,he,null,_[he],void 0,P)}else if(_.onClick)s(p,"onClick",null,_.onClick,void 0,P);else if(I&4&&ht(_.style))for(const D in _.style)_.style[D]}let Y;(Y=_&&_.onVnodeBeforeMount)&&Oe(Y,P,g),W&&Ue(g,null,P,"beforeMount"),((Y=_&&_.onVnodeMounted)||W||U)&&fo(()=>{Y&&Oe(Y,P,g),U&&se.enter(p),W&&Ue(g,null,P,"mounted")},F)}return p.nextSibling},v=(p,g,P,F,$,V,R)=>{R=R||!!g.dynamicChildren;const _=g.children,I=_.length;for(let E=0;E{const{slotScopeIds:R}=g;R&&($=$?$.concat(R):R);const _=o(p),I=v(i(p),g,_,P,F,$,V);return I&&xt(I)&&I.data==="]"?i(g.anchor=I):(_t(),c(g.anchor=f("]"),_,I),I)},b=(p,g,P,F,$,V)=>{if(hn(p.parentElement,1)||_t(),g.el=null,V){const I=B(p);for(;;){const E=i(p);if(E&&E!==I)l(E);else break}}const R=i(p),_=o(p);return l(p),n(null,g,_,R,P,F,dn(_),$),R},B=(p,g="[",P="]")=>{let F=0;for(;p;)if(p=i(p),p&&xt(p)&&(p.data===g&&F++,p.data===P)){if(F===0)return i(p);F--}return p},N=(p,g,P)=>{const F=g.parentNode;F&&F.replaceChild(p,g);let $=P;for(;$;)$.vnode.el===g&&($.vnode.el=$.subTree.el=p),$=$.parent},j=p=>p.nodeType===1&&p.tagName==="TEMPLATE";return[a,d]}const yr="data-allow-mismatch",Gl={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function hn(e,t){if(t===0||t===1)for(;e&&!e.hasAttribute(yr);)e=e.parentElement;const n=e&&e.getAttribute(yr);if(n==null)return!1;if(n==="")return!0;{const s=n.split(",");return t===0&&s.includes("children")?!0:n.split(",").includes(Gl[t])}}Hn().requestIdleCallback;Hn().cancelIdleCallback;function Yl(e,t){if(xt(e)&&e.data==="["){let n=1,s=e.nextSibling;for(;s;){if(s.nodeType===1){if(t(s)===!1)break}else if(xt(s))if(s.data==="]"){if(--n===0)break}else s.data==="["&&n++;s=s.nextSibling}}else t(e)}const pt=e=>!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function wf(e){q(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:s,delay:r=200,hydrate:i,timeout:o,suspensible:l=!0,onError:c}=e;let f=null,a,d=0;const y=()=>(d++,f=null,v()),v=()=>{let S;return f||(S=f=t().catch(b=>{if(b=b instanceof Error?b:new Error(String(b)),c)return new Promise((B,N)=>{c(b,()=>B(y()),()=>N(b),d+1)});throw b}).then(b=>S!==f&&f?f:(b&&(b.__esModule||b[Symbol.toStringTag]==="Module")&&(b=b.default),a=b,b)))};return Ys({name:"AsyncComponentWrapper",__asyncLoader:v,__asyncHydrate(S,b,B){const N=i?()=>{const j=i(B,p=>Yl(S,p));j&&(b.bum||(b.bum=[])).push(j)}:B;a?N():v().then(()=>!b.isUnmounted&&N())},get __asyncResolved(){return a},setup(){const S=ue;if(Xs(S),a)return()=>ns(a,S);const b=p=>{f=null,tn(p,S,13,!s)};if(l&&S.suspense||Pt)return v().then(p=>()=>ns(p,S)).catch(p=>(b(p),()=>s?le(s,{error:p}):null));const B=oe(!1),N=oe(),j=oe(!!r);return r&&setTimeout(()=>{j.value=!1},r),o!=null&&setTimeout(()=>{if(!B.value&&!N.value){const p=new Error(`Async component timed out after ${o}ms.`);b(p),N.value=p}},o),v().then(()=>{B.value=!0,S.parent&&nn(S.parent.vnode)&&S.parent.update()}).catch(p=>{b(p),N.value=p}),()=>{if(B.value&&a)return ns(a,S);if(N.value&&s)return le(s,{error:N.value});if(n&&!j.value)return le(n)}}})}function ns(e,t){const{ref:n,props:s,children:r,ce:i}=t.vnode,o=le(e,s,r);return o.ref=n,o.ce=i,delete t.vnode.ce,o}const nn=e=>e.type.__isKeepAlive;function Xl(e,t){Ui(e,"a",t)}function Jl(e,t){Ui(e,"da",t)}function Ui(e,t,n=ue){const s=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(kn(t,s,n),n){let r=n.parent;for(;r&&r.parent;)nn(r.parent.vnode)&&zl(s,t,n,r),r=r.parent}}function zl(e,t,n,s){const r=kn(t,e,s,!0);Bn(()=>{Hs(s[t],r)},n)}function kn(e,t,n=ue,s=!1){if(n){const r=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...o)=>{rt();const l=sn(n),c=He(t,n,e,o);return l(),it(),c});return s?r.unshift(i):r.push(i),i}}const Xe=e=>(t,n=ue)=>{(!Pt||e==="sp")&&kn(e,(...s)=>t(...s),n)},Ql=Xe("bm"),Lt=Xe("m"),Zl=Xe("bu"),ec=Xe("u"),ki=Xe("bum"),Bn=Xe("um"),tc=Xe("sp"),nc=Xe("rtg"),sc=Xe("rtc");function rc(e,t=ue){kn("ec",e,t)}const Bi="components";function Sf(e,t){return Ki(Bi,e,!0,t)||e}const Wi=Symbol.for("v-ndc");function xf(e){return re(e)?Ki(Bi,e,!1)||e:e||Wi}function Ki(e,t,n=!0,s=!1){const r=de||ue;if(r){const i=r.type;{const l=Bc(i,!1);if(l&&(l===t||l===Le(t)||l===Fn(Le(t))))return i}const o=vr(r[e]||i[e],t)||vr(r.appContext[e],t);return!o&&s?i:o}}function vr(e,t){return e&&(e[t]||e[Le(t)]||e[Fn(Le(t))])}function Ef(e,t,n,s){let r;const i=n,o=K(e);if(o||re(e)){const l=o&&ht(e);let c=!1;l&&(c=!Me(e),e=Dn(e)),r=new Array(e.length);for(let f=0,a=e.length;ft(l,c,void 0,i));else{const l=Object.keys(e);r=new Array(l.length);for(let c=0,f=l.length;cJt(t)?!(t.type===ve||t.type===Se&&!qi(t.children)):!0)?e:null}function Tf(e,t){const n={};for(const s in e)n[/[A-Z]/.test(s)?`on:${s}`:vn(s)]=e[s];return n}const Cs=e=>e?mo(e)?Gn(e):Cs(e.parent):null,Ut=ce(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Cs(e.parent),$root:e=>Cs(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Js(e),$forceUpdate:e=>e.f||(e.f=()=>{Gs(e.update)}),$nextTick:e=>e.n||(e.n=Un.bind(e.proxy)),$watch:e=>Tc.bind(e)}),ss=(e,t)=>e!==Z&&!e.__isScriptSetup&&z(e,t),ic={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:r,props:i,accessCache:o,type:l,appContext:c}=e;let f;if(t[0]!=="$"){const v=o[t];if(v!==void 0)switch(v){case 1:return s[t];case 2:return r[t];case 4:return n[t];case 3:return i[t]}else{if(ss(s,t))return o[t]=1,s[t];if(r!==Z&&z(r,t))return o[t]=2,r[t];if((f=e.propsOptions[0])&&z(f,t))return o[t]=3,i[t];if(n!==Z&&z(n,t))return o[t]=4,n[t];Ts&&(o[t]=0)}}const a=Ut[t];let d,y;if(a)return t==="$attrs"&&me(e.attrs,"get",""),a(e);if((d=l.__cssModules)&&(d=d[t]))return d;if(n!==Z&&z(n,t))return o[t]=4,n[t];if(y=c.config.globalProperties,z(y,t))return y[t]},set({_:e},t,n){const{data:s,setupState:r,ctx:i}=e;return ss(r,t)?(r[t]=n,!0):s!==Z&&z(s,t)?(s[t]=n,!0):z(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:r,propsOptions:i}},o){let l;return!!n[o]||e!==Z&&z(e,o)||ss(t,o)||(l=i[0])&&z(l,o)||z(s,o)||z(Ut,o)||z(r.config.globalProperties,o)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:z(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Af(){return oc().slots}function oc(){const e=qn();return e.setupContext||(e.setupContext=vo(e))}function br(e){return K(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Ts=!0;function lc(e){const t=Js(e),n=e.proxy,s=e.ctx;Ts=!1,t.beforeCreate&&_r(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:o,watch:l,provide:c,inject:f,created:a,beforeMount:d,mounted:y,beforeUpdate:v,updated:S,activated:b,deactivated:B,beforeDestroy:N,beforeUnmount:j,destroyed:p,unmounted:g,render:P,renderTracked:F,renderTriggered:$,errorCaptured:V,serverPrefetch:R,expose:_,inheritAttrs:I,components:E,directives:W,filters:se}=t;if(f&&cc(f,s,null),o)for(const Y in o){const D=o[Y];q(D)&&(s[Y]=D.bind(n))}if(r){const Y=r.call(n,n);ne(Y)&&(e.data=jn(Y))}if(Ts=!0,i)for(const Y in i){const D=i[Y],he=q(D)?D.bind(n,n):q(D.get)?D.get.bind(n,n):ke,rn=!q(D)&&q(D.set)?D.set.bind(n):ke,ot=ie({get:he,set:rn});Object.defineProperty(s,Y,{enumerable:!0,configurable:!0,get:()=>ot.value,set:De=>ot.value=De})}if(l)for(const Y in l)Gi(l[Y],s,n,Y);if(c){const Y=q(c)?c.call(n):c;Reflect.ownKeys(Y).forEach(D=>{pc(D,Y[D])})}a&&_r(a,e,"c");function U(Y,D){K(D)?D.forEach(he=>Y(he.bind(n))):D&&Y(D.bind(n))}if(U(Ql,d),U(Lt,y),U(Zl,v),U(ec,S),U(Xl,b),U(Jl,B),U(rc,V),U(sc,F),U(nc,$),U(ki,j),U(Bn,g),U(tc,R),K(_))if(_.length){const Y=e.exposed||(e.exposed={});_.forEach(D=>{Object.defineProperty(Y,D,{get:()=>n[D],set:he=>n[D]=he})})}else e.exposed||(e.exposed={});P&&e.render===ke&&(e.render=P),I!=null&&(e.inheritAttrs=I),E&&(e.components=E),W&&(e.directives=W),R&&Xs(e)}function cc(e,t,n=ke){K(e)&&(e=As(e));for(const s in e){const r=e[s];let i;ne(r)?"default"in r?i=Ot(r.from||s,r.default,!0):i=Ot(r.from||s):i=Ot(r),fe(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):t[s]=i}}function _r(e,t,n){He(K(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function Gi(e,t,n,s){let r=s.includes(".")?lo(n,s):()=>n[s];if(re(e)){const i=t[e];q(i)&&Fe(r,i)}else if(q(e))Fe(r,e.bind(n));else if(ne(e))if(K(e))e.forEach(i=>Gi(i,t,n,s));else{const i=q(e.handler)?e.handler.bind(n):t[e.handler];q(i)&&Fe(r,i,e)}}function Js(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:o}}=e.appContext,l=i.get(t);let c;return l?c=l:!r.length&&!n&&!s?c=t:(c={},r.length&&r.forEach(f=>Mn(c,f,o,!0)),Mn(c,t,o)),ne(t)&&i.set(t,c),c}function Mn(e,t,n,s=!1){const{mixins:r,extends:i}=t;i&&Mn(e,i,n,!0),r&&r.forEach(o=>Mn(e,o,n,!0));for(const o in t)if(!(s&&o==="expose")){const l=ac[o]||n&&n[o];e[o]=l?l(e[o],t[o]):t[o]}return e}const ac={data:wr,props:Sr,emits:Sr,methods:$t,computed:$t,beforeCreate:be,created:be,beforeMount:be,mounted:be,beforeUpdate:be,updated:be,beforeDestroy:be,beforeUnmount:be,destroyed:be,unmounted:be,activated:be,deactivated:be,errorCaptured:be,serverPrefetch:be,components:$t,directives:$t,watch:uc,provide:wr,inject:fc};function wr(e,t){return t?e?function(){return ce(q(e)?e.call(this,this):e,q(t)?t.call(this,this):t)}:t:e}function fc(e,t){return $t(As(e),As(t))}function As(e){if(K(e)){const t={};for(let n=0;n1)return n&&q(t)?t.call(s&&s.proxy):t}}const Xi={},Ji=()=>Object.create(Xi),zi=e=>Object.getPrototypeOf(e)===Xi;function gc(e,t,n,s=!1){const r={},i=Ji();e.propsDefaults=Object.create(null),Qi(e,t,r,i);for(const o in e.propsOptions[0])o in r||(r[o]=void 0);n?e.props=s?r:wl(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function mc(e,t,n,s){const{props:r,attrs:i,vnode:{patchFlag:o}}=e,l=J(r),[c]=e.propsOptions;let f=!1;if((s||o>0)&&!(o&16)){if(o&8){const a=e.vnode.dynamicProps;for(let d=0;d{c=!0;const[y,v]=Zi(d,t,!0);ce(o,y),v&&l.push(...v)};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}if(!i&&!c)return ne(e)&&s.set(e,Et),Et;if(K(i))for(let a=0;ae[0]==="_"||e==="$stable",zs=e=>K(e)?e.map(Pe):[Pe(e)],vc=(e,t,n)=>{if(t._n)return t;const s=$l((...r)=>zs(t(...r)),n);return s._c=!1,s},to=(e,t,n)=>{const s=e._ctx;for(const r in e){if(eo(r))continue;const i=e[r];if(q(i))t[r]=vc(r,i,s);else if(i!=null){const o=zs(i);t[r]=()=>o}}},no=(e,t)=>{const n=zs(t);e.slots.default=()=>n},so=(e,t,n)=>{for(const s in t)(n||s!=="_")&&(e[s]=t[s])},bc=(e,t,n)=>{const s=e.slots=Ji();if(e.vnode.shapeFlag&32){const r=t._;r?(so(s,t,n),n&&li(s,"_",r,!0)):to(t,s)}else t&&no(e,t)},_c=(e,t,n)=>{const{vnode:s,slots:r}=e;let i=!0,o=Z;if(s.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:so(r,t,n):(i=!t.$stable,to(t,r)),o=t}else t&&(no(e,t),o={default:1});if(i)for(const l in r)!eo(l)&&o[l]==null&&delete r[l]},xe=fo;function wc(e){return ro(e)}function Sc(e){return ro(e,ql)}function ro(e,t){const n=Hn();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:o,createText:l,createComment:c,setText:f,setElementText:a,parentNode:d,nextSibling:y,setScopeId:v=ke,insertStaticContent:S}=e,b=(u,h,m,C=null,w=null,x=null,M=void 0,O=null,A=!!h.dynamicChildren)=>{if(u===h)return;u&&!ut(u,h)&&(C=on(u),De(u,w,x,!0),u=null),h.patchFlag===-2&&(A=!1,h.dynamicChildren=null);const{type:T,ref:k,shapeFlag:L}=h;switch(T){case gt:B(u,h,m,C);break;case ve:N(u,h,m,C);break;case kt:u==null&&j(h,m,C,M);break;case Se:E(u,h,m,C,w,x,M,O,A);break;default:L&1?P(u,h,m,C,w,x,M,O,A):L&6?W(u,h,m,C,w,x,M,O,A):(L&64||L&128)&&T.process(u,h,m,C,w,x,M,O,A,vt)}k!=null&&w&&Pn(k,u&&u.ref,x,h||u,!h)},B=(u,h,m,C)=>{if(u==null)s(h.el=l(h.children),m,C);else{const w=h.el=u.el;h.children!==u.children&&f(w,h.children)}},N=(u,h,m,C)=>{u==null?s(h.el=c(h.children||""),m,C):h.el=u.el},j=(u,h,m,C)=>{[u.el,u.anchor]=S(u.children,h,m,C,u.el,u.anchor)},p=({el:u,anchor:h},m,C)=>{let w;for(;u&&u!==h;)w=y(u),s(u,m,C),u=w;s(h,m,C)},g=({el:u,anchor:h})=>{let m;for(;u&&u!==h;)m=y(u),r(u),u=m;r(h)},P=(u,h,m,C,w,x,M,O,A)=>{h.type==="svg"?M="svg":h.type==="math"&&(M="mathml"),u==null?F(h,m,C,w,x,M,O,A):R(u,h,w,x,M,O,A)},F=(u,h,m,C,w,x,M,O)=>{let A,T;const{props:k,shapeFlag:L,transition:H,dirs:G}=u;if(A=u.el=o(u.type,x,k&&k.is,k),L&8?a(A,u.children):L&16&&V(u.children,A,null,C,w,rs(u,x),M,O),G&&Ue(u,null,C,"created"),$(A,u,u.scopeId,M,C),k){for(const ee in k)ee!=="value"&&!Tt(ee)&&i(A,ee,null,k[ee],x,C);"value"in k&&i(A,"value",null,k.value,x),(T=k.onVnodeBeforeMount)&&Oe(T,C,u)}G&&Ue(u,null,C,"beforeMount");const X=io(w,H);X&&H.beforeEnter(A),s(A,h,m),((T=k&&k.onVnodeMounted)||X||G)&&xe(()=>{T&&Oe(T,C,u),X&&H.enter(A),G&&Ue(u,null,C,"mounted")},w)},$=(u,h,m,C,w)=>{if(m&&v(u,m),C)for(let x=0;x{for(let T=A;T{const O=h.el=u.el;let{patchFlag:A,dynamicChildren:T,dirs:k}=h;A|=u.patchFlag&16;const L=u.props||Z,H=h.props||Z;let G;if(m&<(m,!1),(G=H.onVnodeBeforeUpdate)&&Oe(G,m,h,u),k&&Ue(h,u,m,"beforeUpdate"),m&<(m,!0),(L.innerHTML&&H.innerHTML==null||L.textContent&&H.textContent==null)&&a(O,""),T?_(u.dynamicChildren,T,O,m,C,rs(h,w),x):M||D(u,h,O,null,m,C,rs(h,w),x,!1),A>0){if(A&16)I(O,L,H,m,w);else if(A&2&&L.class!==H.class&&i(O,"class",null,H.class,w),A&4&&i(O,"style",L.style,H.style,w),A&8){const X=h.dynamicProps;for(let ee=0;ee{G&&Oe(G,m,h,u),k&&Ue(h,u,m,"updated")},C)},_=(u,h,m,C,w,x,M)=>{for(let O=0;O{if(h!==m){if(h!==Z)for(const x in h)!Tt(x)&&!(x in m)&&i(u,x,h[x],null,w,C);for(const x in m){if(Tt(x))continue;const M=m[x],O=h[x];M!==O&&x!=="value"&&i(u,x,O,M,w,C)}"value"in m&&i(u,"value",h.value,m.value,w)}},E=(u,h,m,C,w,x,M,O,A)=>{const T=h.el=u?u.el:l(""),k=h.anchor=u?u.anchor:l("");let{patchFlag:L,dynamicChildren:H,slotScopeIds:G}=h;G&&(O=O?O.concat(G):G),u==null?(s(T,m,C),s(k,m,C),V(h.children||[],m,k,w,x,M,O,A)):L>0&&L&64&&H&&u.dynamicChildren?(_(u.dynamicChildren,H,m,w,x,M,O),(h.key!=null||w&&h===w.subTree)&&Qs(u,h,!0)):D(u,h,m,k,w,x,M,O,A)},W=(u,h,m,C,w,x,M,O,A)=>{h.slotScopeIds=O,u==null?h.shapeFlag&512?w.ctx.activate(h,m,C,M,A):se(h,m,C,w,x,M,A):ae(u,h,A)},se=(u,h,m,C,w,x,M)=>{const O=u.component=jc(u,C,w);if(nn(u)&&(O.ctx.renderer=vt),Vc(O,!1,M),O.asyncDep){if(w&&w.registerDep(O,U,M),!u.el){const A=O.subTree=le(ve);N(null,A,h,m)}}else U(O,u,h,m,w,x,M)},ae=(u,h,m)=>{const C=h.component=u.component;if(Mc(u,h,m))if(C.asyncDep&&!C.asyncResolved){Y(C,h,m);return}else C.next=h,C.update();else h.el=u.el,C.vnode=h},U=(u,h,m,C,w,x,M)=>{const O=()=>{if(u.isMounted){let{next:L,bu:H,u:G,parent:X,vnode:ee}=u;{const Ce=oo(u);if(Ce){L&&(L.el=ee.el,Y(u,L,M)),Ce.asyncDep.then(()=>{u.isUnmounted||O()});return}}let Q=L,Ee;lt(u,!1),L?(L.el=ee.el,Y(u,L,M)):L=ee,H&&bn(H),(Ee=L.props&&L.props.onVnodeBeforeUpdate)&&Oe(Ee,X,L,ee),lt(u,!0);const pe=is(u),Ie=u.subTree;u.subTree=pe,b(Ie,pe,d(Ie.el),on(Ie),u,w,x),L.el=pe.el,Q===null&&Lc(u,pe.el),G&&xe(G,w),(Ee=L.props&&L.props.onVnodeUpdated)&&xe(()=>Oe(Ee,X,L,ee),w)}else{let L;const{el:H,props:G}=h,{bm:X,m:ee,parent:Q,root:Ee,type:pe}=u,Ie=pt(h);if(lt(u,!1),X&&bn(X),!Ie&&(L=G&&G.onVnodeBeforeMount)&&Oe(L,Q,h),lt(u,!0),H&&Jn){const Ce=()=>{u.subTree=is(u),Jn(H,u.subTree,u,w,null)};Ie&&pe.__asyncHydrate?pe.__asyncHydrate(H,u,Ce):Ce()}else{Ee.ce&&Ee.ce._injectChildStyle(pe);const Ce=u.subTree=is(u);b(null,Ce,m,C,u,w,x),h.el=Ce.el}if(ee&&xe(ee,w),!Ie&&(L=G&&G.onVnodeMounted)){const Ce=h;xe(()=>Oe(L,Q,Ce),w)}(h.shapeFlag&256||Q&&pt(Q.vnode)&&Q.vnode.shapeFlag&256)&&u.a&&xe(u.a,w),u.isMounted=!0,h=m=C=null}};u.scope.on();const A=u.effect=new di(O);u.scope.off();const T=u.update=A.run.bind(A),k=u.job=A.runIfDirty.bind(A);k.i=u,k.id=u.uid,A.scheduler=()=>Gs(k),lt(u,!0),T()},Y=(u,h,m)=>{h.component=u;const C=u.vnode.props;u.vnode=h,u.next=null,mc(u,h.props,C,m),_c(u,h.children,m),rt(),dr(u),it()},D=(u,h,m,C,w,x,M,O,A=!1)=>{const T=u&&u.children,k=u?u.shapeFlag:0,L=h.children,{patchFlag:H,shapeFlag:G}=h;if(H>0){if(H&128){rn(T,L,m,C,w,x,M,O,A);return}else if(H&256){he(T,L,m,C,w,x,M,O,A);return}}G&8?(k&16&&It(T,w,x),L!==T&&a(m,L)):k&16?G&16?rn(T,L,m,C,w,x,M,O,A):It(T,w,x,!0):(k&8&&a(m,""),G&16&&V(L,m,C,w,x,M,O,A))},he=(u,h,m,C,w,x,M,O,A)=>{u=u||Et,h=h||Et;const T=u.length,k=h.length,L=Math.min(T,k);let H;for(H=0;Hk?It(u,w,x,!0,!1,L):V(h,m,C,w,x,M,O,A,L)},rn=(u,h,m,C,w,x,M,O,A)=>{let T=0;const k=h.length;let L=u.length-1,H=k-1;for(;T<=L&&T<=H;){const G=u[T],X=h[T]=A?et(h[T]):Pe(h[T]);if(ut(G,X))b(G,X,m,null,w,x,M,O,A);else break;T++}for(;T<=L&&T<=H;){const G=u[L],X=h[H]=A?et(h[H]):Pe(h[H]);if(ut(G,X))b(G,X,m,null,w,x,M,O,A);else break;L--,H--}if(T>L){if(T<=H){const G=H+1,X=GH)for(;T<=L;)De(u[T],w,x,!0),T++;else{const G=T,X=T,ee=new Map;for(T=X;T<=H;T++){const Te=h[T]=A?et(h[T]):Pe(h[T]);Te.key!=null&&ee.set(Te.key,T)}let Q,Ee=0;const pe=H-X+1;let Ie=!1,Ce=0;const Nt=new Array(pe);for(T=0;T=pe){De(Te,w,x,!0);continue}let je;if(Te.key!=null)je=ee.get(Te.key);else for(Q=X;Q<=H;Q++)if(Nt[Q-X]===0&&ut(Te,h[Q])){je=Q;break}je===void 0?De(Te,w,x,!0):(Nt[je-X]=T+1,je>=Ce?Ce=je:Ie=!0,b(Te,h[je],m,null,w,x,M,O,A),Ee++)}const lr=Ie?xc(Nt):Et;for(Q=lr.length-1,T=pe-1;T>=0;T--){const Te=X+T,je=h[Te],cr=Te+1{const{el:x,type:M,transition:O,children:A,shapeFlag:T}=u;if(T&6){ot(u.component.subTree,h,m,C);return}if(T&128){u.suspense.move(h,m,C);return}if(T&64){M.move(u,h,m,vt);return}if(M===Se){s(x,h,m);for(let L=0;LO.enter(x),w);else{const{leave:L,delayLeave:H,afterLeave:G}=O,X=()=>s(x,h,m),ee=()=>{L(x,()=>{X(),G&&G()})};H?H(x,X,ee):ee()}else s(x,h,m)},De=(u,h,m,C=!1,w=!1)=>{const{type:x,props:M,ref:O,children:A,dynamicChildren:T,shapeFlag:k,patchFlag:L,dirs:H,cacheIndex:G}=u;if(L===-2&&(w=!1),O!=null&&Pn(O,null,m,u,!0),G!=null&&(h.renderCache[G]=void 0),k&256){h.ctx.deactivate(u);return}const X=k&1&&H,ee=!pt(u);let Q;if(ee&&(Q=M&&M.onVnodeBeforeUnmount)&&Oe(Q,h,u),k&6)Vo(u.component,m,C);else{if(k&128){u.suspense.unmount(m,C);return}X&&Ue(u,null,h,"beforeUnmount"),k&64?u.type.remove(u,h,m,vt,C):T&&!T.hasOnce&&(x!==Se||L>0&&L&64)?It(T,h,m,!1,!0):(x===Se&&L&384||!w&&k&16)&&It(A,h,m),C&&ir(u)}(ee&&(Q=M&&M.onVnodeUnmounted)||X)&&xe(()=>{Q&&Oe(Q,h,u),X&&Ue(u,null,h,"unmounted")},m)},ir=u=>{const{type:h,el:m,anchor:C,transition:w}=u;if(h===Se){jo(m,C);return}if(h===kt){g(u);return}const x=()=>{r(m),w&&!w.persisted&&w.afterLeave&&w.afterLeave()};if(u.shapeFlag&1&&w&&!w.persisted){const{leave:M,delayLeave:O}=w,A=()=>M(m,x);O?O(u.el,x,A):A()}else x()},jo=(u,h)=>{let m;for(;u!==h;)m=y(u),r(u),u=m;r(h)},Vo=(u,h,m)=>{const{bum:C,scope:w,job:x,subTree:M,um:O,m:A,a:T}=u;Er(A),Er(T),C&&bn(C),w.stop(),x&&(x.flags|=8,De(M,u,h,m)),O&&xe(O,h),xe(()=>{u.isUnmounted=!0},h),h&&h.pendingBranch&&!h.isUnmounted&&u.asyncDep&&!u.asyncResolved&&u.suspenseId===h.pendingId&&(h.deps--,h.deps===0&&h.resolve())},It=(u,h,m,C=!1,w=!1,x=0)=>{for(let M=x;M{if(u.shapeFlag&6)return on(u.component.subTree);if(u.shapeFlag&128)return u.suspense.next();const h=y(u.anchor||u.el),m=h&&h[Ii];return m?y(m):h};let Yn=!1;const or=(u,h,m)=>{u==null?h._vnode&&De(h._vnode,null,null,!0):b(h._vnode||null,u,h,null,null,null,m),h._vnode=u,Yn||(Yn=!0,dr(),Rn(),Yn=!1)},vt={p:b,um:De,m:ot,r:ir,mt:se,mc:V,pc:D,pbc:_,n:on,o:e};let Xn,Jn;return t&&([Xn,Jn]=t(vt)),{render:or,hydrate:Xn,createApp:hc(or,Xn)}}function rs({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function lt({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function io(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Qs(e,t,n=!1){const s=e.children,r=t.children;if(K(s)&&K(r))for(let i=0;i>1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=t[o];return n}function oo(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:oo(t)}function Er(e){if(e)for(let t=0;tOt(Ec);function Zs(e,t){return Wn(e,null,t)}function Rf(e,t){return Wn(e,null,{flush:"post"})}function Fe(e,t,n){return Wn(e,t,n)}function Wn(e,t,n=Z){const{immediate:s,deep:r,flush:i,once:o}=n,l=ce({},n),c=t&&s||!t&&i!=="post";let f;if(Pt){if(i==="sync"){const v=Cc();f=v.__watcherHandles||(v.__watcherHandles=[])}else if(!c){const v=()=>{};return v.stop=ke,v.resume=ke,v.pause=ke,v}}const a=ue;l.call=(v,S,b)=>He(v,a,S,b);let d=!1;i==="post"?l.scheduler=v=>{xe(v,a&&a.suspense)}:i!=="sync"&&(d=!0,l.scheduler=(v,S)=>{S?v():Gs(v)}),l.augmentJob=v=>{t&&(v.flags|=4),d&&(v.flags|=2,a&&(v.id=a.uid,v.i=a))};const y=Il(e,t,l);return Pt&&(f?f.push(y):c&&y()),y}function Tc(e,t,n){const s=this.proxy,r=re(e)?e.includes(".")?lo(s,e):()=>s[e]:e.bind(s,s);let i;q(t)?i=t:(i=t.handler,n=t);const o=sn(this),l=Wn(r,i.bind(s),n);return o(),l}function lo(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;rt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Le(t)}Modifiers`]||e[`${st(t)}Modifiers`];function Rc(e,t,...n){if(e.isUnmounted)return;const s=e.vnode.props||Z;let r=n;const i=t.startsWith("update:"),o=i&&Ac(s,t.slice(7));o&&(o.trim&&(r=n.map(a=>re(a)?a.trim():a)),o.number&&(r=n.map(vs)));let l,c=s[l=vn(t)]||s[l=vn(Le(t))];!c&&i&&(c=s[l=vn(st(t))]),c&&He(c,e,6,r);const f=s[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,He(f,e,6,r)}}function co(e,t,n=!1){const s=t.emitsCache,r=s.get(e);if(r!==void 0)return r;const i=e.emits;let o={},l=!1;if(!q(e)){const c=f=>{const a=co(f,t,!0);a&&(l=!0,ce(o,a))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(ne(e)&&s.set(e,null),null):(K(i)?i.forEach(c=>o[c]=null):ce(o,i),ne(e)&&s.set(e,o),o)}function Kn(e,t){return!e||!Zt(t)?!1:(t=t.slice(2).replace(/Once$/,""),z(e,t[0].toLowerCase()+t.slice(1))||z(e,st(t))||z(e,t))}function is(e){const{type:t,vnode:n,proxy:s,withProxy:r,propsOptions:[i],slots:o,attrs:l,emit:c,render:f,renderCache:a,props:d,data:y,setupState:v,ctx:S,inheritAttrs:b}=e,B=On(e);let N,j;try{if(n.shapeFlag&4){const g=r||s,P=g;N=Pe(f.call(P,g,a,d,v,y,S)),j=l}else{const g=t;N=Pe(g.length>1?g(d,{attrs:l,slots:o,emit:c}):g(d,null)),j=t.props?l:Oc(l)}}catch(g){Bt.length=0,tn(g,e,1),N=le(ve)}let p=N;if(j&&b!==!1){const g=Object.keys(j),{shapeFlag:P}=p;g.length&&P&7&&(i&&g.some(Fs)&&(j=Pc(j,i)),p=nt(p,j,!1,!0))}return n.dirs&&(p=nt(p,null,!1,!0),p.dirs=p.dirs?p.dirs.concat(n.dirs):n.dirs),n.transition&&Yt(p,n.transition),N=p,On(B),N}const Oc=e=>{let t;for(const n in e)(n==="class"||n==="style"||Zt(n))&&((t||(t={}))[n]=e[n]);return t},Pc=(e,t)=>{const n={};for(const s in e)(!Fs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function Mc(e,t,n){const{props:s,children:r,component:i}=e,{props:o,children:l,patchFlag:c}=t,f=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return s?Cr(s,o,f):!!o;if(c&8){const a=t.dynamicProps;for(let d=0;de.__isSuspense;function fo(e,t){t&&t.pendingBranch?K(e)?t.effects.push(...e):t.effects.push(e):Hl(e)}const Se=Symbol.for("v-fgt"),gt=Symbol.for("v-txt"),ve=Symbol.for("v-cmt"),kt=Symbol.for("v-stc"),Bt=[];let Ae=null;function Os(e=!1){Bt.push(Ae=e?null:[])}function Ic(){Bt.pop(),Ae=Bt[Bt.length-1]||null}let Xt=1;function Tr(e){Xt+=e,e<0&&Ae&&(Ae.hasOnce=!0)}function uo(e){return e.dynamicChildren=Xt>0?Ae||Et:null,Ic(),Xt>0&&Ae&&Ae.push(e),e}function Of(e,t,n,s,r,i){return uo(po(e,t,n,s,r,i,!0))}function Ps(e,t,n,s,r){return uo(le(e,t,n,s,r,!0))}function Jt(e){return e?e.__v_isVNode===!0:!1}function ut(e,t){return e.type===t.type&&e.key===t.key}const ho=({key:e})=>e??null,Sn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?re(e)||fe(e)||q(e)?{i:de,r:e,k:t,f:!!n}:e:null);function po(e,t=null,n=null,s=0,r=null,i=e===Se?0:1,o=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&ho(t),ref:t&&Sn(t),scopeId:Li,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:de};return l?(er(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=re(n)?8:16),Xt>0&&!o&&Ae&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&Ae.push(c),c}const le=Nc;function Nc(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===Wi)&&(e=ve),Jt(e)){const l=nt(e,t,!0);return n&&er(l,n),Xt>0&&!i&&Ae&&(l.shapeFlag&6?Ae[Ae.indexOf(e)]=l:Ae.push(l)),l.patchFlag=-2,l}if(Wc(e)&&(e=e.__vccOpts),t){t=Fc(t);let{class:l,style:c}=t;l&&!re(l)&&(t.class=js(l)),ne(c)&&(Ks(c)&&!K(c)&&(c=ce({},c)),t.style=Ds(c))}const o=re(e)?1:ao(e)?128:Ni(e)?64:ne(e)?4:q(e)?2:0;return po(e,t,n,s,r,o,i,!0)}function Fc(e){return e?Ks(e)||zi(e)?ce({},e):e:null}function nt(e,t,n=!1,s=!1){const{props:r,ref:i,patchFlag:o,children:l,transition:c}=e,f=t?Hc(r||{},t):r,a={__v_isVNode:!0,__v_skip:!0,type:e.type,props:f,key:f&&ho(f),ref:t&&t.ref?n&&i?K(i)?i.concat(Sn(t)):[i,Sn(t)]:Sn(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Se?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&nt(e.ssContent),ssFallback:e.ssFallback&&nt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&s&&Yt(a,c.clone(a)),a}function go(e=" ",t=0){return le(gt,null,e,t)}function Pf(e,t){const n=le(kt,null,e);return n.staticCount=t,n}function Mf(e="",t=!1){return t?(Os(),Ps(ve,null,e)):le(ve,null,e)}function Pe(e){return e==null||typeof e=="boolean"?le(ve):K(e)?le(Se,null,e.slice()):Jt(e)?et(e):le(gt,null,String(e))}function et(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:nt(e)}function er(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(K(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),er(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!zi(t)?t._ctx=de:r===3&&de&&(de.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else q(t)?(t={default:t,_ctx:de},n=32):(t=String(t),s&64?(n=16,t=[go(t)]):n=8);e.children=t,e.shapeFlag|=n}function Hc(...e){const t={};for(let n=0;nue||de;let Ln,Ms;{const e=Hn(),t=(n,s)=>{let r;return(r=e[n])||(r=e[n]=[]),r.push(s),i=>{r.length>1?r.forEach(o=>o(i)):r[0](i)}};Ln=t("__VUE_INSTANCE_SETTERS__",n=>ue=n),Ms=t("__VUE_SSR_SETTERS__",n=>Pt=n)}const sn=e=>{const t=ue;return Ln(e),e.scope.on(),()=>{e.scope.off(),Ln(t)}},Ar=()=>{ue&&ue.scope.off(),Ln(null)};function mo(e){return e.vnode.shapeFlag&4}let Pt=!1;function Vc(e,t=!1,n=!1){t&&Ms(t);const{props:s,children:r}=e.vnode,i=mo(e);gc(e,s,i,t),bc(e,r,n);const o=i?Uc(e,t):void 0;return t&&Ms(!1),o}function Uc(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,ic);const{setup:s}=n;if(s){rt();const r=e.setupContext=s.length>1?vo(e):null,i=sn(e),o=en(s,e,0,[e.props,r]),l=ri(o);if(it(),i(),(l||e.sp)&&!pt(e)&&Xs(e),l){if(o.then(Ar,Ar),t)return o.then(c=>{Rr(e,c,t)}).catch(c=>{tn(c,e,0)});e.asyncDep=o}else Rr(e,o,t)}else yo(e,t)}function Rr(e,t,n){q(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ne(t)&&(e.setupState=Ri(t)),yo(e,n)}let Or;function yo(e,t,n){const s=e.type;if(!e.render){if(!t&&Or&&!s.render){const r=s.template||Js(e).template;if(r){const{isCustomElement:i,compilerOptions:o}=e.appContext.config,{delimiters:l,compilerOptions:c}=s,f=ce(ce({isCustomElement:i,delimiters:l},o),c);s.render=Or(r,f)}}e.render=s.render||ke}{const r=sn(e);rt();try{lc(e)}finally{it(),r()}}}const kc={get(e,t){return me(e,"get",""),e[t]}};function vo(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,kc),slots:e.slots,emit:e.emit,expose:t}}function Gn(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(Ri(_n(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Ut)return Ut[n](e)},has(t,n){return n in t||n in Ut}})):e.proxy}function Bc(e,t=!0){return q(e)?e.displayName||e.name:e.name||t&&e.__name}function Wc(e){return q(e)&&"__vccOpts"in e}const ie=(e,t)=>Ml(e,t,Pt);function Ls(e,t,n){const s=arguments.length;return s===2?ne(t)&&!K(t)?Jt(t)?le(e,null,[t]):le(e,t):le(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&Jt(n)&&(n=[n]),le(e,t,n))}const Kc="3.5.12";/** +* @vue/runtime-dom v3.5.12 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let Is;const Pr=typeof window<"u"&&window.trustedTypes;if(Pr)try{Is=Pr.createPolicy("vue",{createHTML:e=>e})}catch{}const bo=Is?e=>Is.createHTML(e):e=>e,qc="http://www.w3.org/2000/svg",Gc="http://www.w3.org/1998/Math/MathML",Ke=typeof document<"u"?document:null,Mr=Ke&&Ke.createElement("template"),Yc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t==="svg"?Ke.createElementNS(qc,e):t==="mathml"?Ke.createElementNS(Gc,e):n?Ke.createElement(e,{is:n}):Ke.createElement(e);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>Ke.createTextNode(e),createComment:e=>Ke.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ke.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{Mr.innerHTML=bo(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const l=Mr.content;if(s==="svg"||s==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Je="transition",Ht="animation",zt=Symbol("_vtc"),_o={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Xc=ce({},Hi,_o),Jc=e=>(e.displayName="Transition",e.props=Xc,e),Lf=Jc((e,{slots:t})=>Ls(Bl,zc(e),t)),ct=(e,t=[])=>{K(e)?e.forEach(n=>n(...t)):e&&e(...t)},Lr=e=>e?K(e)?e.some(t=>t.length>1):e.length>1:!1;function zc(e){const t={};for(const E in e)E in _o||(t[E]=e[E]);if(e.css===!1)return t;const{name:n="v",type:s,duration:r,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:f=o,appearToClass:a=l,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:y=`${n}-leave-active`,leaveToClass:v=`${n}-leave-to`}=e,S=Qc(r),b=S&&S[0],B=S&&S[1],{onBeforeEnter:N,onEnter:j,onEnterCancelled:p,onLeave:g,onLeaveCancelled:P,onBeforeAppear:F=N,onAppear:$=j,onAppearCancelled:V=p}=t,R=(E,W,se)=>{at(E,W?a:l),at(E,W?f:o),se&&se()},_=(E,W)=>{E._isLeaving=!1,at(E,d),at(E,v),at(E,y),W&&W()},I=E=>(W,se)=>{const ae=E?$:j,U=()=>R(W,E,se);ct(ae,[W,U]),Ir(()=>{at(W,E?c:i),ze(W,E?a:l),Lr(ae)||Nr(W,s,b,U)})};return ce(t,{onBeforeEnter(E){ct(N,[E]),ze(E,i),ze(E,o)},onBeforeAppear(E){ct(F,[E]),ze(E,c),ze(E,f)},onEnter:I(!1),onAppear:I(!0),onLeave(E,W){E._isLeaving=!0;const se=()=>_(E,W);ze(E,d),ze(E,y),ta(),Ir(()=>{E._isLeaving&&(at(E,d),ze(E,v),Lr(g)||Nr(E,s,B,se))}),ct(g,[E,se])},onEnterCancelled(E){R(E,!1),ct(p,[E])},onAppearCancelled(E){R(E,!0),ct(V,[E])},onLeaveCancelled(E){_(E),ct(P,[E])}})}function Qc(e){if(e==null)return null;if(ne(e))return[os(e.enter),os(e.leave)];{const t=os(e);return[t,t]}}function os(e){return qo(e)}function ze(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[zt]||(e[zt]=new Set)).add(t)}function at(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[zt];n&&(n.delete(t),n.size||(e[zt]=void 0))}function Ir(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Zc=0;function Nr(e,t,n,s){const r=e._endId=++Zc,i=()=>{r===e._endId&&s()};if(n!=null)return setTimeout(i,n);const{type:o,timeout:l,propCount:c}=ea(e,t);if(!o)return s();const f=o+"end";let a=0;const d=()=>{e.removeEventListener(f,y),i()},y=v=>{v.target===e&&++a>=c&&d()};setTimeout(()=>{a(n[S]||"").split(", "),r=s(`${Je}Delay`),i=s(`${Je}Duration`),o=Fr(r,i),l=s(`${Ht}Delay`),c=s(`${Ht}Duration`),f=Fr(l,c);let a=null,d=0,y=0;t===Je?o>0&&(a=Je,d=o,y=i.length):t===Ht?f>0&&(a=Ht,d=f,y=c.length):(d=Math.max(o,f),a=d>0?o>f?Je:Ht:null,y=a?a===Je?i.length:c.length:0);const v=a===Je&&/\b(transform|all)(,|$)/.test(s(`${Je}Property`).toString());return{type:a,timeout:d,propCount:y,hasTransform:v}}function Fr(e,t){for(;e.lengthHr(n)+Hr(e[s])))}function Hr(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function ta(){return document.body.offsetHeight}function na(e,t,n){const s=e[zt];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const $r=Symbol("_vod"),sa=Symbol("_vsh"),ra=Symbol(""),ia=/(^|;)\s*display\s*:/;function oa(e,t,n){const s=e.style,r=re(n);let i=!1;if(n&&!r){if(t)if(re(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();n[l]==null&&xn(s,l,"")}else for(const o in t)n[o]==null&&xn(s,o,"");for(const o in n)o==="display"&&(i=!0),xn(s,o,n[o])}else if(r){if(t!==n){const o=s[ra];o&&(n+=";"+o),s.cssText=n,i=ia.test(n)}}else t&&e.removeAttribute("style");$r in e&&(e[$r]=i?s.display:"",e[sa]&&(s.display="none"))}const Dr=/\s*!important$/;function xn(e,t,n){if(K(n))n.forEach(s=>xn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=la(e,t);Dr.test(n)?e.setProperty(st(s),n.replace(Dr,""),"important"):e[s]=n}}const jr=["Webkit","Moz","ms"],ls={};function la(e,t){const n=ls[t];if(n)return n;let s=Le(t);if(s!=="filter"&&s in e)return ls[t]=s;s=Fn(s);for(let r=0;rcs||(ua.then(()=>cs=0),cs=Date.now());function ha(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;He(pa(s,n.value),t,5,[s])};return n.value=e,n.attached=da(),n}function pa(e,t){if(K(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const Kr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,ga=(e,t,n,s,r,i)=>{const o=r==="svg";t==="class"?na(e,s,o):t==="style"?oa(e,n,s):Zt(t)?Fs(t)||aa(e,t,n,s,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ma(e,t,s,o))?(kr(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Ur(e,t,s,o,i,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!re(s))?kr(e,Le(t),s,i,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Ur(e,t,s,o))};function ma(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&Kr(t)&&q(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Kr(t)&&re(n)?!1:t in e}const qr=e=>{const t=e.props["onUpdate:modelValue"]||!1;return K(t)?n=>bn(t,n):t};function ya(e){e.target.composing=!0}function Gr(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const as=Symbol("_assign"),If={created(e,{modifiers:{lazy:t,trim:n,number:s}},r){e[as]=qr(r);const i=s||r.props&&r.props.type==="number";St(e,t?"change":"input",o=>{if(o.target.composing)return;let l=e.value;n&&(l=l.trim()),i&&(l=vs(l)),e[as](l)}),n&&St(e,"change",()=>{e.value=e.value.trim()}),t||(St(e,"compositionstart",ya),St(e,"compositionend",Gr),St(e,"change",Gr))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:r,number:i}},o){if(e[as]=qr(o),e.composing)return;const l=(i||e.type==="number")&&!/^0\d/.test(e.value)?vs(e.value):e.value,c=t??"";l!==c&&(document.activeElement===e&&e.type!=="range"&&(s&&t===n||r&&e.value.trim()===c)||(e.value=c))}},va=["ctrl","shift","alt","meta"],ba={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>va.some(n=>e[`${n}Key`]&&!t.includes(n))},Nf=(e,t)=>{const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(r,...i)=>{for(let o=0;o{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=r=>{if(!("key"in r))return;const i=st(r.key);if(t.some(o=>o===i||_a[o]===i))return e(r)})},wo=ce({patchProp:ga},Yc);let Wt,Yr=!1;function wa(){return Wt||(Wt=wc(wo))}function Sa(){return Wt=Yr?Wt:Sc(wo),Yr=!0,Wt}const Hf=(...e)=>{const t=wa().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=xo(s);if(!r)return;const i=t._component;!q(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const o=n(r,!1,So(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),o},t},$f=(...e)=>{const t=Sa().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=xo(s);if(r)return n(r,!0,So(r))},t};function So(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function xo(e){return re(e)?document.querySelector(e):e}const Df=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},xa=window.__VP_SITE_DATA__;function tr(e){return ui()?(tl(e),!0):!1}function Be(e){return typeof e=="function"?e():Ai(e)}const Eo=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const jf=e=>e!=null,Ea=Object.prototype.toString,Ca=e=>Ea.call(e)==="[object Object]",Qt=()=>{},Xr=Ta();function Ta(){var e,t;return Eo&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||((t=window==null?void 0:window.navigator)==null?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function Aa(e,t){function n(...s){return new Promise((r,i)=>{Promise.resolve(e(()=>t.apply(this,s),{fn:t,thisArg:this,args:s})).then(r).catch(i)})}return n}const Co=e=>e();function Ra(e,t={}){let n,s,r=Qt;const i=l=>{clearTimeout(l),r(),r=Qt};return l=>{const c=Be(e),f=Be(t.maxWait);return n&&i(n),c<=0||f!==void 0&&f<=0?(s&&(i(s),s=null),Promise.resolve(l())):new Promise((a,d)=>{r=t.rejectOnCancel?d:a,f&&!s&&(s=setTimeout(()=>{n&&i(n),s=null,a(l())},f)),n=setTimeout(()=>{s&&i(s),s=null,a(l())},c)})}}function Oa(e=Co){const t=oe(!0);function n(){t.value=!1}function s(){t.value=!0}const r=(...i)=>{t.value&&e(...i)};return{isActive:Vn(t),pause:n,resume:s,eventFilter:r}}function Pa(e){return qn()}function To(...e){if(e.length!==1)return Rl(...e);const t=e[0];return typeof t=="function"?Vn(Cl(()=>({get:t,set:Qt}))):oe(t)}function Ao(e,t,n={}){const{eventFilter:s=Co,...r}=n;return Fe(e,Aa(s,t),r)}function Ma(e,t,n={}){const{eventFilter:s,...r}=n,{eventFilter:i,pause:o,resume:l,isActive:c}=Oa(s);return{stop:Ao(e,t,{...r,eventFilter:i}),pause:o,resume:l,isActive:c}}function nr(e,t=!0,n){Pa()?Lt(e,n):t?e():Un(e)}function Vf(e,t,n={}){const{debounce:s=0,maxWait:r=void 0,...i}=n;return Ao(e,t,{...i,eventFilter:Ra(s,{maxWait:r})})}function Uf(e,t,n){let s;fe(n)?s={evaluating:n}:s={};const{lazy:r=!1,evaluating:i=void 0,shallow:o=!0,onError:l=Qt}=s,c=oe(!r),f=o?qs(t):oe(t);let a=0;return Zs(async d=>{if(!c.value)return;a++;const y=a;let v=!1;i&&Promise.resolve().then(()=>{i.value=!0});try{const S=await e(b=>{d(()=>{i&&(i.value=!1),v||b()})});y===a&&(f.value=S)}catch(S){l(S)}finally{i&&y===a&&(i.value=!1),v=!0}}),r?ie(()=>(c.value=!0,f.value)):f}const $e=Eo?window:void 0;function Ro(e){var t;const n=Be(e);return(t=n==null?void 0:n.$el)!=null?t:n}function Mt(...e){let t,n,s,r;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,s,r]=e,t=$e):[t,n,s,r]=e,!t)return Qt;Array.isArray(n)||(n=[n]),Array.isArray(s)||(s=[s]);const i=[],o=()=>{i.forEach(a=>a()),i.length=0},l=(a,d,y,v)=>(a.addEventListener(d,y,v),()=>a.removeEventListener(d,y,v)),c=Fe(()=>[Ro(t),Be(r)],([a,d])=>{if(o(),!a)return;const y=Ca(d)?{...d}:d;i.push(...n.flatMap(v=>s.map(S=>l(a,v,S,y))))},{immediate:!0,flush:"post"}),f=()=>{c(),o()};return tr(f),f}function La(e){return typeof e=="function"?e:typeof e=="string"?t=>t.key===e:Array.isArray(e)?t=>e.includes(t.key):()=>!0}function kf(...e){let t,n,s={};e.length===3?(t=e[0],n=e[1],s=e[2]):e.length===2?typeof e[1]=="object"?(t=!0,n=e[0],s=e[1]):(t=e[0],n=e[1]):(t=!0,n=e[0]);const{target:r=$e,eventName:i="keydown",passive:o=!1,dedupe:l=!1}=s,c=La(t);return Mt(r,i,a=>{a.repeat&&Be(l)||c(a)&&n(a)},o)}function Ia(){const e=oe(!1),t=qn();return t&&Lt(()=>{e.value=!0},t),e}function Na(e){const t=Ia();return ie(()=>(t.value,!!e()))}function Oo(e,t={}){const{window:n=$e}=t,s=Na(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let r;const i=oe(!1),o=f=>{i.value=f.matches},l=()=>{r&&("removeEventListener"in r?r.removeEventListener("change",o):r.removeListener(o))},c=Zs(()=>{s.value&&(l(),r=n.matchMedia(Be(e)),"addEventListener"in r?r.addEventListener("change",o):r.addListener(o),i.value=r.matches)});return tr(()=>{c(),l(),r=void 0}),i}const pn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},gn="__vueuse_ssr_handlers__",Fa=Ha();function Ha(){return gn in pn||(pn[gn]=pn[gn]||{}),pn[gn]}function Po(e,t){return Fa[e]||t}function sr(e){return Oo("(prefers-color-scheme: dark)",e)}function $a(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Da={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Jr="vueuse-storage";function rr(e,t,n,s={}){var r;const{flush:i="pre",deep:o=!0,listenToStorageChanges:l=!0,writeDefaults:c=!0,mergeDefaults:f=!1,shallow:a,window:d=$e,eventFilter:y,onError:v=_=>{console.error(_)},initOnMounted:S}=s,b=(a?qs:oe)(typeof t=="function"?t():t);if(!n)try{n=Po("getDefaultStorage",()=>{var _;return(_=$e)==null?void 0:_.localStorage})()}catch(_){v(_)}if(!n)return b;const B=Be(t),N=$a(B),j=(r=s.serializer)!=null?r:Da[N],{pause:p,resume:g}=Ma(b,()=>F(b.value),{flush:i,deep:o,eventFilter:y});d&&l&&nr(()=>{n instanceof Storage?Mt(d,"storage",V):Mt(d,Jr,R),S&&V()}),S||V();function P(_,I){if(d){const E={key:e,oldValue:_,newValue:I,storageArea:n};d.dispatchEvent(n instanceof Storage?new StorageEvent("storage",E):new CustomEvent(Jr,{detail:E}))}}function F(_){try{const I=n.getItem(e);if(_==null)P(I,null),n.removeItem(e);else{const E=j.write(_);I!==E&&(n.setItem(e,E),P(I,E))}}catch(I){v(I)}}function $(_){const I=_?_.newValue:n.getItem(e);if(I==null)return c&&B!=null&&n.setItem(e,j.write(B)),B;if(!_&&f){const E=j.read(I);return typeof f=="function"?f(E,B):N==="object"&&!Array.isArray(E)?{...B,...E}:E}else return typeof I!="string"?I:j.read(I)}function V(_){if(!(_&&_.storageArea!==n)){if(_&&_.key==null){b.value=B;return}if(!(_&&_.key!==e)){p();try{(_==null?void 0:_.newValue)!==j.write(b.value)&&(b.value=$(_))}catch(I){v(I)}finally{_?Un(g):g()}}}}function R(_){V(_.detail)}return b}const ja="*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";function Va(e={}){const{selector:t="html",attribute:n="class",initialValue:s="auto",window:r=$e,storage:i,storageKey:o="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:c,emitAuto:f,disableTransition:a=!0}=e,d={auto:"",light:"light",dark:"dark",...e.modes||{}},y=sr({window:r}),v=ie(()=>y.value?"dark":"light"),S=c||(o==null?To(s):rr(o,s,i,{window:r,listenToStorageChanges:l})),b=ie(()=>S.value==="auto"?v.value:S.value),B=Po("updateHTMLAttrs",(g,P,F)=>{const $=typeof g=="string"?r==null?void 0:r.document.querySelector(g):Ro(g);if(!$)return;const V=new Set,R=new Set;let _=null;if(P==="class"){const E=F.split(/\s/g);Object.values(d).flatMap(W=>(W||"").split(/\s/g)).filter(Boolean).forEach(W=>{E.includes(W)?V.add(W):R.add(W)})}else _={key:P,value:F};if(V.size===0&&R.size===0&&_===null)return;let I;a&&(I=r.document.createElement("style"),I.appendChild(document.createTextNode(ja)),r.document.head.appendChild(I));for(const E of V)$.classList.add(E);for(const E of R)$.classList.remove(E);_&&$.setAttribute(_.key,_.value),a&&(r.getComputedStyle(I).opacity,document.head.removeChild(I))});function N(g){var P;B(t,n,(P=d[g])!=null?P:g)}function j(g){e.onChanged?e.onChanged(g,N):N(g)}Fe(b,j,{flush:"post",immediate:!0}),nr(()=>j(b.value));const p=ie({get(){return f?S.value:b.value},set(g){S.value=g}});try{return Object.assign(p,{store:S,system:v,state:b})}catch{return p}}function Ua(e={}){const{valueDark:t="dark",valueLight:n="",window:s=$e}=e,r=Va({...e,onChanged:(l,c)=>{var f;e.onChanged?(f=e.onChanged)==null||f.call(e,l==="dark",c,l):c(l)},modes:{dark:t,light:n}}),i=ie(()=>r.system?r.system.value:sr({window:s}).value?"dark":"light");return ie({get(){return r.value==="dark"},set(l){const c=l?"dark":"light";i.value===c?r.value="auto":r.value=c}})}function fs(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function Bf(e,t,n={}){const{window:s=$e}=n;return rr(e,t,s==null?void 0:s.localStorage,n)}function Mo(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const us=new WeakMap;function Wf(e,t=!1){const n=oe(t);let s=null,r="";Fe(To(e),l=>{const c=fs(Be(l));if(c){const f=c;if(us.get(f)||us.set(f,f.style.overflow),f.style.overflow!=="hidden"&&(r=f.style.overflow),f.style.overflow==="hidden")return n.value=!0;if(n.value)return f.style.overflow="hidden"}},{immediate:!0});const i=()=>{const l=fs(Be(e));!l||n.value||(Xr&&(s=Mt(l,"touchmove",c=>{ka(c)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},o=()=>{const l=fs(Be(e));!l||!n.value||(Xr&&(s==null||s()),l.style.overflow=r,us.delete(l),n.value=!1)};return tr(o),ie({get(){return n.value},set(l){l?i():o()}})}function Kf(e,t,n={}){const{window:s=$e}=n;return rr(e,t,s==null?void 0:s.sessionStorage,n)}function qf(e={}){const{window:t=$e,behavior:n="auto"}=e;if(!t)return{x:oe(0),y:oe(0)};const s=oe(t.scrollX),r=oe(t.scrollY),i=ie({get(){return s.value},set(l){scrollTo({left:l,behavior:n})}}),o=ie({get(){return r.value},set(l){scrollTo({top:l,behavior:n})}});return Mt(t,"scroll",()=>{s.value=t.scrollX,r.value=t.scrollY},{capture:!1,passive:!0}),{x:i,y:o}}function Gf(e={}){const{window:t=$e,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:s=Number.POSITIVE_INFINITY,listenOrientation:r=!0,includeScrollbar:i=!0,type:o="inner"}=e,l=oe(n),c=oe(s),f=()=>{t&&(o==="outer"?(l.value=t.outerWidth,c.value=t.outerHeight):i?(l.value=t.innerWidth,c.value=t.innerHeight):(l.value=t.document.documentElement.clientWidth,c.value=t.document.documentElement.clientHeight))};if(f(),nr(f),Mt("resize",f,{passive:!0}),r){const a=Oo("(orientation: portrait)");Fe(a,()=>f())}return{width:l,height:c}}const ds={BASE_URL:"/HPCC-Platform/",DEV:!1,MODE:"production",PROD:!0,SSR:!1};var hs={};const Lo=/^(?:[a-z]+:|\/\/)/i,Ba="vitepress-theme-appearance",Wa=/#.*$/,Ka=/[?#].*$/,qa=/(?:(^|\/)index)?\.(?:md|html)$/,ge=typeof document<"u",Io={relativePath:"404.md",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function Ga(e,t,n=!1){if(t===void 0)return!1;if(e=zr(`/${e}`),n)return new RegExp(t).test(e);if(zr(t)!==e)return!1;const s=t.match(Wa);return s?(ge?location.hash:"")===s[0]:!0}function zr(e){return decodeURI(e).replace(Ka,"").replace(qa,"$1")}function Ya(e){return Lo.test(e)}function Xa(e,t){return Object.keys((e==null?void 0:e.locales)||{}).find(n=>n!=="root"&&!Ya(n)&&Ga(t,`/${n}/`,!0))||"root"}function Ja(e,t){var s,r,i,o,l,c,f;const n=Xa(e,t);return Object.assign({},e,{localeIndex:n,lang:((s=e.locales[n])==null?void 0:s.lang)??e.lang,dir:((r=e.locales[n])==null?void 0:r.dir)??e.dir,title:((i=e.locales[n])==null?void 0:i.title)??e.title,titleTemplate:((o=e.locales[n])==null?void 0:o.titleTemplate)??e.titleTemplate,description:((l=e.locales[n])==null?void 0:l.description)??e.description,head:Fo(e.head,((c=e.locales[n])==null?void 0:c.head)??[]),themeConfig:{...e.themeConfig,...(f=e.locales[n])==null?void 0:f.themeConfig}})}function No(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const r=za(e.title,s);return n===r.slice(3)?n:`${n}${r}`}function za(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function Qa(e,t){const[n,s]=t;if(n!=="meta")return!1;const r=Object.entries(s)[0];return r==null?!1:e.some(([i,o])=>i===n&&o[r[0]]===r[1])}function Fo(e,t){return[...e.filter(n=>!Qa(t,n)),...t]}const Za=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,ef=/^[a-z]:/i;function Qr(e){const t=ef.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(Za,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const ps=new Set;function tf(e){if(ps.size===0){const n=typeof process=="object"&&(hs==null?void 0:hs.VITE_EXTRA_EXTENSIONS)||(ds==null?void 0:ds.VITE_EXTRA_EXTENSIONS)||"";("3g2,3gp,aac,ai,apng,au,avif,bin,bmp,cer,class,conf,crl,css,csv,dll,doc,eps,epub,exe,gif,gz,ics,ief,jar,jpe,jpeg,jpg,js,json,jsonld,m4a,man,mid,midi,mjs,mov,mp2,mp3,mp4,mpe,mpeg,mpg,mpp,oga,ogg,ogv,ogx,opus,otf,p10,p7c,p7m,p7s,pdf,png,ps,qt,roff,rtf,rtx,ser,svg,t,tif,tiff,tr,ts,tsv,ttf,txt,vtt,wav,weba,webm,webp,woff,woff2,xhtml,xml,yaml,yml,zip"+(n&&typeof n=="string"?","+n:"")).split(",").forEach(s=>ps.add(s))}const t=e.split(".").pop();return t==null||!ps.has(t.toLowerCase())}function Yf(e){return e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d")}const nf=Symbol(),mt=qs(xa);function Xf(e){const t=ie(()=>Ja(mt.value,e.data.relativePath)),n=t.value.appearance,s=n==="force-dark"?oe(!0):n==="force-auto"?sr():n?Ua({storageKey:Ba,initialValue:()=>n==="dark"?"dark":"auto",...typeof n=="object"?n:{}}):oe(!1),r=oe(ge?location.hash:"");return ge&&window.addEventListener("hashchange",()=>{r.value=location.hash}),Fe(()=>e.data,()=>{r.value=ge?location.hash:""}),{site:t,theme:ie(()=>t.value.themeConfig),page:ie(()=>e.data),frontmatter:ie(()=>e.data.frontmatter),params:ie(()=>e.data.params),lang:ie(()=>t.value.lang),dir:ie(()=>e.data.frontmatter.dir||t.value.dir),localeIndex:ie(()=>t.value.localeIndex||"root"),title:ie(()=>No(t.value,e.data)),description:ie(()=>e.data.description||t.value.description),isDark:s,hash:ie(()=>r.value)}}function sf(){const e=Ot(nf);if(!e)throw new Error("vitepress data not properly injected in app");return e}function rf(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Zr(e){return Lo.test(e)||!e.startsWith("/")?e:rf(mt.value.base,e)}function of(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),ge){const n="/HPCC-Platform/";t=Qr(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let s=__VP_HASH_MAP__[t.toLowerCase()];if(s||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",s=__VP_HASH_MAP__[t.toLowerCase()]),!s)return null;t=`${n}assets/${t}.${s}.js`}else t=`./${Qr(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let En=[];function Jf(e){En.push(e),Bn(()=>{En=En.filter(t=>t!==e)})}function lf(){let e=mt.value.scrollOffset,t=0,n=24;if(typeof e=="object"&&"padding"in e&&(n=e.padding,e=e.selector),typeof e=="number")t=e;else if(typeof e=="string")t=ei(e,n);else if(Array.isArray(e))for(const s of e){const r=ei(s,n);if(r){t=r;break}}return t}function ei(e,t){const n=document.querySelector(e);if(!n)return 0;const s=n.getBoundingClientRect().bottom;return s<0?0:s+t}const cf=Symbol(),Ho="http://a.com",af=()=>({path:"/",component:null,data:Io});function zf(e,t){const n=jn(af()),s={route:n,go:r};async function r(l=ge?location.href:"/"){var c,f;l=gs(l),await((c=s.onBeforeRouteChange)==null?void 0:c.call(s,l))!==!1&&(ge&&l!==gs(location.href)&&(history.replaceState({scrollPosition:window.scrollY},""),history.pushState({},"",l)),await o(l),await((f=s.onAfterRouteChanged)==null?void 0:f.call(s,l)))}let i=null;async function o(l,c=0,f=!1){var y,v;if(await((y=s.onBeforePageLoad)==null?void 0:y.call(s,l))===!1)return;const a=new URL(l,Ho),d=i=a.pathname;try{let S=await e(d);if(!S)throw new Error(`Page not found: ${d}`);if(i===d){i=null;const{default:b,__pageData:B}=S;if(!b)throw new Error(`Invalid route component: ${b}`);await((v=s.onAfterPageLoad)==null?void 0:v.call(s,l)),n.path=ge?d:Zr(d),n.component=_n(b),n.data=_n(B),ge&&Un(()=>{let N=mt.value.base+B.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!mt.value.cleanUrls&&!N.endsWith("/")&&(N+=".html"),N!==a.pathname&&(a.pathname=N,l=N+a.search+a.hash,history.replaceState({},"",l)),a.hash&&!c){let j=null;try{j=document.getElementById(decodeURIComponent(a.hash).slice(1))}catch(p){console.warn(p)}if(j){ti(j,a.hash);return}}window.scrollTo(0,c)})}}catch(S){if(!/fetch|Page not found/.test(S.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(S),!f)try{const b=await fetch(mt.value.base+"hashmap.json");window.__VP_HASH_MAP__=await b.json(),await o(l,c,!0);return}catch{}if(i===d){i=null,n.path=ge?d:Zr(d),n.component=t?_n(t):null;const b=ge?d.replace(/(^|\/)$/,"$1index").replace(/(\.html)?$/,".md").replace(/^\//,""):"404.md";n.data={...Io,relativePath:b}}}}return ge&&(history.state===null&&history.replaceState({},""),window.addEventListener("click",l=>{if(l.defaultPrevented||!(l.target instanceof Element)||l.target.closest("button")||l.button!==0||l.ctrlKey||l.shiftKey||l.altKey||l.metaKey)return;const c=l.target.closest("a");if(!c||c.closest(".vp-raw")||c.hasAttribute("download")||c.hasAttribute("target"))return;const f=c.getAttribute("href")??(c instanceof SVGAElement?c.getAttribute("xlink:href"):null);if(f==null)return;const{href:a,origin:d,pathname:y,hash:v,search:S}=new URL(f,c.baseURI),b=new URL(location.href);d===b.origin&&tf(y)&&(l.preventDefault(),y===b.pathname&&S===b.search?(v!==b.hash&&(history.pushState({},"",a),window.dispatchEvent(new HashChangeEvent("hashchange",{oldURL:b.href,newURL:a}))),v?ti(c,v,c.classList.contains("header-anchor")):window.scrollTo(0,0)):r(a))},{capture:!0}),window.addEventListener("popstate",async l=>{var c;l.state!==null&&(await o(gs(location.href),l.state&&l.state.scrollPosition||0),(c=s.onAfterRouteChanged)==null||c.call(s,location.href))}),window.addEventListener("hashchange",l=>{l.preventDefault()})),s}function ff(){const e=Ot(cf);if(!e)throw new Error("useRouter() is called without provider.");return e}function $o(){return ff().route}function ti(e,t,n=!1){let s=null;try{s=e.classList.contains("header-anchor")?e:document.getElementById(decodeURIComponent(t).slice(1))}catch(r){console.warn(r)}if(s){let r=function(){!n||Math.abs(o-window.scrollY)>window.innerHeight?window.scrollTo(0,o):window.scrollTo({left:0,top:o,behavior:"smooth"})};const i=parseInt(window.getComputedStyle(s).paddingTop,10),o=window.scrollY+s.getBoundingClientRect().top-lf()+i;requestAnimationFrame(r)}}function gs(e){const t=new URL(e,Ho);return t.pathname=t.pathname.replace(/(^|\/)index(\.html)?$/,"$1"),mt.value.cleanUrls?t.pathname=t.pathname.replace(/\.html$/,""):!t.pathname.endsWith("/")&&!t.pathname.endsWith(".html")&&(t.pathname+=".html"),t.pathname+t.search+t.hash}const mn=()=>En.forEach(e=>e()),Qf=Ys({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=$o(),{frontmatter:n,site:s}=sf();return Fe(n,mn,{deep:!0,flush:"post"}),()=>Ls(e.as,s.value.contentProps??{style:{position:"relative"}},[t.component?Ls(t.component,{onVnodeMounted:mn,onVnodeUpdated:mn,onVnodeUnmounted:mn}):"404 Page Not Found"])}}),uf="modulepreload",df=function(e){return"/HPCC-Platform/"+e},ni={},Zf=function(t,n,s){let r=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),l=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));r=Promise.allSettled(n.map(c=>{if(c=df(c),c in ni)return;ni[c]=!0;const f=c.endsWith(".css"),a=f?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${a}`))return;const d=document.createElement("link");if(d.rel=f?"stylesheet":uf,f||(d.as="script"),d.crossOrigin="",d.href=c,l&&d.setAttribute("nonce",l),document.head.appendChild(d),f)return new Promise((y,v)=>{d.addEventListener("load",y),d.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${c}`)))})}))}function i(o){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=o,window.dispatchEvent(l),!l.defaultPrevented)throw o}return r.then(o=>{for(const l of o||[])l.status==="rejected"&&i(l.reason);return t().catch(i)})},eu=Ys({setup(e,{slots:t}){const n=oe(!1);return Lt(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function tu(){ge&&window.addEventListener("click",e=>{var n;const t=e.target;if(t.matches(".vp-code-group input")){const s=(n=t.parentElement)==null?void 0:n.parentElement;if(!s)return;const r=Array.from(s.querySelectorAll("input")).indexOf(t);if(r<0)return;const i=s.querySelector(".blocks");if(!i)return;const o=Array.from(i.children).find(f=>f.classList.contains("active"));if(!o)return;const l=i.children[r];if(!l||o===l)return;o.classList.remove("active"),l.classList.add("active");const c=s==null?void 0:s.querySelector(`label[for="${t.id}"]`);c==null||c.scrollIntoView({block:"nearest"})}})}function nu(){if(ge){const e=new WeakMap;window.addEventListener("click",t=>{var s;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const r=n.parentElement,i=(s=n.nextElementSibling)==null?void 0:s.nextElementSibling;if(!r||!i)return;const o=/language-(shellscript|shell|bash|sh|zsh)/.test(r.className),l=[".vp-copy-ignore",".diff.remove"],c=i.cloneNode(!0);c.querySelectorAll(l.join(",")).forEach(a=>a.remove());let f=c.textContent||"";o&&(f=f.replace(/^ *(\$|>) /gm,"").trim()),hf(f).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const a=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,a)})}})}}async function hf(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),r=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),r&&(s.removeAllRanges(),s.addRange(r)),n&&n.focus()}}function su(e,t){let n=!0,s=[];const r=i=>{if(n){n=!1,i.forEach(l=>{const c=ms(l);for(const f of document.head.children)if(f.isEqualNode(c)){s.push(f);return}});return}const o=i.map(ms);s.forEach((l,c)=>{const f=o.findIndex(a=>a==null?void 0:a.isEqualNode(l??null));f!==-1?delete o[f]:(l==null||l.remove(),delete s[c])}),o.forEach(l=>l&&document.head.appendChild(l)),s=[...s,...o].filter(Boolean)};Zs(()=>{const i=e.data,o=t.value,l=i&&i.description,c=i&&i.frontmatter.head||[],f=No(o,i);f!==document.title&&(document.title=f);const a=l||o.description;let d=document.querySelector("meta[name=description]");d?d.getAttribute("content")!==a&&d.setAttribute("content",a):ms(["meta",{name:"description",content:a}]),r(Fo(o.head,gf(c)))})}function ms([e,t,n]){const s=document.createElement(e);for(const r in t)s.setAttribute(r,t[r]);return n&&(s.innerHTML=n),e==="script"&&t.async==null&&(s.async=!1),s}function pf(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function gf(e){return e.filter(t=>!pf(t))}const ys=new Set,Do=()=>document.createElement("link"),mf=e=>{const t=Do();t.rel="prefetch",t.href=e,document.head.appendChild(t)},yf=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let yn;const vf=ge&&(yn=Do())&&yn.relList&&yn.relList.supports&&yn.relList.supports("prefetch")?mf:yf;function ru(){if(!ge||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(o=>{if(o.isIntersecting){const l=o.target;n.unobserve(l);const{pathname:c}=l;if(!ys.has(c)){ys.add(c);const f=of(c);f&&vf(f)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{hostname:o,pathname:l}=new URL(i.href instanceof SVGAnimatedString?i.href.animVal:i.href,i.baseURI),c=l.match(/\.\w+$/);c&&c[0]!==".html"||i.target!=="_blank"&&o===location.hostname&&(l!==location.pathname?n.observe(i):ys.add(l))})})};Lt(s);const r=$o();Fe(()=>r.path,s),Bn(()=>{n&&n.disconnect()})}export{ki as $,lf as A,Sf as B,Ef as C,qs as D,Jf as E,Se as F,le as G,xf as H,Lo as I,$o as J,Hc as K,Ot as L,Gf as M,Ds as N,kf as O,Un as P,qf as Q,ge as R,Vn as S,Lf as T,wf as U,Zf as V,Wf as W,pc as X,Ff as Y,Tf as Z,Df as _,go as a,Nf as a0,Af as a1,Ls as a2,Pf as a3,su as a4,cf as a5,Xf as a6,nf as a7,Qf as a8,eu as a9,mt as aa,$f as ab,zf as ac,of as ad,ru as ae,nu as af,tu as ag,Be as ah,Ro as ai,jf as aj,tr as ak,Uf as al,Kf as am,Bf as an,Vf as ao,ff as ap,Mt as aq,bf as ar,If as as,fe as at,_f as au,_n as av,Hf as aw,Yf as ax,Ps as b,Of as c,Ys as d,Mf as e,tf as f,Zr as g,ie as h,Ya as i,po as j,Ai as k,Ga as l,Oo as m,js as n,Os as o,oe as p,Fe as q,Cf as r,Zs as s,Zo as t,sf as u,Lt as v,$l as w,Bn as x,Rf as y,ec as z}; diff --git a/assets/chunks/theme.B_cCIxVx.js b/assets/chunks/theme.B_cCIxVx.js new file mode 100644 index 00000000000..373b2ca3693 --- /dev/null +++ b/assets/chunks/theme.B_cCIxVx.js @@ -0,0 +1,54 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/chunks/VPLocalSearchBox.CrIhCP-G.js","assets/chunks/framework.DkhCEVKm.js"])))=>i.map(i=>d[i]); +var Dr=Object.defineProperty;var $r=(e,t,n)=>t in e?Dr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var $e=(e,t,n)=>$r(e,typeof t!="symbol"?t+"":t,n);import{d as S,o as d,c as g,r as v,n as H,a as Ve,t as q,b as F,w as _,e as k,T as Vn,_ as L,u as Br,i as Vr,f as Or,g as On,h as B,j as y,k as f,l as Oe,m as yn,p as U,q as _e,s as qt,v as Me,x as Mn,y as Rn,z as Mr,A as Rr,B as Re,F as z,C as J,D as Qs,E as Ht,G as T,H as he,I as Js,J as zt,K as Te,L as Gt,M as jr,N as Zs,O as _n,P as Ur,Q as Xs,R as Wt,S as qr,U as Hr,V as at,W as ei,X as ti,Y as zr,Z as Gr,$ as Wr,a0 as Kr,a1 as Yr,a2 as Qr}from"./framework.DkhCEVKm.js";const Jr=S({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(e){return(t,n)=>(d(),g("span",{class:H(["VPBadge",t.type])},[v(t.$slots,"default",{},()=>[Ve(q(t.text),1)])],2))}}),Zr={key:0,class:"VPBackdrop"},Xr=S({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(e){return(t,n)=>(d(),F(Vn,{name:"fade"},{default:_(()=>[t.show?(d(),g("div",Zr)):k("",!0)]),_:1}))}}),ea=L(Xr,[["__scopeId","data-v-54a304ca"]]),V=Br;function ta(e,t){let n,s=!1;return()=>{n&&clearTimeout(n),s?n=setTimeout(e,t):(e(),(s=!0)&&setTimeout(()=>s=!1,t))}}function wn(e){return/^\//.test(e)?e:`/${e}`}function jn(e){const{pathname:t,search:n,hash:s,protocol:i}=new URL(e,"http://a.com");if(Vr(e)||e.startsWith("#")||!i.startsWith("http")||!Or(t))return e;const{site:r}=V(),a=t.endsWith("/")||t.endsWith(".html")?e:e.replace(/(?:(^\.+)\/)?.*$/,`$1${t.replace(/(\.md)?$/,r.value.cleanUrls?"":".html")}${n}${s}`);return On(a)}function ht({correspondingLink:e=!1}={}){const{site:t,localeIndex:n,page:s,theme:i,hash:r}=V(),a=B(()=>{var u,c;return{label:(u=t.value.locales[n.value])==null?void 0:u.label,link:((c=t.value.locales[n.value])==null?void 0:c.link)||(n.value==="root"?"/":`/${n.value}/`)}});return{localeLinks:B(()=>Object.entries(t.value.locales).flatMap(([u,c])=>a.value.label===c.label?[]:{text:c.label,link:na(c.link||(u==="root"?"/":`/${u}/`),i.value.i18nRouting!==!1&&e,s.value.relativePath.slice(a.value.link.length-1),!t.value.cleanUrls)+r.value})),currentLang:a}}function na(e,t,n,s){return t?e.replace(/\/$/,"")+wn(n.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,s?".html":"")):e}const sa={class:"NotFound"},ia={class:"code"},ra={class:"title"},aa={class:"quote"},oa={class:"action"},ua=["href","aria-label"],la=S({__name:"NotFound",setup(e){const{theme:t}=V(),{currentLang:n}=ht();return(s,i)=>{var r,a,o,u,c;return d(),g("div",sa,[y("p",ia,q(((r=f(t).notFound)==null?void 0:r.code)??"404"),1),y("h1",ra,q(((a=f(t).notFound)==null?void 0:a.title)??"PAGE NOT FOUND"),1),i[0]||(i[0]=y("div",{class:"divider"},null,-1)),y("blockquote",aa,q(((o=f(t).notFound)==null?void 0:o.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),y("div",oa,[y("a",{class:"link",href:f(On)(f(n).link),"aria-label":((u=f(t).notFound)==null?void 0:u.linkLabel)??"go to home"},q(((c=f(t).notFound)==null?void 0:c.linkText)??"Take me home"),9,ua)])])}}}),ca=L(la,[["__scopeId","data-v-6ff51ddd"]]);function ni(e,t){if(Array.isArray(e))return St(e);if(e==null)return[];t=wn(t);const n=Object.keys(e).sort((i,r)=>r.split("/").length-i.split("/").length).find(i=>t.startsWith(wn(i))),s=n?e[n]:[];return Array.isArray(s)?St(s):St(s.items,s.base)}function ha(e){const t=[];let n=0;for(const s in e){const i=e[s];if(i.items){n=t.push(i);continue}t[n]||t.push({items:[]}),t[n].items.push(i)}return t}function pa(e){const t=[];function n(s){for(const i of s)i.text&&i.link&&t.push({text:i.text,link:i.link,docFooterText:i.docFooterText}),i.items&&n(i.items)}return n(e),t}function xn(e,t){return Array.isArray(t)?t.some(n=>xn(e,n)):Oe(e,t.link)?!0:t.items?xn(e,t.items):!1}function St(e,t){return[...e].map(n=>{const s={...n},i=s.base||t;return i&&s.link&&(s.link=i+s.link),s.items&&(s.items=St(s.items,i)),s})}function Ae(){const{frontmatter:e,page:t,theme:n}=V(),s=yn("(min-width: 960px)"),i=U(!1),r=B(()=>{const N=n.value.sidebar,I=t.value.relativePath;return N?ni(N,I):[]}),a=U(r.value);_e(r,(N,I)=>{JSON.stringify(N)!==JSON.stringify(I)&&(a.value=r.value)});const o=B(()=>e.value.sidebar!==!1&&a.value.length>0&&e.value.layout!=="home"),u=B(()=>c?e.value.aside==null?n.value.aside==="left":e.value.aside==="left":!1),c=B(()=>e.value.layout==="home"?!1:e.value.aside!=null?!!e.value.aside:n.value.aside!==!1),h=B(()=>o.value&&s.value),p=B(()=>o.value?ha(a.value):[]);function m(){i.value=!0}function b(){i.value=!1}function w(){i.value?b():m()}return{isOpen:i,sidebar:a,sidebarGroups:p,hasSidebar:o,hasAside:c,leftAside:u,isSidebarEnabled:h,open:m,close:b,toggle:w}}function da(e,t){let n;qt(()=>{n=e.value?document.activeElement:void 0}),Me(()=>{window.addEventListener("keyup",s)}),Mn(()=>{window.removeEventListener("keyup",s)});function s(i){i.key==="Escape"&&e.value&&(t(),n==null||n.focus())}}function fa(e){const{page:t,hash:n}=V(),s=U(!1),i=B(()=>e.value.collapsed!=null),r=B(()=>!!e.value.link),a=U(!1),o=()=>{a.value=Oe(t.value.relativePath,e.value.link)};_e([t,e,n],o),Me(o);const u=B(()=>a.value?!0:e.value.items?xn(t.value.relativePath,e.value.items):!1),c=B(()=>!!(e.value.items&&e.value.items.length));qt(()=>{s.value=!!(i.value&&e.value.collapsed)}),Rn(()=>{(a.value||u.value)&&(s.value=!1)});function h(){i.value&&(s.value=!s.value)}return{collapsed:s,collapsible:i,isLink:r,isActiveLink:a,hasActiveLink:u,hasChildren:c,toggle:h}}function ma(){const{hasSidebar:e}=Ae(),t=yn("(min-width: 960px)"),n=yn("(min-width: 1280px)");return{isAsideEnabled:B(()=>!n.value&&!t.value?!1:e.value?n.value:t.value)}}const En=[];function si(e){return typeof e.outline=="object"&&!Array.isArray(e.outline)&&e.outline.label||e.outlineTitle||"On this page"}function Un(e){const t=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(n=>n.id&&n.hasChildNodes()).map(n=>{const s=Number(n.tagName[1]);return{element:n,title:va(n),link:"#"+n.id,level:s}});return ga(t,e)}function va(e){let t="";for(const n of e.childNodes)if(n.nodeType===1){if(n.classList.contains("VPBadge")||n.classList.contains("header-anchor")||n.classList.contains("ignore-header"))continue;t+=n.textContent}else n.nodeType===3&&(t+=n.textContent);return t.trim()}function ga(e,t){if(t===!1)return[];const n=(typeof t=="object"&&!Array.isArray(t)?t.level:t)||2,[s,i]=typeof n=="number"?[n,n]:n==="deep"?[2,6]:n;return _a(e,s,i)}function ba(e,t){const{isAsideEnabled:n}=ma(),s=ta(r,100);let i=null;Me(()=>{requestAnimationFrame(r),window.addEventListener("scroll",s)}),Mr(()=>{a(location.hash)}),Mn(()=>{window.removeEventListener("scroll",s)});function r(){if(!n.value)return;const o=window.scrollY,u=window.innerHeight,c=document.body.offsetHeight,h=Math.abs(o+u-c)<1,p=En.map(({element:b,link:w})=>({link:w,top:ya(b)})).filter(({top:b})=>!Number.isNaN(b)).sort((b,w)=>b.top-w.top);if(!p.length){a(null);return}if(o<1){a(null);return}if(h){a(p[p.length-1].link);return}let m=null;for(const{link:b,top:w}of p){if(w>o+Rr()+4)break;m=b}a(m)}function a(o){i&&i.classList.remove("active"),o==null?i=null:i=e.value.querySelector(`a[href="${decodeURIComponent(o)}"]`);const u=i;u?(u.classList.add("active"),t.value.style.top=u.offsetTop+39+"px",t.value.style.opacity="1"):(t.value.style.top="33px",t.value.style.opacity="0")}}function ya(e){let t=0;for(;e!==document.body;){if(e===null)return NaN;t+=e.offsetTop,e=e.offsetParent}return t}function _a(e,t,n){En.length=0;const s=[],i=[];return e.forEach(r=>{const a={...r,children:[]};let o=i[i.length-1];for(;o&&o.level>=a.level;)i.pop(),o=i[i.length-1];if(a.element.classList.contains("ignore-header")||o&&"shouldIgnore"in o){i.push({level:a.level,shouldIgnore:!0});return}a.level>n||a.level{const i=Re("VPDocOutlineItem",!0);return d(),g("ul",{class:H(["VPDocOutlineItem",n.root?"root":"nested"])},[(d(!0),g(z,null,J(n.headers,({children:r,link:a,title:o})=>(d(),g("li",null,[y("a",{class:"outline-link",href:a,onClick:t,title:o},q(o),9,wa),r!=null&&r.length?(d(),F(i,{key:0,headers:r},null,8,["headers"])):k("",!0)]))),256))],2)}}}),ii=L(xa,[["__scopeId","data-v-53c99d69"]]),Ea={class:"content"},Aa={"aria-level":"2",class:"outline-title",id:"doc-outline-aria-label",role:"heading"},ka=S({__name:"VPDocAsideOutline",setup(e){const{frontmatter:t,theme:n}=V(),s=Qs([]);Ht(()=>{s.value=Un(t.value.outline??n.value.outline)});const i=U(),r=U();return ba(i,r),(a,o)=>(d(),g("nav",{"aria-labelledby":"doc-outline-aria-label",class:H(["VPDocAsideOutline",{"has-outline":s.value.length>0}]),ref_key:"container",ref:i},[y("div",Ea,[y("div",{class:"outline-marker",ref_key:"marker",ref:r},null,512),y("div",Aa,q(f(si)(f(n))),1),T(ii,{headers:s.value,root:!0},null,8,["headers"])])],2))}}),Ca=L(ka,[["__scopeId","data-v-f610f197"]]),Sa={class:"VPDocAsideCarbonAds"},Pa=S({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(e){const t=()=>null;return(n,s)=>(d(),g("div",Sa,[T(f(t),{"carbon-ads":n.carbonAds},null,8,["carbon-ads"])]))}}),Ia={class:"VPDocAside"},Ta=S({__name:"VPDocAside",setup(e){const{theme:t}=V();return(n,s)=>(d(),g("div",Ia,[v(n.$slots,"aside-top",{},void 0,!0),v(n.$slots,"aside-outline-before",{},void 0,!0),T(Ca),v(n.$slots,"aside-outline-after",{},void 0,!0),s[0]||(s[0]=y("div",{class:"spacer"},null,-1)),v(n.$slots,"aside-ads-before",{},void 0,!0),f(t).carbonAds?(d(),F(Pa,{key:0,"carbon-ads":f(t).carbonAds},null,8,["carbon-ads"])):k("",!0),v(n.$slots,"aside-ads-after",{},void 0,!0),v(n.$slots,"aside-bottom",{},void 0,!0)]))}}),Na=L(Ta,[["__scopeId","data-v-cb998dce"]]);function Fa(){const{theme:e,page:t}=V();return B(()=>{const{text:n="Edit this page",pattern:s=""}=e.value.editLink||{};let i;return typeof s=="function"?i=s(t.value):i=s.replace(/:path/g,t.value.filePath),{url:i,text:n}})}function La(){const{page:e,theme:t,frontmatter:n}=V();return B(()=>{var c,h,p,m,b,w,N,I;const s=ni(t.value.sidebar,e.value.relativePath),i=pa(s),r=Da(i,x=>x.link.replace(/[?#].*$/,"")),a=r.findIndex(x=>Oe(e.value.relativePath,x.link)),o=((c=t.value.docFooter)==null?void 0:c.prev)===!1&&!n.value.prev||n.value.prev===!1,u=((h=t.value.docFooter)==null?void 0:h.next)===!1&&!n.value.next||n.value.next===!1;return{prev:o?void 0:{text:(typeof n.value.prev=="string"?n.value.prev:typeof n.value.prev=="object"?n.value.prev.text:void 0)??((p=r[a-1])==null?void 0:p.docFooterText)??((m=r[a-1])==null?void 0:m.text),link:(typeof n.value.prev=="object"?n.value.prev.link:void 0)??((b=r[a-1])==null?void 0:b.link)},next:u?void 0:{text:(typeof n.value.next=="string"?n.value.next:typeof n.value.next=="object"?n.value.next.text:void 0)??((w=r[a+1])==null?void 0:w.docFooterText)??((N=r[a+1])==null?void 0:N.text),link:(typeof n.value.next=="object"?n.value.next.link:void 0)??((I=r[a+1])==null?void 0:I.link)}}})}function Da(e,t){const n=new Set;return e.filter(s=>{const i=t(s);return n.has(i)?!1:n.add(i)})}const pe=S({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(e){const t=e,n=B(()=>t.tag??(t.href?"a":"span")),s=B(()=>t.href&&Js.test(t.href)||t.target==="_blank");return(i,r)=>(d(),F(he(n.value),{class:H(["VPLink",{link:i.href,"vp-external-link-icon":s.value,"no-icon":i.noIcon}]),href:i.href?f(jn)(i.href):void 0,target:i.target??(s.value?"_blank":void 0),rel:i.rel??(s.value?"noreferrer":void 0)},{default:_(()=>[v(i.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),$a={class:"VPLastUpdated"},Ba=["datetime"],Va=S({__name:"VPDocFooterLastUpdated",setup(e){const{theme:t,page:n,lang:s}=V(),i=B(()=>new Date(n.value.lastUpdated)),r=B(()=>i.value.toISOString()),a=U("");return Me(()=>{qt(()=>{var o,u,c;a.value=new Intl.DateTimeFormat((u=(o=t.value.lastUpdated)==null?void 0:o.formatOptions)!=null&&u.forceLocale?s.value:void 0,((c=t.value.lastUpdated)==null?void 0:c.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(i.value)})}),(o,u)=>{var c;return d(),g("p",$a,[Ve(q(((c=f(t).lastUpdated)==null?void 0:c.text)||f(t).lastUpdatedText||"Last updated")+": ",1),y("time",{datetime:r.value},q(a.value),9,Ba)])}}}),Oa=L(Va,[["__scopeId","data-v-1bb0c8a8"]]),Ma={key:0,class:"VPDocFooter"},Ra={key:0,class:"edit-info"},ja={key:0,class:"edit-link"},Ua={key:1,class:"last-updated"},qa={key:1,class:"prev-next","aria-labelledby":"doc-footer-aria-label"},Ha={class:"pager"},za=["innerHTML"],Ga=["innerHTML"],Wa={class:"pager"},Ka=["innerHTML"],Ya=["innerHTML"],Qa=S({__name:"VPDocFooter",setup(e){const{theme:t,page:n,frontmatter:s}=V(),i=Fa(),r=La(),a=B(()=>t.value.editLink&&s.value.editLink!==!1),o=B(()=>n.value.lastUpdated),u=B(()=>a.value||o.value||r.value.prev||r.value.next);return(c,h)=>{var p,m,b,w;return u.value?(d(),g("footer",Ma,[v(c.$slots,"doc-footer-before",{},void 0,!0),a.value||o.value?(d(),g("div",Ra,[a.value?(d(),g("div",ja,[T(pe,{class:"edit-link-button",href:f(i).url,"no-icon":!0},{default:_(()=>[h[0]||(h[0]=y("span",{class:"vpi-square-pen edit-link-icon"},null,-1)),Ve(" "+q(f(i).text),1)]),_:1},8,["href"])])):k("",!0),o.value?(d(),g("div",Ua,[T(Oa)])):k("",!0)])):k("",!0),(p=f(r).prev)!=null&&p.link||(m=f(r).next)!=null&&m.link?(d(),g("nav",qa,[h[1]||(h[1]=y("span",{class:"visually-hidden",id:"doc-footer-aria-label"},"Pager",-1)),y("div",Ha,[(b=f(r).prev)!=null&&b.link?(d(),F(pe,{key:0,class:"pager-link prev",href:f(r).prev.link},{default:_(()=>{var N;return[y("span",{class:"desc",innerHTML:((N=f(t).docFooter)==null?void 0:N.prev)||"Previous page"},null,8,za),y("span",{class:"title",innerHTML:f(r).prev.text},null,8,Ga)]}),_:1},8,["href"])):k("",!0)]),y("div",Wa,[(w=f(r).next)!=null&&w.link?(d(),F(pe,{key:0,class:"pager-link next",href:f(r).next.link},{default:_(()=>{var N;return[y("span",{class:"desc",innerHTML:((N=f(t).docFooter)==null?void 0:N.next)||"Next page"},null,8,Ka),y("span",{class:"title",innerHTML:f(r).next.text},null,8,Ya)]}),_:1},8,["href"])):k("",!0)])])):k("",!0)])):k("",!0)}}}),Ja=L(Qa,[["__scopeId","data-v-1bcd8184"]]),Za={class:"container"},Xa={class:"aside-container"},eo={class:"aside-content"},to={class:"content"},no={class:"content-container"},so={class:"main"},io=S({__name:"VPDoc",setup(e){const{theme:t}=V(),n=zt(),{hasSidebar:s,hasAside:i,leftAside:r}=Ae(),a=B(()=>n.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(o,u)=>{const c=Re("Content");return d(),g("div",{class:H(["VPDoc",{"has-sidebar":f(s),"has-aside":f(i)}])},[v(o.$slots,"doc-top",{},void 0,!0),y("div",Za,[f(i)?(d(),g("div",{key:0,class:H(["aside",{"left-aside":f(r)}])},[u[0]||(u[0]=y("div",{class:"aside-curtain"},null,-1)),y("div",Xa,[y("div",eo,[T(Na,null,{"aside-top":_(()=>[v(o.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":_(()=>[v(o.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":_(()=>[v(o.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":_(()=>[v(o.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":_(()=>[v(o.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":_(()=>[v(o.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):k("",!0),y("div",to,[y("div",no,[v(o.$slots,"doc-before",{},void 0,!0),y("main",so,[T(c,{class:H(["vp-doc",[a.value,f(t).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),T(Ja,null,{"doc-footer-before":_(()=>[v(o.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),v(o.$slots,"doc-after",{},void 0,!0)])])]),v(o.$slots,"doc-bottom",{},void 0,!0)],2)}}}),ro=L(io,[["__scopeId","data-v-e6f2a212"]]),ao=S({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{},target:{},rel:{}},setup(e){const t=e,n=B(()=>t.href&&Js.test(t.href)),s=B(()=>t.tag||(t.href?"a":"button"));return(i,r)=>(d(),F(he(s.value),{class:H(["VPButton",[i.size,i.theme]]),href:i.href?f(jn)(i.href):void 0,target:t.target??(n.value?"_blank":void 0),rel:t.rel??(n.value?"noreferrer":void 0)},{default:_(()=>[Ve(q(i.text),1)]),_:1},8,["class","href","target","rel"]))}}),oo=L(ao,[["__scopeId","data-v-93dc4167"]]),uo=["src","alt"],lo=S({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(e){return(t,n)=>{const s=Re("VPImage",!0);return t.image?(d(),g(z,{key:0},[typeof t.image=="string"||"src"in t.image?(d(),g("img",Te({key:0,class:"VPImage"},typeof t.image=="string"?t.$attrs:{...t.image,...t.$attrs},{src:f(On)(typeof t.image=="string"?t.image:t.image.src),alt:t.alt??(typeof t.image=="string"?"":t.image.alt||"")}),null,16,uo)):(d(),g(z,{key:1},[T(s,Te({class:"dark",image:t.image.dark,alt:t.image.alt},t.$attrs),null,16,["image","alt"]),T(s,Te({class:"light",image:t.image.light,alt:t.image.alt},t.$attrs),null,16,["image","alt"])],64))],64)):k("",!0)}}}),Nt=L(lo,[["__scopeId","data-v-ab19afbb"]]),co={class:"container"},ho={class:"main"},po={key:0,class:"name"},fo=["innerHTML"],mo=["innerHTML"],vo=["innerHTML"],go={key:0,class:"actions"},bo={key:0,class:"image"},yo={class:"image-container"},_o=S({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(e){const t=Gt("hero-image-slot-exists");return(n,s)=>(d(),g("div",{class:H(["VPHero",{"has-image":n.image||f(t)}])},[y("div",co,[y("div",ho,[v(n.$slots,"home-hero-info-before",{},void 0,!0),v(n.$slots,"home-hero-info",{},()=>[n.name?(d(),g("h1",po,[y("span",{innerHTML:n.name,class:"clip"},null,8,fo)])):k("",!0),n.text?(d(),g("p",{key:1,innerHTML:n.text,class:"text"},null,8,mo)):k("",!0),n.tagline?(d(),g("p",{key:2,innerHTML:n.tagline,class:"tagline"},null,8,vo)):k("",!0)],!0),v(n.$slots,"home-hero-info-after",{},void 0,!0),n.actions?(d(),g("div",go,[(d(!0),g(z,null,J(n.actions,i=>(d(),g("div",{key:i.link,class:"action"},[T(oo,{tag:"a",size:"medium",theme:i.theme,text:i.text,href:i.link,target:i.target,rel:i.rel},null,8,["theme","text","href","target","rel"])]))),128))])):k("",!0),v(n.$slots,"home-hero-actions-after",{},void 0,!0)]),n.image||f(t)?(d(),g("div",bo,[y("div",yo,[s[0]||(s[0]=y("div",{class:"image-bg"},null,-1)),v(n.$slots,"home-hero-image",{},()=>[n.image?(d(),F(Nt,{key:0,class:"image-src",image:n.image},null,8,["image"])):k("",!0)],!0)])])):k("",!0)])],2))}}),wo=L(_o,[["__scopeId","data-v-b10c5094"]]),xo=S({__name:"VPHomeHero",setup(e){const{frontmatter:t}=V();return(n,s)=>f(t).hero?(d(),F(wo,{key:0,class:"VPHomeHero",name:f(t).hero.name,text:f(t).hero.text,tagline:f(t).hero.tagline,image:f(t).hero.image,actions:f(t).hero.actions},{"home-hero-info-before":_(()=>[v(n.$slots,"home-hero-info-before")]),"home-hero-info":_(()=>[v(n.$slots,"home-hero-info")]),"home-hero-info-after":_(()=>[v(n.$slots,"home-hero-info-after")]),"home-hero-actions-after":_(()=>[v(n.$slots,"home-hero-actions-after")]),"home-hero-image":_(()=>[v(n.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):k("",!0)}}),Eo={class:"box"},Ao={key:0,class:"icon"},ko=["innerHTML"],Co=["innerHTML"],So=["innerHTML"],Po={key:4,class:"link-text"},Io={class:"link-text-value"},To=S({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(e){return(t,n)=>(d(),F(pe,{class:"VPFeature",href:t.link,rel:t.rel,target:t.target,"no-icon":!0,tag:t.link?"a":"div"},{default:_(()=>[y("article",Eo,[typeof t.icon=="object"&&t.icon.wrap?(d(),g("div",Ao,[T(Nt,{image:t.icon,alt:t.icon.alt,height:t.icon.height||48,width:t.icon.width||48},null,8,["image","alt","height","width"])])):typeof t.icon=="object"?(d(),F(Nt,{key:1,image:t.icon,alt:t.icon.alt,height:t.icon.height||48,width:t.icon.width||48},null,8,["image","alt","height","width"])):t.icon?(d(),g("div",{key:2,class:"icon",innerHTML:t.icon},null,8,ko)):k("",!0),y("h2",{class:"title",innerHTML:t.title},null,8,Co),t.details?(d(),g("p",{key:3,class:"details",innerHTML:t.details},null,8,So)):k("",!0),t.linkText?(d(),g("div",Po,[y("p",Io,[Ve(q(t.linkText)+" ",1),n[0]||(n[0]=y("span",{class:"vpi-arrow-right link-text-icon"},null,-1))])])):k("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),No=L(To,[["__scopeId","data-v-bd37d1a2"]]),Fo={key:0,class:"VPFeatures"},Lo={class:"container"},Do={class:"items"},$o=S({__name:"VPFeatures",props:{features:{}},setup(e){const t=e,n=B(()=>{const s=t.features.length;if(s){if(s===2)return"grid-2";if(s===3)return"grid-3";if(s%3===0)return"grid-6";if(s>3)return"grid-4"}else return});return(s,i)=>s.features?(d(),g("div",Fo,[y("div",Lo,[y("div",Do,[(d(!0),g(z,null,J(s.features,r=>(d(),g("div",{key:r.title,class:H(["item",[n.value]])},[T(No,{icon:r.icon,title:r.title,details:r.details,link:r.link,"link-text":r.linkText,rel:r.rel,target:r.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):k("",!0)}}),Bo=L($o,[["__scopeId","data-v-b1eea84a"]]),Vo=S({__name:"VPHomeFeatures",setup(e){const{frontmatter:t}=V();return(n,s)=>f(t).features?(d(),F(Bo,{key:0,class:"VPHomeFeatures",features:f(t).features},null,8,["features"])):k("",!0)}}),Oo=S({__name:"VPHomeContent",setup(e){const{width:t}=jr({initialWidth:0,includeScrollbar:!1});return(n,s)=>(d(),g("div",{class:"vp-doc container",style:Zs(f(t)?{"--vp-offset":`calc(50% - ${f(t)/2}px)`}:{})},[v(n.$slots,"default",{},void 0,!0)],4))}}),Mo=L(Oo,[["__scopeId","data-v-c141a4bd"]]),Ro={class:"VPHome"},jo=S({__name:"VPHome",setup(e){const{frontmatter:t}=V();return(n,s)=>{const i=Re("Content");return d(),g("div",Ro,[v(n.$slots,"home-hero-before",{},void 0,!0),T(xo,null,{"home-hero-info-before":_(()=>[v(n.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":_(()=>[v(n.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":_(()=>[v(n.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":_(()=>[v(n.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":_(()=>[v(n.$slots,"home-hero-image",{},void 0,!0)]),_:3}),v(n.$slots,"home-hero-after",{},void 0,!0),v(n.$slots,"home-features-before",{},void 0,!0),T(Vo),v(n.$slots,"home-features-after",{},void 0,!0),f(t).markdownStyles!==!1?(d(),F(Mo,{key:0},{default:_(()=>[T(i)]),_:1})):(d(),F(i,{key:1}))])}}}),Uo=L(jo,[["__scopeId","data-v-07b1ad08"]]),qo={},Ho={class:"VPPage"};function zo(e,t){const n=Re("Content");return d(),g("div",Ho,[v(e.$slots,"page-top"),T(n),v(e.$slots,"page-bottom")])}const Go=L(qo,[["render",zo]]),Wo=S({__name:"VPContent",setup(e){const{page:t,frontmatter:n}=V(),{hasSidebar:s}=Ae();return(i,r)=>(d(),g("div",{class:H(["VPContent",{"has-sidebar":f(s),"is-home":f(n).layout==="home"}]),id:"VPContent"},[f(t).isNotFound?v(i.$slots,"not-found",{key:0},()=>[T(ca)],!0):f(n).layout==="page"?(d(),F(Go,{key:1},{"page-top":_(()=>[v(i.$slots,"page-top",{},void 0,!0)]),"page-bottom":_(()=>[v(i.$slots,"page-bottom",{},void 0,!0)]),_:3})):f(n).layout==="home"?(d(),F(Uo,{key:2},{"home-hero-before":_(()=>[v(i.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":_(()=>[v(i.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":_(()=>[v(i.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":_(()=>[v(i.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":_(()=>[v(i.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":_(()=>[v(i.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":_(()=>[v(i.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":_(()=>[v(i.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":_(()=>[v(i.$slots,"home-features-after",{},void 0,!0)]),_:3})):f(n).layout&&f(n).layout!=="doc"?(d(),F(he(f(n).layout),{key:3})):(d(),F(ro,{key:4},{"doc-top":_(()=>[v(i.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":_(()=>[v(i.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":_(()=>[v(i.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":_(()=>[v(i.$slots,"doc-before",{},void 0,!0)]),"doc-after":_(()=>[v(i.$slots,"doc-after",{},void 0,!0)]),"aside-top":_(()=>[v(i.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":_(()=>[v(i.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":_(()=>[v(i.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":_(()=>[v(i.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":_(()=>[v(i.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":_(()=>[v(i.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),Ko=L(Wo,[["__scopeId","data-v-9a6c75ad"]]),Yo={class:"container"},Qo=["innerHTML"],Jo=["innerHTML"],Zo=S({__name:"VPFooter",setup(e){const{theme:t,frontmatter:n}=V(),{hasSidebar:s}=Ae();return(i,r)=>f(t).footer&&f(n).footer!==!1?(d(),g("footer",{key:0,class:H(["VPFooter",{"has-sidebar":f(s)}])},[y("div",Yo,[f(t).footer.message?(d(),g("p",{key:0,class:"message",innerHTML:f(t).footer.message},null,8,Qo)):k("",!0),f(t).footer.copyright?(d(),g("p",{key:1,class:"copyright",innerHTML:f(t).footer.copyright},null,8,Jo)):k("",!0)])],2)):k("",!0)}}),Xo=L(Zo,[["__scopeId","data-v-566314d4"]]);function eu(){const{theme:e,frontmatter:t}=V(),n=Qs([]),s=B(()=>n.value.length>0);return Ht(()=>{n.value=Un(t.value.outline??e.value.outline)}),{headers:n,hasLocalNav:s}}const tu={class:"menu-text"},nu={class:"header"},su={class:"outline"},iu=S({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(e){const t=e,{theme:n}=V(),s=U(!1),i=U(0),r=U(),a=U();function o(p){var m;(m=r.value)!=null&&m.contains(p.target)||(s.value=!1)}_e(s,p=>{if(p){document.addEventListener("click",o);return}document.removeEventListener("click",o)}),_n("Escape",()=>{s.value=!1}),Ht(()=>{s.value=!1});function u(){s.value=!s.value,i.value=window.innerHeight+Math.min(window.scrollY-t.navHeight,0)}function c(p){p.target.classList.contains("outline-link")&&(a.value&&(a.value.style.transition="none"),Ur(()=>{s.value=!1}))}function h(){s.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(p,m)=>(d(),g("div",{class:"VPLocalNavOutlineDropdown",style:Zs({"--vp-vh":i.value+"px"}),ref_key:"main",ref:r},[p.headers.length>0?(d(),g("button",{key:0,onClick:u,class:H({open:s.value})},[y("span",tu,q(f(si)(f(n))),1),m[0]||(m[0]=y("span",{class:"vpi-chevron-right icon"},null,-1))],2)):(d(),g("button",{key:1,onClick:h},q(f(n).returnToTopLabel||"Return to top"),1)),T(Vn,{name:"flyout"},{default:_(()=>[s.value?(d(),g("div",{key:0,ref_key:"items",ref:a,class:"items",onClick:c},[y("div",nu,[y("a",{class:"top-link",href:"#",onClick:h},q(f(n).returnToTopLabel||"Return to top"),1)]),y("div",su,[T(ii,{headers:p.headers},null,8,["headers"])])],512)):k("",!0)]),_:1})],4))}}),ru=L(iu,[["__scopeId","data-v-883964e0"]]),au={class:"container"},ou=["aria-expanded"],uu={class:"menu-text"},lu=S({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(e){const{theme:t,frontmatter:n}=V(),{hasSidebar:s}=Ae(),{headers:i}=eu(),{y:r}=Xs(),a=U(0);Me(()=>{a.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),Ht(()=>{i.value=Un(n.value.outline??t.value.outline)});const o=B(()=>i.value.length===0),u=B(()=>o.value&&!s.value),c=B(()=>({VPLocalNav:!0,"has-sidebar":s.value,empty:o.value,fixed:u.value}));return(h,p)=>f(n).layout!=="home"&&(!u.value||f(r)>=a.value)?(d(),g("div",{key:0,class:H(c.value)},[y("div",au,[f(s)?(d(),g("button",{key:0,class:"menu","aria-expanded":h.open,"aria-controls":"VPSidebarNav",onClick:p[0]||(p[0]=m=>h.$emit("open-menu"))},[p[1]||(p[1]=y("span",{class:"vpi-align-left menu-icon"},null,-1)),y("span",uu,q(f(t).sidebarMenuLabel||"Menu"),1)],8,ou)):k("",!0),T(ru,{headers:f(i),navHeight:a.value},null,8,["headers","navHeight"])])],2)):k("",!0)}}),cu=L(lu,[["__scopeId","data-v-2488c25a"]]);function hu(){const e=U(!1);function t(){e.value=!0,window.addEventListener("resize",i)}function n(){e.value=!1,window.removeEventListener("resize",i)}function s(){e.value?n():t()}function i(){window.outerWidth>=768&&n()}const r=zt();return _e(()=>r.path,n),{isScreenOpen:e,openScreen:t,closeScreen:n,toggleScreen:s}}const pu={},du={class:"VPSwitch",type:"button",role:"switch"},fu={class:"check"},mu={key:0,class:"icon"};function vu(e,t){return d(),g("button",du,[y("span",fu,[e.$slots.default?(d(),g("span",mu,[v(e.$slots,"default",{},void 0,!0)])):k("",!0)])])}const gu=L(pu,[["render",vu],["__scopeId","data-v-b4ccac88"]]),bu=S({__name:"VPSwitchAppearance",setup(e){const{isDark:t,theme:n}=V(),s=Gt("toggle-appearance",()=>{t.value=!t.value}),i=U("");return Rn(()=>{i.value=t.value?n.value.lightModeSwitchTitle||"Switch to light theme":n.value.darkModeSwitchTitle||"Switch to dark theme"}),(r,a)=>(d(),F(gu,{title:i.value,class:"VPSwitchAppearance","aria-checked":f(t),onClick:f(s)},{default:_(()=>a[0]||(a[0]=[y("span",{class:"vpi-sun sun"},null,-1),y("span",{class:"vpi-moon moon"},null,-1)])),_:1},8,["title","aria-checked","onClick"]))}}),qn=L(bu,[["__scopeId","data-v-be9742d9"]]),yu={key:0,class:"VPNavBarAppearance"},_u=S({__name:"VPNavBarAppearance",setup(e){const{site:t}=V();return(n,s)=>f(t).appearance&&f(t).appearance!=="force-dark"&&f(t).appearance!=="force-auto"?(d(),g("div",yu,[T(qn)])):k("",!0)}}),wu=L(_u,[["__scopeId","data-v-3f90c1a5"]]),Hn=U();let ri=!1,an=0;function xu(e){const t=U(!1);if(Wt){!ri&&Eu(),an++;const n=_e(Hn,s=>{var i,r,a;s===e.el.value||(i=e.el.value)!=null&&i.contains(s)?(t.value=!0,(r=e.onFocus)==null||r.call(e)):(t.value=!1,(a=e.onBlur)==null||a.call(e))});Mn(()=>{n(),an--,an||Au()})}return qr(t)}function Eu(){document.addEventListener("focusin",ai),ri=!0,Hn.value=document.activeElement}function Au(){document.removeEventListener("focusin",ai)}function ai(){Hn.value=document.activeElement}const ku={class:"VPMenuLink"},Cu=["innerHTML"],Su=S({__name:"VPMenuLink",props:{item:{}},setup(e){const{page:t}=V();return(n,s)=>(d(),g("div",ku,[T(pe,{class:H({active:f(Oe)(f(t).relativePath,n.item.activeMatch||n.item.link,!!n.item.activeMatch)}),href:n.item.link,target:n.item.target,rel:n.item.rel,"no-icon":n.item.noIcon},{default:_(()=>[y("span",{innerHTML:n.item.text},null,8,Cu)]),_:1},8,["class","href","target","rel","no-icon"])]))}}),Kt=L(Su,[["__scopeId","data-v-7eeeb2dc"]]),Pu={class:"VPMenuGroup"},Iu={key:0,class:"title"},Tu=S({__name:"VPMenuGroup",props:{text:{},items:{}},setup(e){return(t,n)=>(d(),g("div",Pu,[t.text?(d(),g("p",Iu,q(t.text),1)):k("",!0),(d(!0),g(z,null,J(t.items,s=>(d(),g(z,null,["link"in s?(d(),F(Kt,{key:0,item:s},null,8,["item"])):k("",!0)],64))),256))]))}}),Nu=L(Tu,[["__scopeId","data-v-a6b0397c"]]),Fu={class:"VPMenu"},Lu={key:0,class:"items"},Du=S({__name:"VPMenu",props:{items:{}},setup(e){return(t,n)=>(d(),g("div",Fu,[t.items?(d(),g("div",Lu,[(d(!0),g(z,null,J(t.items,s=>(d(),g(z,{key:JSON.stringify(s)},["link"in s?(d(),F(Kt,{key:0,item:s},null,8,["item"])):"component"in s?(d(),F(he(s.component),Te({key:1,ref_for:!0},s.props),null,16)):(d(),F(Nu,{key:2,text:s.text,items:s.items},null,8,["text","items"]))],64))),128))])):k("",!0),v(t.$slots,"default",{},void 0,!0)]))}}),$u=L(Du,[["__scopeId","data-v-20ed86d6"]]),Bu=["aria-expanded","aria-label"],Vu={key:0,class:"text"},Ou=["innerHTML"],Mu={key:1,class:"vpi-more-horizontal icon"},Ru={class:"menu"},ju=S({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(e){const t=U(!1),n=U();xu({el:n,onBlur:s});function s(){t.value=!1}return(i,r)=>(d(),g("div",{class:"VPFlyout",ref_key:"el",ref:n,onMouseenter:r[1]||(r[1]=a=>t.value=!0),onMouseleave:r[2]||(r[2]=a=>t.value=!1)},[y("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":t.value,"aria-label":i.label,onClick:r[0]||(r[0]=a=>t.value=!t.value)},[i.button||i.icon?(d(),g("span",Vu,[i.icon?(d(),g("span",{key:0,class:H([i.icon,"option-icon"])},null,2)):k("",!0),i.button?(d(),g("span",{key:1,innerHTML:i.button},null,8,Ou)):k("",!0),r[3]||(r[3]=y("span",{class:"vpi-chevron-down text-icon"},null,-1))])):(d(),g("span",Mu))],8,Bu),y("div",Ru,[T($u,{items:i.items},{default:_(()=>[v(i.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),zn=L(ju,[["__scopeId","data-v-bfe7971f"]]),Uu=["href","aria-label","innerHTML"],qu=S({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(e){const t=e,n=B(()=>typeof t.icon=="object"?t.icon.svg:``);return(s,i)=>(d(),g("a",{class:"VPSocialLink no-icon",href:s.link,"aria-label":s.ariaLabel??(typeof s.icon=="string"?s.icon:""),target:"_blank",rel:"noopener",innerHTML:n.value},null,8,Uu))}}),Hu=L(qu,[["__scopeId","data-v-358b6670"]]),zu={class:"VPSocialLinks"},Gu=S({__name:"VPSocialLinks",props:{links:{}},setup(e){return(t,n)=>(d(),g("div",zu,[(d(!0),g(z,null,J(t.links,({link:s,icon:i,ariaLabel:r})=>(d(),F(Hu,{key:s,icon:i,link:s,ariaLabel:r},null,8,["icon","link","ariaLabel"]))),128))]))}}),Gn=L(Gu,[["__scopeId","data-v-e71e869c"]]),Wu={key:0,class:"group translations"},Ku={class:"trans-title"},Yu={key:1,class:"group"},Qu={class:"item appearance"},Ju={class:"label"},Zu={class:"appearance-action"},Xu={key:2,class:"group"},el={class:"item social-links"},tl=S({__name:"VPNavBarExtra",setup(e){const{site:t,theme:n}=V(),{localeLinks:s,currentLang:i}=ht({correspondingLink:!0}),r=B(()=>s.value.length&&i.value.label||t.value.appearance||n.value.socialLinks);return(a,o)=>r.value?(d(),F(zn,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:_(()=>[f(s).length&&f(i).label?(d(),g("div",Wu,[y("p",Ku,q(f(i).label),1),(d(!0),g(z,null,J(f(s),u=>(d(),F(Kt,{key:u.link,item:u},null,8,["item"]))),128))])):k("",!0),f(t).appearance&&f(t).appearance!=="force-dark"&&f(t).appearance!=="force-auto"?(d(),g("div",Yu,[y("div",Qu,[y("p",Ju,q(f(n).darkModeSwitchLabel||"Appearance"),1),y("div",Zu,[T(qn)])])])):k("",!0),f(n).socialLinks?(d(),g("div",Xu,[y("div",el,[T(Gn,{class:"social-links-list",links:f(n).socialLinks},null,8,["links"])])])):k("",!0)]),_:1})):k("",!0)}}),nl=L(tl,[["__scopeId","data-v-f953d92f"]]),sl=["aria-expanded"],il=S({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(e){return(t,n)=>(d(),g("button",{type:"button",class:H(["VPNavBarHamburger",{active:t.active}]),"aria-label":"mobile navigation","aria-expanded":t.active,"aria-controls":"VPNavScreen",onClick:n[0]||(n[0]=s=>t.$emit("click"))},n[1]||(n[1]=[y("span",{class:"container"},[y("span",{class:"top"}),y("span",{class:"middle"}),y("span",{class:"bottom"})],-1)]),10,sl))}}),rl=L(il,[["__scopeId","data-v-6bee1efd"]]),al=["innerHTML"],ol=S({__name:"VPNavBarMenuLink",props:{item:{}},setup(e){const{page:t}=V();return(n,s)=>(d(),F(pe,{class:H({VPNavBarMenuLink:!0,active:f(Oe)(f(t).relativePath,n.item.activeMatch||n.item.link,!!n.item.activeMatch)}),href:n.item.link,target:n.item.target,rel:n.item.rel,"no-icon":n.item.noIcon,tabindex:"0"},{default:_(()=>[y("span",{innerHTML:n.item.text},null,8,al)]),_:1},8,["class","href","target","rel","no-icon"]))}}),ul=L(ol,[["__scopeId","data-v-815115f5"]]),ll=S({__name:"VPNavBarMenuGroup",props:{item:{}},setup(e){const t=e,{page:n}=V(),s=r=>"component"in r?!1:"link"in r?Oe(n.value.relativePath,r.link,!!t.item.activeMatch):r.items.some(s),i=B(()=>s(t.item));return(r,a)=>(d(),F(zn,{class:H({VPNavBarMenuGroup:!0,active:f(Oe)(f(n).relativePath,r.item.activeMatch,!!r.item.activeMatch)||i.value}),button:r.item.text,items:r.item.items},null,8,["class","button","items"]))}}),cl={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},hl=S({__name:"VPNavBarMenu",setup(e){const{theme:t}=V();return(n,s)=>f(t).nav?(d(),g("nav",cl,[s[0]||(s[0]=y("span",{id:"main-nav-aria-label",class:"visually-hidden"}," Main Navigation ",-1)),(d(!0),g(z,null,J(f(t).nav,i=>(d(),g(z,{key:JSON.stringify(i)},["link"in i?(d(),F(ul,{key:0,item:i},null,8,["item"])):"component"in i?(d(),F(he(i.component),Te({key:1,ref_for:!0},i.props),null,16)):(d(),F(ll,{key:2,item:i},null,8,["item"]))],64))),128))])):k("",!0)}}),pl=L(hl,[["__scopeId","data-v-afb2845e"]]);function dl(e){const{localeIndex:t,theme:n}=V();function s(i){var w,N,I;const r=i.split("."),a=(w=n.value.search)==null?void 0:w.options,o=a&&typeof a=="object",u=o&&((I=(N=a.locales)==null?void 0:N[t.value])==null?void 0:I.translations)||null,c=o&&a.translations||null;let h=u,p=c,m=e;const b=r.pop();for(const x of r){let C=null;const D=m==null?void 0:m[x];D&&(C=m=D);const R=p==null?void 0:p[x];R&&(C=p=R);const Ce=h==null?void 0:h[x];Ce&&(C=h=Ce),D||(m=C),R||(p=C),Ce||(h=C)}return(h==null?void 0:h[b])??(p==null?void 0:p[b])??(m==null?void 0:m[b])??""}return s}const fl=["aria-label"],ml={class:"DocSearch-Button-Container"},vl={class:"DocSearch-Button-Placeholder"},gs=S({__name:"VPNavBarSearchButton",setup(e){const n=dl({button:{buttonText:"Search",buttonAriaLabel:"Search"}});return(s,i)=>(d(),g("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":f(n)("button.buttonAriaLabel")},[y("span",ml,[i[0]||(i[0]=y("span",{class:"vp-icon DocSearch-Search-Icon"},null,-1)),y("span",vl,q(f(n)("button.buttonText")),1)]),i[1]||(i[1]=y("span",{class:"DocSearch-Button-Keys"},[y("kbd",{class:"DocSearch-Button-Key"}),y("kbd",{class:"DocSearch-Button-Key"},"K")],-1))],8,fl))}}),gl={class:"VPNavBarSearch"},bl={id:"local-search"},yl={key:1,id:"docsearch"},_l=S({__name:"VPNavBarSearch",setup(e){const t=Hr(()=>at(()=>import("./VPLocalSearchBox.CrIhCP-G.js"),__vite__mapDeps([0,1]))),n=()=>null,{theme:s}=V(),i=U(!1),r=U(!1);Me(()=>{});function a(){i.value||(i.value=!0,setTimeout(o,16))}function o(){const p=new Event("keydown");p.key="k",p.metaKey=!0,window.dispatchEvent(p),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||o()},16)}function u(p){const m=p.target,b=m.tagName;return m.isContentEditable||b==="INPUT"||b==="SELECT"||b==="TEXTAREA"}const c=U(!1);_n("k",p=>{(p.ctrlKey||p.metaKey)&&(p.preventDefault(),c.value=!0)}),_n("/",p=>{u(p)||(p.preventDefault(),c.value=!0)});const h="local";return(p,m)=>{var b;return d(),g("div",gl,[f(h)==="local"?(d(),g(z,{key:0},[c.value?(d(),F(f(t),{key:0,onClose:m[0]||(m[0]=w=>c.value=!1)})):k("",!0),y("div",bl,[T(gs,{onClick:m[1]||(m[1]=w=>c.value=!0)})])],64)):f(h)==="algolia"?(d(),g(z,{key:1},[i.value?(d(),F(f(n),{key:0,algolia:((b=f(s).search)==null?void 0:b.options)??f(s).algolia,onVnodeBeforeMount:m[2]||(m[2]=w=>r.value=!0)},null,8,["algolia"])):k("",!0),r.value?k("",!0):(d(),g("div",yl,[T(gs,{onClick:a})]))],64)):k("",!0)])}}}),wl=S({__name:"VPNavBarSocialLinks",setup(e){const{theme:t}=V();return(n,s)=>f(t).socialLinks?(d(),F(Gn,{key:0,class:"VPNavBarSocialLinks",links:f(t).socialLinks},null,8,["links"])):k("",!0)}}),xl=L(wl,[["__scopeId","data-v-ef6192dc"]]),El=["href","rel","target"],Al={key:1},kl={key:2},Cl=S({__name:"VPNavBarTitle",setup(e){const{site:t,theme:n}=V(),{hasSidebar:s}=Ae(),{currentLang:i}=ht(),r=B(()=>{var u;return typeof n.value.logoLink=="string"?n.value.logoLink:(u=n.value.logoLink)==null?void 0:u.link}),a=B(()=>{var u;return typeof n.value.logoLink=="string"||(u=n.value.logoLink)==null?void 0:u.rel}),o=B(()=>{var u;return typeof n.value.logoLink=="string"||(u=n.value.logoLink)==null?void 0:u.target});return(u,c)=>(d(),g("div",{class:H(["VPNavBarTitle",{"has-sidebar":f(s)}])},[y("a",{class:"title",href:r.value??f(jn)(f(i).link),rel:a.value,target:o.value},[v(u.$slots,"nav-bar-title-before",{},void 0,!0),f(n).logo?(d(),F(Nt,{key:0,class:"logo",image:f(n).logo},null,8,["image"])):k("",!0),f(n).siteTitle?(d(),g("span",Al,q(f(n).siteTitle),1)):f(n).siteTitle===void 0?(d(),g("span",kl,q(f(t).title),1)):k("",!0),v(u.$slots,"nav-bar-title-after",{},void 0,!0)],8,El)],2))}}),Sl=L(Cl,[["__scopeId","data-v-0ad69264"]]),Pl={class:"items"},Il={class:"title"},Tl=S({__name:"VPNavBarTranslations",setup(e){const{theme:t}=V(),{localeLinks:n,currentLang:s}=ht({correspondingLink:!0});return(i,r)=>f(n).length&&f(s).label?(d(),F(zn,{key:0,class:"VPNavBarTranslations",icon:"vpi-languages",label:f(t).langMenuLabel||"Change language"},{default:_(()=>[y("div",Pl,[y("p",Il,q(f(s).label),1),(d(!0),g(z,null,J(f(n),a=>(d(),F(Kt,{key:a.link,item:a},null,8,["item"]))),128))])]),_:1},8,["label"])):k("",!0)}}),Nl=L(Tl,[["__scopeId","data-v-acee064b"]]),Fl={class:"wrapper"},Ll={class:"container"},Dl={class:"title"},$l={class:"content"},Bl={class:"content-body"},Vl=S({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(e){const t=e,{y:n}=Xs(),{hasSidebar:s}=Ae(),{frontmatter:i}=V(),r=U({});return Rn(()=>{r.value={"has-sidebar":s.value,home:i.value.layout==="home",top:n.value===0,"screen-open":t.isScreenOpen}}),(a,o)=>(d(),g("div",{class:H(["VPNavBar",r.value])},[y("div",Fl,[y("div",Ll,[y("div",Dl,[T(Sl,null,{"nav-bar-title-before":_(()=>[v(a.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":_(()=>[v(a.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),y("div",$l,[y("div",Bl,[v(a.$slots,"nav-bar-content-before",{},void 0,!0),T(_l,{class:"search"}),T(pl,{class:"menu"}),T(Nl,{class:"translations"}),T(wu,{class:"appearance"}),T(xl,{class:"social-links"}),T(nl,{class:"extra"}),v(a.$slots,"nav-bar-content-after",{},void 0,!0),T(rl,{class:"hamburger",active:a.isScreenOpen,onClick:o[0]||(o[0]=u=>a.$emit("toggle-screen"))},null,8,["active"])])])])]),o[1]||(o[1]=y("div",{class:"divider"},[y("div",{class:"divider-line"})],-1))],2))}}),Ol=L(Vl,[["__scopeId","data-v-9fd4d1dd"]]),Ml={key:0,class:"VPNavScreenAppearance"},Rl={class:"text"},jl=S({__name:"VPNavScreenAppearance",setup(e){const{site:t,theme:n}=V();return(s,i)=>f(t).appearance&&f(t).appearance!=="force-dark"&&f(t).appearance!=="force-auto"?(d(),g("div",Ml,[y("p",Rl,q(f(n).darkModeSwitchLabel||"Appearance"),1),T(qn)])):k("",!0)}}),Ul=L(jl,[["__scopeId","data-v-a3e2920d"]]),ql=["innerHTML"],Hl=S({__name:"VPNavScreenMenuLink",props:{item:{}},setup(e){const t=Gt("close-screen");return(n,s)=>(d(),F(pe,{class:"VPNavScreenMenuLink",href:n.item.link,target:n.item.target,rel:n.item.rel,"no-icon":n.item.noIcon,onClick:f(t)},{default:_(()=>[y("span",{innerHTML:n.item.text},null,8,ql)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),zl=L(Hl,[["__scopeId","data-v-fa963d97"]]),Gl=["innerHTML"],Wl=S({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(e){const t=Gt("close-screen");return(n,s)=>(d(),F(pe,{class:"VPNavScreenMenuGroupLink",href:n.item.link,target:n.item.target,rel:n.item.rel,"no-icon":n.item.noIcon,onClick:f(t)},{default:_(()=>[y("span",{innerHTML:n.item.text},null,8,Gl)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),oi=L(Wl,[["__scopeId","data-v-e04f3e85"]]),Kl={class:"VPNavScreenMenuGroupSection"},Yl={key:0,class:"title"},Ql=S({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(e){return(t,n)=>(d(),g("div",Kl,[t.text?(d(),g("p",Yl,q(t.text),1)):k("",!0),(d(!0),g(z,null,J(t.items,s=>(d(),F(oi,{key:s.text,item:s},null,8,["item"]))),128))]))}}),Jl=L(Ql,[["__scopeId","data-v-f60dbfa7"]]),Zl=["aria-controls","aria-expanded"],Xl=["innerHTML"],ec=["id"],tc={key:0,class:"item"},nc={key:1,class:"item"},sc={key:2,class:"group"},ic=S({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(e){const t=e,n=U(!1),s=B(()=>`NavScreenGroup-${t.text.replace(" ","-").toLowerCase()}`);function i(){n.value=!n.value}return(r,a)=>(d(),g("div",{class:H(["VPNavScreenMenuGroup",{open:n.value}])},[y("button",{class:"button","aria-controls":s.value,"aria-expanded":n.value,onClick:i},[y("span",{class:"button-text",innerHTML:r.text},null,8,Xl),a[0]||(a[0]=y("span",{class:"vpi-plus button-icon"},null,-1))],8,Zl),y("div",{id:s.value,class:"items"},[(d(!0),g(z,null,J(r.items,o=>(d(),g(z,{key:JSON.stringify(o)},["link"in o?(d(),g("div",tc,[T(oi,{item:o},null,8,["item"])])):"component"in o?(d(),g("div",nc,[(d(),F(he(o.component),Te({ref_for:!0},o.props,{"screen-menu":""}),null,16))])):(d(),g("div",sc,[T(Jl,{text:o.text,items:o.items},null,8,["text","items"])]))],64))),128))],8,ec)],2))}}),rc=L(ic,[["__scopeId","data-v-d99bfeec"]]),ac={key:0,class:"VPNavScreenMenu"},oc=S({__name:"VPNavScreenMenu",setup(e){const{theme:t}=V();return(n,s)=>f(t).nav?(d(),g("nav",ac,[(d(!0),g(z,null,J(f(t).nav,i=>(d(),g(z,{key:JSON.stringify(i)},["link"in i?(d(),F(zl,{key:0,item:i},null,8,["item"])):"component"in i?(d(),F(he(i.component),Te({key:1,ref_for:!0},i.props,{"screen-menu":""}),null,16)):(d(),F(rc,{key:2,text:i.text||"",items:i.items},null,8,["text","items"]))],64))),128))])):k("",!0)}}),uc=S({__name:"VPNavScreenSocialLinks",setup(e){const{theme:t}=V();return(n,s)=>f(t).socialLinks?(d(),F(Gn,{key:0,class:"VPNavScreenSocialLinks",links:f(t).socialLinks},null,8,["links"])):k("",!0)}}),lc={class:"list"},cc=S({__name:"VPNavScreenTranslations",setup(e){const{localeLinks:t,currentLang:n}=ht({correspondingLink:!0}),s=U(!1);function i(){s.value=!s.value}return(r,a)=>f(t).length&&f(n).label?(d(),g("div",{key:0,class:H(["VPNavScreenTranslations",{open:s.value}])},[y("button",{class:"title",onClick:i},[a[0]||(a[0]=y("span",{class:"vpi-languages icon lang"},null,-1)),Ve(" "+q(f(n).label)+" ",1),a[1]||(a[1]=y("span",{class:"vpi-chevron-down icon chevron"},null,-1))]),y("ul",lc,[(d(!0),g(z,null,J(f(t),o=>(d(),g("li",{key:o.link,class:"item"},[T(pe,{class:"link",href:o.link},{default:_(()=>[Ve(q(o.text),1)]),_:2},1032,["href"])]))),128))])],2)):k("",!0)}}),hc=L(cc,[["__scopeId","data-v-516e4bc3"]]),pc={class:"container"},dc=S({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(e){const t=U(null),n=ei(Wt?document.body:null);return(s,i)=>(d(),F(Vn,{name:"fade",onEnter:i[0]||(i[0]=r=>n.value=!0),onAfterLeave:i[1]||(i[1]=r=>n.value=!1)},{default:_(()=>[s.open?(d(),g("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:t,id:"VPNavScreen"},[y("div",pc,[v(s.$slots,"nav-screen-content-before",{},void 0,!0),T(oc,{class:"menu"}),T(hc,{class:"translations"}),T(Ul,{class:"appearance"}),T(uc,{class:"social-links"}),v(s.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):k("",!0)]),_:3}))}}),fc=L(dc,[["__scopeId","data-v-2dd6d0c7"]]),mc={key:0,class:"VPNav"},vc=S({__name:"VPNav",setup(e){const{isScreenOpen:t,closeScreen:n,toggleScreen:s}=hu(),{frontmatter:i}=V(),r=B(()=>i.value.navbar!==!1);return ti("close-screen",n),qt(()=>{Wt&&document.documentElement.classList.toggle("hide-nav",!r.value)}),(a,o)=>r.value?(d(),g("header",mc,[T(Ol,{"is-screen-open":f(t),onToggleScreen:f(s)},{"nav-bar-title-before":_(()=>[v(a.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":_(()=>[v(a.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":_(()=>[v(a.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":_(()=>[v(a.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),T(fc,{open:f(t)},{"nav-screen-content-before":_(()=>[v(a.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":_(()=>[v(a.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):k("",!0)}}),gc=L(vc,[["__scopeId","data-v-7ad780c2"]]),bc=["role","tabindex"],yc={key:1,class:"items"},_c=S({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(e){const t=e,{collapsed:n,collapsible:s,isLink:i,isActiveLink:r,hasActiveLink:a,hasChildren:o,toggle:u}=fa(B(()=>t.item)),c=B(()=>o.value?"section":"div"),h=B(()=>i.value?"a":"div"),p=B(()=>o.value?t.depth+2===7?"p":`h${t.depth+2}`:"p"),m=B(()=>i.value?void 0:"button"),b=B(()=>[[`level-${t.depth}`],{collapsible:s.value},{collapsed:n.value},{"is-link":i.value},{"is-active":r.value},{"has-active":a.value}]);function w(I){"key"in I&&I.key!=="Enter"||!t.item.link&&u()}function N(){t.item.link&&u()}return(I,x)=>{const C=Re("VPSidebarItem",!0);return d(),F(he(c.value),{class:H(["VPSidebarItem",b.value])},{default:_(()=>[I.item.text?(d(),g("div",Te({key:0,class:"item",role:m.value},Gr(I.item.items?{click:w,keydown:w}:{},!0),{tabindex:I.item.items&&0}),[x[1]||(x[1]=y("div",{class:"indicator"},null,-1)),I.item.link?(d(),F(pe,{key:0,tag:h.value,class:"link",href:I.item.link,rel:I.item.rel,target:I.item.target},{default:_(()=>[(d(),F(he(p.value),{class:"text",innerHTML:I.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(d(),F(he(p.value),{key:1,class:"text",innerHTML:I.item.text},null,8,["innerHTML"])),I.item.collapsed!=null&&I.item.items&&I.item.items.length?(d(),g("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:N,onKeydown:zr(N,["enter"]),tabindex:"0"},x[0]||(x[0]=[y("span",{class:"vpi-chevron-right caret-icon"},null,-1)]),32)):k("",!0)],16,bc)):k("",!0),I.item.items&&I.item.items.length?(d(),g("div",yc,[I.depth<5?(d(!0),g(z,{key:0},J(I.item.items,D=>(d(),F(C,{key:D.text,item:D,depth:I.depth+1},null,8,["item","depth"]))),128)):k("",!0)])):k("",!0)]),_:1},8,["class"])}}}),wc=L(_c,[["__scopeId","data-v-edd2eed8"]]),xc=S({__name:"VPSidebarGroup",props:{items:{}},setup(e){const t=U(!0);let n=null;return Me(()=>{n=setTimeout(()=>{n=null,t.value=!1},300)}),Wr(()=>{n!=null&&(clearTimeout(n),n=null)}),(s,i)=>(d(!0),g(z,null,J(s.items,r=>(d(),g("div",{key:r.text,class:H(["group",{"no-transition":t.value}])},[T(wc,{item:r,depth:0},null,8,["item"])],2))),128))}}),Ec=L(xc,[["__scopeId","data-v-51288d80"]]),Ac={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},kc=S({__name:"VPSidebar",props:{open:{type:Boolean}},setup(e){const{sidebarGroups:t,hasSidebar:n}=Ae(),s=e,i=U(null),r=ei(Wt?document.body:null);_e([s,i],()=>{var o;s.open?(r.value=!0,(o=i.value)==null||o.focus()):r.value=!1},{immediate:!0,flush:"post"});const a=U(0);return _e(t,()=>{a.value+=1},{deep:!0}),(o,u)=>f(n)?(d(),g("aside",{key:0,class:H(["VPSidebar",{open:o.open}]),ref_key:"navEl",ref:i,onClick:u[0]||(u[0]=Kr(()=>{},["stop"]))},[u[2]||(u[2]=y("div",{class:"curtain"},null,-1)),y("nav",Ac,[u[1]||(u[1]=y("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),v(o.$slots,"sidebar-nav-before",{},void 0,!0),(d(),F(Ec,{items:f(t),key:a.value},null,8,["items"])),v(o.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):k("",!0)}}),Cc=L(kc,[["__scopeId","data-v-42c4c606"]]),Sc=S({__name:"VPSkipLink",setup(e){const t=zt(),n=U();_e(()=>t.path,()=>n.value.focus());function s({target:i}){const r=document.getElementById(decodeURIComponent(i.hash).slice(1));if(r){const a=()=>{r.removeAttribute("tabindex"),r.removeEventListener("blur",a)};r.setAttribute("tabindex","-1"),r.addEventListener("blur",a),r.focus(),window.scrollTo(0,0)}}return(i,r)=>(d(),g(z,null,[y("span",{ref_key:"backToTop",ref:n,tabindex:"-1"},null,512),y("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:s}," Skip to content ")],64))}}),Pc=L(Sc,[["__scopeId","data-v-c8291ffa"]]),Ic=S({__name:"Layout",setup(e){const{isOpen:t,open:n,close:s}=Ae(),i=zt();_e(()=>i.path,s),da(t,s);const{frontmatter:r}=V(),a=Yr(),o=B(()=>!!a["home-hero-image"]);return ti("hero-image-slot-exists",o),(u,c)=>{const h=Re("Content");return f(r).layout!==!1?(d(),g("div",{key:0,class:H(["Layout",f(r).pageClass])},[v(u.$slots,"layout-top",{},void 0,!0),T(Pc),T(ea,{class:"backdrop",show:f(t),onClick:f(s)},null,8,["show","onClick"]),T(gc,null,{"nav-bar-title-before":_(()=>[v(u.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":_(()=>[v(u.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":_(()=>[v(u.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":_(()=>[v(u.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":_(()=>[v(u.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":_(()=>[v(u.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),T(cu,{open:f(t),onOpenMenu:f(n)},null,8,["open","onOpenMenu"]),T(Cc,{open:f(t)},{"sidebar-nav-before":_(()=>[v(u.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":_(()=>[v(u.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),T(Ko,null,{"page-top":_(()=>[v(u.$slots,"page-top",{},void 0,!0)]),"page-bottom":_(()=>[v(u.$slots,"page-bottom",{},void 0,!0)]),"not-found":_(()=>[v(u.$slots,"not-found",{},void 0,!0)]),"home-hero-before":_(()=>[v(u.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":_(()=>[v(u.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":_(()=>[v(u.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":_(()=>[v(u.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":_(()=>[v(u.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":_(()=>[v(u.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":_(()=>[v(u.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":_(()=>[v(u.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":_(()=>[v(u.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":_(()=>[v(u.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":_(()=>[v(u.$slots,"doc-before",{},void 0,!0)]),"doc-after":_(()=>[v(u.$slots,"doc-after",{},void 0,!0)]),"doc-top":_(()=>[v(u.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":_(()=>[v(u.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":_(()=>[v(u.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":_(()=>[v(u.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":_(()=>[v(u.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":_(()=>[v(u.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":_(()=>[v(u.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":_(()=>[v(u.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),T(Xo),v(u.$slots,"layout-bottom",{},void 0,!0)],2)):(d(),F(h,{key:1}))}}}),Tc=L(Ic,[["__scopeId","data-v-d8b57b2d"]]),bs={Layout:Tc,enhanceApp:({app:e})=>{e.component("Badge",Jr)}};function Ft(e,t,n){n=n||{};var s=e.ownerDocument,i=s.defaultView.CustomEvent;typeof i=="function"?i=new i(t,{detail:n}):(i=s.createEvent("Event"),i.initEvent(t,!1,!1),i.detail=n),e.dispatchEvent(i)}function ui(e){return Array.isArray(e)||e instanceof Int8Array||e instanceof Int16Array||e instanceof Int32Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Uint16Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array}function li(e){return e===(e|0)+""}function ce(e){const t=document.createElement("span");return t.className="observablehq--cellname",t.textContent=`${e} = `,t}const Nc=Symbol.prototype.toString;function We(e){return Nc.call(e)}const{getOwnPropertySymbols:Fc,prototype:{hasOwnProperty:Lc}}=Object,{toStringTag:Dc}=Symbol,ci={},pt=Fc;function Yt(e,t){return Lc.call(e,t)}function An(e){return e[Dc]||e.constructor&&e.constructor.name||"Object"}function oe(e,t){try{const n=e[t];return n&&n.constructor,n}catch{return ci}}const $c=[{symbol:"@@__IMMUTABLE_INDEXED__@@",name:"Indexed",modifier:!0},{symbol:"@@__IMMUTABLE_KEYED__@@",name:"Keyed",modifier:!0},{symbol:"@@__IMMUTABLE_LIST__@@",name:"List",arrayish:!0},{symbol:"@@__IMMUTABLE_MAP__@@",name:"Map"},{symbol:"@@__IMMUTABLE_ORDERED__@@",name:"Ordered",modifier:!0,prefix:!0},{symbol:"@@__IMMUTABLE_RECORD__@@",name:"Record"},{symbol:"@@__IMMUTABLE_SET__@@",name:"Set",arrayish:!0,setish:!0},{symbol:"@@__IMMUTABLE_STACK__@@",name:"Stack",arrayish:!0}];function hi(e){try{let t=$c.filter(({symbol:a})=>e[a]===!0);if(!t.length)return;const n=t.find(a=>!a.modifier),s=n.name==="Map"&&t.find(a=>a.modifier&&a.prefix),i=t.some(a=>a.arrayish),r=t.some(a=>a.setish);return{name:`${s?s.name:""}${n.name}`,symbols:t,arrayish:i&&!r,setish:r}}catch{return null}}const{getPrototypeOf:Wn,getOwnPropertyDescriptors:Bc}=Object,pi=Wn({});function di(e,t,n,s){let i=ui(e),r,a,o,u;e instanceof Map?e instanceof e.constructor?(r=`Map(${e.size})`,a=Vc):(r="Map()",a=nt):e instanceof Set?e instanceof e.constructor?(r=`Set(${e.size})`,a=Oc):(r="Set()",a=nt):i?(r=`${e.constructor.name}(${e.length})`,a=Rc):(u=hi(e))?(r=`Immutable.${u.name}${u.name==="Record"?"":`(${e.size})`}`,i=u.arrayish,a=u.arrayish?jc:u.setish?Mc:qc):s?(r=An(e),a=Uc):(r=An(e),a=nt);const c=document.createElement("span");c.className="observablehq--expanded",n&&c.appendChild(ce(n));const h=c.appendChild(document.createElement("a"));h.innerHTML=` + + `,h.appendChild(document.createTextNode(`${r}${i?" [":" {"}`)),h.addEventListener("mouseup",function(p){p.stopPropagation(),Dt(c,Kn(e,null,n,s))}),a=a(e);for(let p=0;!(o=a.next()).done&&p<20;++p)c.appendChild(o.value);if(!o.done){const p=c.appendChild(document.createElement("a"));p.className="observablehq--field",p.style.display="block",p.appendChild(document.createTextNode(" … more")),p.addEventListener("mouseup",function(m){m.stopPropagation(),c.insertBefore(o.value,c.lastChild.previousSibling);for(let b=0;!(o=a.next()).done&&b<19;++b)c.insertBefore(o.value,c.lastChild.previousSibling);o.done&&c.removeChild(c.lastChild.previousSibling),Ft(c,"load")})}return c.appendChild(document.createTextNode(i?"]":"}")),c}function*Vc(e){for(const[t,n]of e)yield Hc(t,n);yield*nt(e)}function*Oc(e){for(const t of e)yield mi(t);yield*nt(e)}function*Mc(e){for(const t of e)yield mi(t)}function*Rc(e){for(let t=0,n=e.length;t ")),n.appendChild(te(t)),n}function mi(e){const t=document.createElement("div");return t.className="observablehq--field",t.appendChild(document.createTextNode(" ")),t.appendChild(te(e)),t}function ys(e){const t=window.getSelection();return t.type==="Range"&&(t.containsNode(e,!0)||e.contains(t.anchorNode)||e.contains(t.focusNode))}function Kn(e,t,n,s){let i=ui(e),r,a,o,u;if(e instanceof Map?e instanceof e.constructor?(r=`Map(${e.size})`,a=zc):(r="Map()",a=st):e instanceof Set?e instanceof e.constructor?(r=`Set(${e.size})`,a=Gc):(r="Set()",a=st):i?(r=`${e.constructor.name}(${e.length})`,a=Yc):(u=hi(e))?(r=`Immutable.${u.name}${u.name==="Record"?"":`(${e.size})`}`,i=u.arrayish,a=u.arrayish?Kc:u.setish?Wc:Qc):(r=An(e),a=st),t){const p=document.createElement("span");return p.className="observablehq--shallow",n&&p.appendChild(ce(n)),p.appendChild(document.createTextNode(r)),p.addEventListener("mouseup",function(m){ys(p)||(m.stopPropagation(),Dt(p,Kn(e)))}),p}const c=document.createElement("span");c.className="observablehq--collapsed",n&&c.appendChild(ce(n));const h=c.appendChild(document.createElement("a"));h.innerHTML=` + + `,h.appendChild(document.createTextNode(`${r}${i?" [":" {"}`)),c.addEventListener("mouseup",function(p){ys(c)||(p.stopPropagation(),Dt(c,di(e,null,n,s)))},!0),a=a(e);for(let p=0;!(o=a.next()).done&&p<20;++p)p>0&&c.appendChild(document.createTextNode(", ")),c.appendChild(o.value);return o.done||c.appendChild(document.createTextNode(", …")),c.appendChild(document.createTextNode(i?"]":"}")),c}function*zc(e){for(const[t,n]of e)yield Jc(t,n);yield*st(e)}function*Gc(e){for(const t of e)yield te(t,!0);yield*st(e)}function*Wc(e){for(const t of e)yield te(t,!0)}function*Kc(e){let t=-1,n=0;for(const s=e.size;nt+1&&(yield Lt(n-t-1)),yield te(e.get(n),!0),t=n;n>t+1&&(yield Lt(n-t-1))}function*Yc(e){let t=-1,n=0;for(const s=e.length;nt+1&&(yield Lt(n-t-1)),yield te(oe(e,n),!0),t=n);n>t+1&&(yield Lt(n-t-1));for(const s in e)!li(s)&&Yt(e,s)&&(yield ot(s,oe(e,s),"observablehq--key"));for(const s of pt(e))yield ot(We(s),oe(e,s),"observablehq--symbol")}function*st(e){for(const t in e)Yt(e,t)&&(yield ot(t,oe(e,t),"observablehq--key"));for(const t of pt(e))yield ot(We(t),oe(e,t),"observablehq--symbol")}function*Qc(e){for(const[t,n]of e)yield ot(t,n,"observablehq--key")}function Lt(e){const t=document.createElement("span");return t.className="observablehq--empty",t.textContent=e===1?"empty":`empty × ${e}`,t}function ot(e,t,n){const s=document.createDocumentFragment(),i=s.appendChild(document.createElement("span"));return i.className=n,i.textContent=e,s.appendChild(document.createTextNode(": ")),s.appendChild(te(t,!0)),s}function Jc(e,t){const n=document.createDocumentFragment();return n.appendChild(te(e,!0)),n.appendChild(document.createTextNode(" => ")),n.appendChild(te(t,!0)),n}function Zc(e,t){if(e instanceof Date||(e=new Date(+e)),isNaN(e))return typeof t=="function"?t(e):t;const n=e.getUTCHours(),s=e.getUTCMinutes(),i=e.getUTCSeconds(),r=e.getUTCMilliseconds();return`${Xc(e.getUTCFullYear())}-${ve(e.getUTCMonth()+1,2)}-${ve(e.getUTCDate(),2)}${n||s||i||r?`T${ve(n,2)}:${ve(s,2)}${i||r?`:${ve(i,2)}${r?`.${ve(r,3)}`:""}`:""}Z`:""}`}function Xc(e){return e<0?`-${ve(-e,6)}`:e>9999?`+${ve(e,6)}`:ve(e,4)}function ve(e,t){return`${e}`.padStart(t,"0")}function eh(e){return Zc(e,"Invalid Date")}var th=Error.prototype.toString;function nh(e){return e.stack||th.call(e)}var sh=RegExp.prototype.toString;function ih(e){return sh.call(e)}const on=20;function rh(e,t,n,s){if(t===!1){if(ws(e,/["\n]/g)<=ws(e,/`|\${/g)){const c=document.createElement("span");s&&c.appendChild(ce(s));const h=c.appendChild(document.createElement("span"));return h.className="observablehq--string",h.textContent=JSON.stringify(e),c}const a=e.split(` +`);if(a.length>on&&!n){const c=document.createElement("div");s&&c.appendChild(ce(s));const h=c.appendChild(document.createElement("span"));h.className="observablehq--string",h.textContent="`"+_s(a.slice(0,on).join(` +`));const p=c.appendChild(document.createElement("span")),m=a.length-on;return p.textContent=`Show ${m} truncated line${m>1?"s":""}`,p.className="observablehq--string-expand",p.addEventListener("mouseup",function(b){b.stopPropagation(),Dt(c,te(e,t,!0,s))}),c}const o=document.createElement("span");s&&o.appendChild(ce(s));const u=o.appendChild(document.createElement("span"));return u.className=`observablehq--string${n?" observablehq--expanded":""}`,u.textContent="`"+_s(e)+"`",o}const i=document.createElement("span");s&&i.appendChild(ce(s));const r=i.appendChild(document.createElement("span"));return r.className="observablehq--string",r.textContent=JSON.stringify(e.length>100?`${e.slice(0,50)}…${e.slice(-49)}`:e),i}function _s(e){return e.replace(/[\\`\x00-\x09\x0b-\x19]|\${/g,ah)}function ah(e){var t=e.charCodeAt(0);switch(t){case 8:return"\\b";case 9:return"\\t";case 11:return"\\v";case 12:return"\\f";case 13:return"\\r"}return t<16?"\\x0"+t.toString(16):t<32?"\\x"+t.toString(16):"\\"+e}function ws(e,t){for(var n=0;t.exec(e);)++n;return n}var oh=Function.prototype.toString,uh={prefix:"async ƒ"},lh={prefix:"async ƒ*"},xs={prefix:"class"},ch={prefix:"ƒ"},hh={prefix:"ƒ*"};function ph(e,t){var n,s,i=oh.call(e);switch(e.constructor&&e.constructor.name){case"AsyncFunction":n=uh;break;case"AsyncGeneratorFunction":n=lh;break;case"GeneratorFunction":n=hh;break;default:n=/^class\b/.test(i)?xs:ch;break}return n===xs?Ze(n,"",t):(s=/^(?:async\s*)?(\w+)\s*=>/.exec(i))?Ze(n,"("+s[1]+")",t):(s=/^(?:async\s*)?\(\s*(\w+(?:\s*,\s*\w+)*)?\s*\)/.exec(i))?Ze(n,s[1]?"("+s[1].replace(/\s*,\s*/g,", ")+")":"()",t):(s=/^(?:async\s*)?function(?:\s*\*)?(?:\s*\w+)?\s*\(\s*(\w+(?:\s*,\s*\w+)*)?\s*\)/.exec(i))?Ze(n,s[1]?"("+s[1].replace(/\s*,\s*/g,", ")+")":"()",t):Ze(n,"(…)",t)}function Ze(e,t,n){var s=document.createElement("span");s.className="observablehq--function",n&&s.appendChild(ce(n));var i=s.appendChild(document.createElement("span"));return i.className="observablehq--keyword",i.textContent=e.prefix,s.appendChild(document.createTextNode(t)),s}const{prototype:{toString:dh}}=Object;function te(e,t,n,s,i){let r=typeof e;switch(r){case"boolean":case"undefined":{e+="";break}case"number":{e=e===0&&1/e<0?"-0":e+"";break}case"bigint":{e=e+"n";break}case"symbol":{e=We(e);break}case"function":return ph(e,s);case"string":return rh(e,t,n,s);default:{if(e===null){r=null,e="null";break}if(e instanceof Date){r="date",e=eh(e);break}if(e===ci){r="forbidden",e="[forbidden]";break}switch(dh.call(e)){case"[object RegExp]":{r="regexp",e=ih(e);break}case"[object Error]":case"[object DOMException]":{r="error",e=nh(e);break}default:return(n?di:Kn)(e,t,s,i)}break}}const a=document.createElement("span");s&&a.appendChild(ce(s));const o=a.appendChild(document.createElement("span"));return o.className=`observablehq--${r}`,o.textContent=e,a}function Dt(e,t){e.classList.contains("observablehq--inspect")&&t.classList.add("observablehq--inspect"),e.parentNode.replaceChild(t,e),Ft(t,"load")}const fh=/\s+\(\d+:\d+\)$/m;class kn{constructor(t){if(!t)throw new Error("invalid node");this._node=t,t.classList.add("observablehq")}pending(){const{_node:t}=this;t.classList.remove("observablehq--error"),t.classList.add("observablehq--running")}fulfilled(t,n){const{_node:s}=this;if((!mh(t)||t.parentNode&&t.parentNode!==s)&&(t=te(t,!1,s.firstChild&&s.firstChild.classList&&s.firstChild.classList.contains("observablehq--expanded"),n),t.classList.add("observablehq--inspect")),s.classList.remove("observablehq--running","observablehq--error"),s.firstChild!==t)if(s.firstChild){for(;s.lastChild!==s.firstChild;)s.removeChild(s.lastChild);s.replaceChild(t,s.firstChild)}else s.appendChild(t);Ft(s,"update")}rejected(t,n){const{_node:s}=this;for(s.classList.remove("observablehq--running"),s.classList.add("observablehq--error");s.lastChild;)s.removeChild(s.lastChild);var i=document.createElement("div");i.className="observablehq--inspect",n&&i.appendChild(ce(n)),i.appendChild(document.createTextNode((t+"").replace(fh,""))),s.appendChild(i),Ft(s,"error",{error:t})}}kn.into=function(e){if(typeof e=="string"&&(e=document.querySelector(e),e==null))throw new Error("container not found");return function(){return new kn(e.appendChild(document.createElement("div")))}};function mh(e){return(e instanceof Element||e instanceof Text)&&e instanceof e.constructor}var Es={},un={},ln=34,Xe=10,cn=13;function vi(e){return new Function("d","return {"+e.map(function(t,n){return JSON.stringify(t)+": d["+n+'] || ""'}).join(",")+"}")}function vh(e,t){var n=vi(e);return function(s,i){return t(n(s),i,e)}}function As(e){var t=Object.create(null),n=[];return e.forEach(function(s){for(var i in s)i in t||n.push(t[i]=i)}),n}function ee(e,t){var n=e+"",s=n.length;return s9999?"+"+ee(e,6):ee(e,4)}function bh(e){var t=e.getUTCHours(),n=e.getUTCMinutes(),s=e.getUTCSeconds(),i=e.getUTCMilliseconds();return isNaN(e)?"Invalid Date":gh(e.getUTCFullYear())+"-"+ee(e.getUTCMonth()+1,2)+"-"+ee(e.getUTCDate(),2)+(i?"T"+ee(t,2)+":"+ee(n,2)+":"+ee(s,2)+"."+ee(i,3)+"Z":s?"T"+ee(t,2)+":"+ee(n,2)+":"+ee(s,2)+"Z":n||t?"T"+ee(t,2)+":"+ee(n,2)+"Z":"")}function gi(e){var t=new RegExp('["'+e+` +\r]`),n=e.charCodeAt(0);function s(p,m){var b,w,N=i(p,function(I,x){if(b)return b(I,x-1);w=I,b=m?vh(I,m):vi(I)});return N.columns=w||[],N}function i(p,m){var b=[],w=p.length,N=0,I=0,x,C=w<=0,D=!1;p.charCodeAt(w-1)===Xe&&--w,p.charCodeAt(w-1)===cn&&--w;function R(){if(C)return un;if(D)return D=!1,Es;var _t,wt=N,Je;if(p.charCodeAt(wt)===ln){for(;N++=w?C=!0:(Je=p.charCodeAt(N++))===Xe?D=!0:Je===cn&&(D=!0,p.charCodeAt(N)===Xe&&++N),p.slice(wt+1,_t-1).replace(/""/g,'"')}for(;N{if(!o.ok)throw new ze("unable to load package.json");return o.redirected&&!xt.has(o.url)&&xt.set(o.url,a),o.json()})),a}return async function(r,a){if(r.startsWith(e)&&(r=r.substring(e.length)),/^(\w+:)|\/\//i.test(r))return r;if(/^[.]{0,2}\//i.test(r))return new URL(r,a??location).href;if(!r.length||/^[\s._]/.test(r)||/\s$/.test(r))throw new ze("illegal name");const o=Is(r);if(!o)return`${e}${r}`;if(!o.version&&a!=null&&a.startsWith(e)){const c=await s(Is(a.substring(e.length)));o.version=c.dependencies&&c.dependencies[o.name]||c.peerDependencies&&c.peerDependencies[o.name]}if(o.path&&!Ps.test(o.path)&&(o.path+=".js"),o.path&&o.version&&Hh.test(o.version))return`${e}${o.name}@${o.version}/${o.path}`;const u=await s(o);return`${e}${u.name}@${u.version}/${o.path||n(u)||"index.js"}`}}var zh=Qt(wi());function Qt(e){const t=new Map,n=i(null);function s(o){if(typeof o!="string")return o;let u=t.get(o);return u||t.set(o,u=new Promise((c,h)=>{const p=document.createElement("script");p.onload=()=>{try{c(dt.pop()(i(o)))}catch{h(new ze("invalid module"))}p.remove()},p.onerror=()=>{h(new ze("unable to load module")),p.remove()},p.async=!0,p.src=o,window.define=xi,document.head.appendChild(p)})),u}function i(o){return u=>Promise.resolve(e(u,o)).then(s)}function r(o){return Qt((u,c)=>u in o&&(u=o[u],c=null,typeof u!="string")?u:e(u,c))}function a(o){return arguments.length>1?Promise.all(Sn.call(arguments,n)).then(Gh):n(o)}return a.alias=r,a.resolve=e,a}function Gh(e){const t={};for(const n of e)for(const s in n)Uh.call(n,s)&&(n[s]==null?Object.defineProperty(t,s,{get:Wh(n,s)}):t[s]=n[s]);return t}function Wh(e,t){return()=>e[t]}function Kh(e){return e=e+"",e==="exports"||e==="module"}function xi(e,t,n){const s=arguments.length;s<2?(n=e,t=[]):s<3&&(n=t,t=typeof e=="string"?[]:e),dt.push(jh.call(t,Kh)?i=>{const r={},a={exports:r};return Promise.all(Sn.call(t,o=>(o=o+"",o==="exports"?r:o==="module"?a:i(o)))).then(o=>(n.apply(null,o),a.exports))}:i=>Promise.all(Sn.call(t,i)).then(r=>typeof n=="function"?n.apply(null,r):n))}xi.amd={};const Pe="https://cdn.observableusercontent.com/npm/";let Ne=zh;function Yh(e){Ne=e}function Qh(e){return e==null?Ne:Qt(e)}async function Ei(e){const[t,n]=await Promise.all([e(Cs.resolve()),e.resolve(Cs.resolve("dist/"))]);return t({locateFile:s=>`${n}${s}`})}class ft{constructor(t){Object.defineProperties(this,{_db:{value:t}})}static async open(t){const[n,s]=await Promise.all([Ei(Ne),Promise.resolve(t).then(Pn)]);return new ft(new n.Database(s))}async query(t,n){return await Zh(this._db,t,n)}async queryRow(t,n){return(await this.query(t,n))[0]||null}async explain(t,n){const s=await this.query(`EXPLAIN QUERY PLAN ${t}`,n);return Se("pre",{className:"observablehq--inspect"},[pn(s.map(i=>i.detail).join(` +`))])}async describeTables({schema:t}={}){return this.query(`SELECT NULLIF(schema, 'main') AS schema, name FROM pragma_table_list() WHERE type = 'table'${t==null?"":" AND schema = ?"} AND name NOT LIKE 'sqlite_%' ORDER BY schema, name`,t==null?[]:[t])}async describeColumns({schema:t,table:n}={}){if(n==null)throw new Error("missing table");const s=await this.query(`SELECT name, type, "notnull" FROM pragma_table_info(?${t==null?"":", ?"}) ORDER BY cid`,t==null?[n]:[n,t]);if(!s.length)throw new Error(`table not found: ${n}`);return s.map(({name:i,type:r,notnull:a})=>({name:i,type:Jh(r),databaseType:r,nullable:!a}))}async describe(t){const n=await(t===void 0?this.query("SELECT name FROM sqlite_master WHERE type = 'table'"):this.query("SELECT * FROM pragma_table_info(?)",[t]));if(!n.length)throw new Error("Not found");const{columns:s}=n;return Se("table",{value:n},[Se("thead",[Se("tr",s.map(i=>Se("th",[pn(i)])))]),Se("tbody",n.map(i=>Se("tr",s.map(r=>Se("td",[pn(i[r])])))))])}async sql(){return this.query(...this.queryTag.apply(this,arguments))}queryTag(t,...n){return[t.join("?"),n]}}Object.defineProperty(ft.prototype,"dialect",{value:"sqlite"});function Jh(e){switch(e){case"NULL":return"null";case"INT":case"INTEGER":case"TINYINT":case"SMALLINT":case"MEDIUMINT":case"BIGINT":case"UNSIGNED BIG INT":case"INT2":case"INT8":return"integer";case"TEXT":case"CLOB":return"string";case"REAL":case"DOUBLE":case"DOUBLE PRECISION":case"FLOAT":case"NUMERIC":return"number";case"BLOB":return"buffer";case"DATE":case"DATETIME":return"string";default:return/^(?:(?:(?:VARYING|NATIVE) )?CHARACTER|(?:N|VAR|NVAR)CHAR)\(/.test(e)?"string":/^(?:DECIMAL|NUMERIC)\(/.test(e)?"number":"other"}}function Pn(e){return typeof e=="string"?fetch(e).then(Pn):e instanceof Response||e instanceof Blob?e.arrayBuffer().then(Pn):e instanceof ArrayBuffer?new Uint8Array(e):e}async function Zh(e,t,n){const[s]=await e.exec(t,n);if(!s)return[];const{columns:i,values:r}=s,a=r.map(o=>Object.fromEntries(o.map((u,c)=>[i[c],u])));return a.columns=i,a}function Se(e,t,n){arguments.length===2&&(n=t,t=void 0);const s=document.createElement(e);if(t!==void 0)for(const i in t)s[i]=t[i];if(n!==void 0)for(const i of n)s.appendChild(i);return s}function pn(e){return document.createTextNode(e)}function dn(e,t){return e==null||t==null?NaN:et?1:e>=t?0:NaN}function Xh(e,t=dn){let n,s=!1;if(t.length===1){let i;for(const r of e){const a=t(r);(s?dn(a,i)>0:dn(a,a)===0)&&(n=r,i=a,s=!0)}}else for(const i of e)(s?t(i,n)>0:t(i,i)===0)&&(n=i,s=!0);return n}function ep(e){if(typeof e[Symbol.iterator]!="function")throw new TypeError("values is not iterable");return Array.from(e).reverse()}function Yn(e){return e&&typeof e.toArrowBuffer=="function"}function $t(e){return e&&typeof e.getChild=="function"&&typeof e.toArray=="function"&&e.schema&&Array.isArray(e.schema.fields)}function tp(e){return e.schema.fields.map(np)}function np(e){return{name:e.name,type:sp(e.type),nullable:e.nullable,databaseType:String(e.type)}}function sp(e){switch(e.typeId){case 2:return"integer";case 3:case 7:return"number";case 4:case 15:return"buffer";case 5:return"string";case 6:return"boolean";case 8:case 9:case 10:return"date";case 12:case 16:return"array";case 13:case 14:return"object";case 11:case 17:default:return"other"}}async function Qn(){return await at(()=>import(`${Pe}${_i.resolve()}`),[])}let fn;class mt{constructor(t){Object.defineProperties(this,{_db:{value:t}})}async queryStream(t,n){const s=await this._db.connect();let i,r;try{if((n==null?void 0:n.length)>0?i=await(await s.prepare(t)).send(...n):i=await s.send(t),r=await i.next(),r.done)throw new Error("missing first batch")}catch(a){throw await s.close(),a}return{schema:tp(r.value),async*readRows(){try{for(;!r.done;)yield r.value.toArray(),r=await i.next()}finally{await s.close()}}}}async query(t,n){const s=await this.queryStream(t,n),i=[];for await(const r of s.readRows())for(const a of r)i.push(a);return i.schema=s.schema,i}async queryRow(t,n){const i=(await this.queryStream(t,n)).readRows();try{const{done:r,value:a}=await i.next();return r||!a.length?null:a[0]}finally{await i.return()}}async sql(t,...n){return await this.query(t.join("?"),n)}queryTag(t,...n){return[t.join("?"),n]}escape(t){return`"${t}"`}async describeTables(){return(await this.query("SHOW TABLES")).map(({name:n})=>({name:n}))}async describeColumns({table:t}={}){return(await this.query(`DESCRIBE ${this.escape(t)}`)).map(({column_name:s,column_type:i,null:r})=>({name:s,type:up(i),nullable:r!=="NO",databaseType:i}))}static async of(t={},n={}){var i,r;const s=await op();return((i=n.query)==null?void 0:i.castTimestampToDate)===void 0&&(n={...n,query:{...n.query,castTimestampToDate:!0}}),((r=n.query)==null?void 0:r.castBigIntToDouble)===void 0&&(n={...n,query:{...n.query,castBigIntToDouble:!0}}),await s.open(n),await Promise.all(Object.entries(t).map(async([a,o])=>{if(o instanceof Q)await Ts(s,a,o);else if($t(o))await Bt(s,a,o);else if(Array.isArray(o))await Ns(s,a,o);else if(Yn(o))await rp(s,a,o);else if("data"in o){const{data:u,...c}=o;$t(u)?await Bt(s,a,u,c):await Ns(s,a,u,c)}else if("file"in o){const{file:u,...c}=o;await Ts(s,a,u,c)}else throw new Error(`invalid source: ${o}`)})),new mt(s)}}Object.defineProperty(mt.prototype,"dialect",{value:"duckdb"});async function Ts(e,t,n,s){const i=await n.url();if(i.startsWith("blob:")){const a=await n.arrayBuffer();await e.registerFileBuffer(n.name,new Uint8Array(a))}else await e.registerFileURL(n.name,new URL(i,location).href,4);const r=await e.connect();try{switch(n.mimeType){case"text/csv":case"text/tab-separated-values":return await r.insertCSVFromPath(n.name,{name:t,schema:"main",...s}).catch(async a=>{if(a.toString().includes("Could not convert"))return await ip(r,n,t);throw a});case"application/json":return await r.insertJSONFromPath(n.name,{name:t,schema:"main",...s});default:if(/\.arrow$/i.test(n.name)){const a=new Uint8Array(await n.arrayBuffer());return await r.insertArrowFromIPCStream(a,{name:t,schema:"main",...s})}if(/\.parquet$/i.test(n.name))return await r.query(`CREATE VIEW '${t}' AS SELECT * FROM parquet_scan('${n.name}')`);throw new Error(`unknown file type: ${n.mimeType}`)}}finally{await r.close()}}async function ip(e,t,n){return await(await e.prepare(`CREATE TABLE '${n}' AS SELECT * FROM read_csv_auto(?, ALL_VARCHAR=TRUE)`)).send(t.name)}async function Bt(e,t,n,s){const i=await e.connect();try{await i.insertArrowTable(n,{name:t,schema:"main",...s})}finally{await i.close()}}async function rp(e,t,n){const i=(await Qn()).tableFromIPC(n.toArrowBuffer());return await Bt(e,t,i)}async function Ns(e,t,n,s){const r=(await Qn()).tableFromJSON(n);return await Bt(e,t,r,s)}async function ap(){const e=await at(()=>import(`${Pe}${et.resolve()}`),[]),t=await e.selectBundle({mvp:{mainModule:`${Pe}${et.resolve("dist/duckdb-mvp.wasm")}`,mainWorker:`${Pe}${et.resolve("dist/duckdb-browser-mvp.worker.js")}`},eh:{mainModule:`${Pe}${et.resolve("dist/duckdb-eh.wasm")}`,mainWorker:`${Pe}${et.resolve("dist/duckdb-browser-eh.worker.js")}`}}),n=new e.ConsoleLogger;return{module:e,bundle:t,logger:n}}async function op(){fn===void 0&&(fn=ap());const{module:e,bundle:t,logger:n}=await fn,s=await e.createWorker(t.mainWorker),i=new e.AsyncDuckDB(n,s);return await i.instantiate(t.mainModule),i}function up(e){switch(e){case"BIGINT":case"HUGEINT":case"UBIGINT":return"bigint";case"DOUBLE":case"REAL":case"FLOAT":return"number";case"INTEGER":case"SMALLINT":case"TINYINT":case"USMALLINT":case"UINTEGER":case"UTINYINT":return"integer";case"BOOLEAN":return"boolean";case"DATE":case"TIMESTAMP":case"TIMESTAMP WITH TIME ZONE":return"date";case"VARCHAR":case"UUID":return"string";default:return/^DECIMAL\(/.test(e)?"integer":"other"}}const Jn=20;function lp(e,t){return e&&(typeof e.sql=="function"||typeof e.queryTag=="function"&&(typeof e.query=="function"||typeof e.queryStream=="function"))&&t!=="table"&&e!==Ti}function Jt(e){return Array.isArray(e)&&(Ai(e.schema)||ki(e.columns)||cp(e)||Si(e)||Pi(e))||Ii(e)}function cp(e){const t=Math.min(Jn,e.length);for(let n=0;n0&&hp(e[0])}function hp(e){for(const t in e)return!0;return!1}function Ai(e){return Array.isArray(e)&&e.every(pp)}function ki(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function pp(e){return e&&typeof e.name=="string"&&typeof e.type=="string"}function Ci(e){return Ii(e)||Si(e)||Pi(e)}function Si(e){const t=Math.min(Jn,e.length);if(!(t>0))return!1;let n,s=!1;for(let i=0;i0))return!1;let n=!1;for(let s=0;s{if(e=await dp(await e,s),lp(e))return Fs(e,bp(t,e),n);if(Jt(e))return Vp(e,t);throw e?new Error("invalid data source"):new Error("missing data source")},{sql(e,t,n){return async function(){return Fs(await fp(await e,n),arguments,t)}}});function Ni(e){const t=new WeakMap;return(n,s)=>{if(!n||typeof n!="object")throw new Error("invalid data source");let i=t.get(n);return(!i||Jt(n)&&n.length!==i._numRows)&&(i=e(n,s),i._numRows=n.length,t.set(n,i)),i}}const dp=Ni(async(e,t)=>{if(e instanceof Q){switch(e.mimeType){case"text/csv":return e.csv();case"text/tab-separated-values":return e.tsv();case"application/json":return e.json();case"application/x-sqlite3":return e.sqlite()}if(/\.(arrow|parquet)$/i.test(e.name))return qe(e,t);throw new Error(`unsupported file type: ${e.mimeType}`)}return $t(e)||Yn(e)?qe(e,t):Jt(e)&&Ci(e)?Array.from(e,n=>({value:n})):e}),fp=Ni(async(e,t)=>{if(e instanceof Q){switch(e.mimeType){case"text/csv":case"text/tab-separated-values":case"application/json":return qe(e,t);case"application/x-sqlite3":return e.sqlite()}if(/\.(arrow|parquet)$/i.test(e.name))return qe(e,t);throw new Error(`unsupported file type: ${e.mimeType}`)}return Jt(e)?qe(await mp(e,t),t):$t(e)||Yn(e)?qe(e,t):e});async function mp(e,t){const n=await Qn();return Ci(e)?n.tableFromArrays({[t]:e}):n.tableFromJSON(e)}function qe(e,t=e instanceof Q?vp(e):"__table"){return mt.of({[t]:e})}function vp(e){return e.name.replace(/@\d+(?=\.|$)/,"").replace(/\.\w+$/,"")}async function Fs(e,t,n){if(!e)throw new Error("missing data source");if(typeof e.queryTag=="function"){const s=new AbortController,i={signal:s.signal};if(n.then(()=>s.abort("invalidated")),typeof e.queryStream=="function")return gp(e.queryStream(...e.queryTag.apply(e,t),i));if(typeof e.query=="function")return e.query(...e.queryTag.apply(e,t),i)}if(typeof e.sql=="function")return e.sql.apply(e,t);throw new Error("source does not implement query, queryStream, or sql")}async function*gp(e){let t=performance.now();const n=await e,s=[];s.done=!1,s.error=null,s.schema=n.schema;try{for await(const i of n.readRows()){performance.now()-t>150&&s.length>0&&(yield s,t=performance.now());for(const r of i)s.push(r)}s.done=!0,yield s}catch(i){s.error=i,yield s}}function bp(e,t){var p;const n=typeof t.escape=="function"?t.escape:m=>m,{select:s,from:i,filter:r,sort:a,slice:o}=e;if(!i.table)throw new Error("missing from table");if(s.columns&&s.columns.length===0)throw new Error("at least one column must be selected");const u=new Map((p=e.names)==null?void 0:p.map(({column:m,name:b})=>[m,b])),h=[[`SELECT ${s.columns?s.columns.map(m=>{const b=u.get(m);return b?`${n(m)} AS ${n(b)}`:n(m)}).join(", "):"*"} FROM ${yp(i.table,n)}`]];for(let m=0;m ",n);break;case"gt":W(" > ",n);break;case"lt":W(" < ",n);break;case"gte":W(" >= ",n);break;case"lte":W(" <= ",n);break;default:throw new Error("Invalid filter operation")}Ue(t[1],n,s);return}switch(Ue(t[0],n,s),e){case"in":W(" IN (",n);break;case"nin":W(" NOT IN (",n);break;default:throw new Error("Invalid filter operation")}wp(t.slice(1),n),W(")",n)}function Ue(e,t,n){e.type==="column"?W(n(e.value),t):(t.push(e.value),t[0].push(""))}function wp(e,t){let n=!0;for(const s of e)n?n=!1:W(",",t),t.push(s.value),t[0].push("")}function xp(e){return{...e,value:`%${e.value}%`}}function Fi(e,t){return(e==null||!(e>=e))-(t==null||!(t>=t))}function Ep(e,t){return Fi(e,t)||(et?1:0)}function Ap(e,t){return Fi(e,t)||(e>t?-1:etypeof e=="number"&&!Number.isNaN(e),Cp=e=>Number.isInteger(e)&&!Number.isNaN(e),Sp=e=>typeof e=="string",Pp=e=>typeof e=="boolean",Ip=e=>typeof e=="bigint",Tp=e=>e instanceof Date&&!isNaN(e),Np=e=>e instanceof ArrayBuffer,Fp=e=>Array.isArray(e),Lp=e=>typeof e=="object"&&e!==null,Dp=e=>e!=null;function Ds(e){switch(e){case"string":return Sp;case"bigint":return Ip;case"boolean":return Pp;case"number":return kp;case"integer":return Cp;case"date":return Tp;case"buffer":return Np;case"array":return Fp;case"object":return Lp;case"other":default:return Dp}}const Li=/^(([-+]\d{2})?\d{4}(-\d{2}(-\d{2}))|(\d{1,2})\/(\d{1,2})\/(\d{2,4}))([T ]\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/;function $p(e,t){switch(t){case"string":return typeof e=="string"||e==null?e:String(e);case"boolean":if(typeof e=="string"){const n=e.trim().toLowerCase();return n==="true"?!0:n==="false"?!1:null}return typeof e=="boolean"||e==null?e:!!e;case"bigint":return typeof e=="bigint"||e==null?e:Number.isInteger(typeof e=="string"&&!e.trim()?NaN:+e)?BigInt(e):void 0;case"integer":case"number":return typeof e=="number"?e:e==null||typeof e=="string"&&!e.trim()?NaN:Number(e);case"date":{if(e instanceof Date||e==null)return e;if(typeof e=="number")return new Date(e);const n=String(e).trim();return typeof e=="string"&&!n?null:new Date(Li.test(n)?n:NaN)}case"array":case"object":case"buffer":case"other":return e;default:throw new Error(`Unable to coerce to type: ${t}`)}}function Bp(e){const{columns:t}=e;let{schema:n}=e;return Ai(n)?{schema:n,inferred:!1}:(n=Di(e,ki(t)?t:void 0),{schema:n,inferred:!0})}function $s(e,t){const n=e;let{schema:s,inferred:i}=Bp(e);const r=new Map(s.map(({name:a,type:o})=>[a,o]));if(t.types){for(const{name:a,type:o}of t.types){r.set(a,o),s===n.schema&&(s=s.slice());const u=s.findIndex(c=>c.name===a);u>-1&&(s[u]={...s[u],type:o})}e=e.map(a=>In(a,r,s))}else i&&(e=e.map(a=>In(a,r,s)));return{source:e,schema:s}}function Bs(e,t){if(!t.names)return e;const n=new Map(t.names.map(s=>[s.column,s]));return e.map(s=>Object.fromEntries(Object.keys(s).map(i=>{const r=n.get(i);return[(r==null?void 0:r.name)??i,s[i]]})))}function Vp(e,t){const n=new Map,s=e,i=$s(e,t);e=i.source;let r=i.schema;if(t.derive){const c=[];t.derive.map(({name:p,value:m})=>{let b=[];Bs(e,t).map((w,N)=>{let I;try{I=m(w)}catch(x){b.push({index:N,error:x}),I=void 0}c[N]?c[N]={...c[N],[p]:I}:c.push({[p]:I})}),b.length&&n.set(p,b)});const h=$s(c,t);e=e.map((p,m)=>({...p,...h.source[m]})),r=[...r,...h.schema]}for(const{type:c,operands:h}of t.filter){const[{value:p}]=h,m=h.slice(1).map(({value:b})=>b);switch(c){case"v":{const[b]=m,w=Ds(b);e=e.filter(N=>w(N[p]));break}case"nv":{const[b]=m,w=Ds(b);e=e.filter(N=>!w(N[p]));break}case"eq":{const[b]=m;if(b instanceof Date){const w=+b;e=e.filter(N=>+N[p]===w)}else e=e.filter(w=>w[p]===b);break}case"ne":{const[b]=m;e=e.filter(w=>w[p]!==b);break}case"c":{const[b]=m;e=e.filter(w=>typeof w[p]=="string"&&w[p].includes(b));break}case"nc":{const[b]=m;e=e.filter(w=>typeof w[p]=="string"&&!w[p].includes(b));break}case"in":{const b=new Set(m);e=e.filter(w=>b.has(w[p]));break}case"nin":{const b=new Set(m);e=e.filter(w=>!b.has(w[p]));break}case"n":{e=e.filter(b=>b[p]==null);break}case"nn":{e=e.filter(b=>b[p]!=null);break}case"lt":{const[b]=m;e=e.filter(w=>w[p]w[p]<=b);break}case"gt":{const[b]=m;e=e.filter(w=>w[p]>b);break}case"gte":{const[b]=m;e=e.filter(w=>w[p]>=b);break}default:throw new Error(`unknown filter type: ${c}`)}}for(const{column:c,direction:h}of ep(t.sort)){const p=h==="desc"?Ap:Ep;e===s&&(e=e.slice()),e.sort((m,b)=>p(m[c],b[c]))}let{from:a,to:o}=t.slice;a=a==null?0:Math.max(0,a),o=o==null?1/0:Math.max(0,o),(a>0||o<1/0)&&(e=e.slice(Math.max(0,a),Math.max(0,o)));let u=r.slice();if(t.select.columns){if(r){const c=new Map(r.map(h=>[h.name,h]));r=t.select.columns.map(h=>c.get(h))}e=e.map(c=>Object.fromEntries(t.select.columns.map(h=>[h,c[h]])))}if(t.names){const c=new Map(t.names.map(h=>[h.column,h]));r&&(r=r.map(h=>{const p=c.get(h.name);return{...h,...p?{name:p.name}:null}})),u&&(u=u.map(h=>{const p=c.get(h.name);return{...h,...p?{name:p.name}:null}})),e=Bs(e,t)}return e!==s&&r&&(e.schema=r),e.fullSchema=u,e.errors=n,e}function In(e,t,n){const s={};for(const i of n){const r=t.get(i.name),a=e[i.name];s[i.name]=r==="raw"?a:$p(a,r)}return s}function Op(){return{boolean:0,integer:0,number:0,date:0,string:0,array:0,object:0,bigint:0,buffer:0,defined:0}}const Mp=["boolean","integer","number","date","bigint","array","object","buffer"];function Rp(e){const t=new Set;for(const n of e)if(n)for(const s in n)Object.prototype.hasOwnProperty.call(n,s)&&t.add(s);return Array.from(t)}function Di(e,t=Rp(e)){const n=[],i=e.slice(0,100);for(const r of t){const a=Op();for(const c of i){let h=c[r];if(h==null)continue;const p=typeof h;if(p!=="string")++a.defined,Array.isArray(h)?++a.array:h instanceof Date?++a.date:h instanceof ArrayBuffer?++a.buffer:p==="number"?(++a.number,Number.isInteger(h)&&++a.integer):p in a&&++a[p];else{if(h=h.trim(),!h)continue;++a.defined,++a.string,/^(true|false)$/i.test(h)?++a.boolean:h&&!isNaN(h)?(++a.number,Number.isInteger(+h)&&++a.integer):Li.test(h)&&++a.date}}const o=Math.max(1,a.defined*.9),u=Xh(Mp,c=>a[c]>=o?a[c]:NaN)??(a.string>=o?"string":"other");n.push({name:r,type:u,inferred:u})}return n}class jp{constructor(t){Object.defineProperties(this,{_:{value:t},sheetNames:{value:t.worksheets.map(n=>n.name),enumerable:!0}})}sheet(t,n){const s=typeof t=="number"?this.sheetNames[t]:this.sheetNames.includes(t+="")?t:null;if(s==null)throw new Error(`Sheet not found: ${t}`);const i=this._.getWorksheet(s);return Up(i,n)}}function Up(e,{range:t,headers:n}={}){let[[s,i],[r,a]]=qp(t,e);const o=n?e._rows[i++]:null;let u=new Set(["#"]);for(let h=s;h<=r;h++){const p=o?Vs(o.findCell(h+1)):null;let m=p&&p+""||Hp(h);for(;u.has(m);)m+="_";u.add(m)}u=new Array(s).concat(Array.from(u));const c=new Array(a-i+1);for(let h=i;h<=a;h++){const p=c[h-i]=Object.create(null,{"#":{value:h+1}}),m=e.getRow(h+1);if(m.hasValues)for(let b=s;b<=r;b++){const w=Vs(m.findCell(b+1));w!=null&&(p[u[b+1]]=w)}}return c.columns=u.filter(()=>!0),c}function Vs(e){if(!e)return;const{value:t}=e;if(t&&typeof t=="object"&&!(t instanceof Date)){if(t.formula||t.sharedFormula)return t.result&&t.result.error?NaN:t.result;if(t.richText)return Os(t);if(t.text){let{text:n}=t;return n.richText&&(n=Os(n)),t.hyperlink&&t.hyperlink!==n?`${t.hyperlink} ${n}`:n}return t}return t}function Os(e){return e.richText.map(t=>t.text).join("")}function qp(e=":",{columnCount:t,rowCount:n}){if(e+="",!e.match(/^[A-Z]*\d*:[A-Z]*\d*$/))throw new Error("Malformed range specifier");const[[s=0,i=0],[r=t-1,a=n-1]]=e.split(":").map(zp);return[[s,i],[r,a]]}function Hp(e){let t="";e++;do t=String.fromCharCode(64+(e%26||26))+t;while(e=Math.floor((e-1)/26));return t}function zp(e){const[,t,n]=e.match(/^([A-Z]*)(\d*)$/);let s=0;if(t)for(let i=0;i[s,i]));return Object.assign(e.map(s=>In(s,n,t)),{schema:t})}async function Ms(e,t,{array:n=!1,typed:s=!1}={}){const i=await e.text(),r=t===" "?n?xh:wh:n?_h:yh;if(s==="auto"&&!n){const a=r(i);return Gp(a,Di(a,a.columns))}return r(i,s&&Eh)}class Zn{constructor(t,n){Object.defineProperty(this,"name",{value:t,enumerable:!0}),n!==void 0&&Object.defineProperty(this,"mimeType",{value:n+"",enumerable:!0})}async blob(){return(await me(this)).blob()}async arrayBuffer(){return(await me(this)).arrayBuffer()}async text(){return(await me(this)).text()}async json(){return(await me(this)).json()}async stream(){return(await me(this)).body}async csv(t){return Ms(this,",",t)}async tsv(t){return Ms(this," ",t)}async image(t){const n=await this.url();return new Promise((s,i)=>{const r=new Image;new URL(n,document.baseURI).origin!==new URL(location).origin&&(r.crossOrigin="anonymous"),Object.assign(r,t),r.onload=()=>s(r),r.onerror=()=>i(new Error(`Unable to load file: ${this.name}`)),r.src=n})}async arrow({version:t=4}={}){switch(t){case 4:{const[n,s]=await Promise.all([Ne(Cn.resolve()),me(this)]);return n.Table.from(s)}case 9:{const[n,s]=await Promise.all([at(()=>import(`${Pe}${Bh.resolve()}`),[]),me(this)]);return n.tableFromIPC(s)}case 11:{const[n,s]=await Promise.all([at(()=>import(`${Pe}${_i.resolve()}`),[]),me(this)]);return n.tableFromIPC(s)}default:throw new Error(`unsupported arrow version: ${t}`)}}async sqlite(){return ft.open(me(this))}async zip(){const[t,n]=await Promise.all([Ne(Nh.resolve()),this.arrayBuffer()]);return new Yp(await t.loadAsync(n))}async xml(t="application/xml"){return new DOMParser().parseFromString(await this.text(),t)}async html(){return this.xml("text/html")}async xlsx(){const[t,n]=await Promise.all([Ne(Mh.resolve()),this.arrayBuffer()]);return new jp(await new t.Workbook().xlsx.load(n))}}class Q extends Zn{constructor(t,n,s){super(n,s),Object.defineProperty(this,"_url",{value:t})}async url(){return await this._url+""}}function Wp(e){throw new Error(`File not found: ${e}`)}function Kp(e){return Object.assign(t=>{const n=e(t+="");if(n==null)throw new Error(`File not found: ${t}`);if(typeof n=="object"&&"url"in n){const{url:s,mimeType:i}=n;return new Q(s,t,i)}return new Q(n,t)},{prototype:Q.prototype})}class Yp{constructor(t){Object.defineProperty(this,"_",{value:t}),this.filenames=Object.keys(t.files).filter(n=>!t.files[n].dir)}file(t){const n=this._.file(t+="");if(!n||n.dir)throw new Error(`file not found: ${t}`);return new Qp(n)}}class Qp extends Zn{constructor(t){super(t.name),Object.defineProperty(this,"_",{value:t}),Object.defineProperty(this,"_url",{writable:!0})}async url(){return this._url||(this._url=this.blob().then(URL.createObjectURL))}async blob(){return this._.async("blob")}async arrayBuffer(){return this._.async("arraybuffer")}async text(){return this._.async("text")}async json(){return JSON.parse(await this.text())}}function Jp(e,t){var n=document.createElement("canvas");return n.width=e,n.height=t,n}function Zp(e,t,n){n==null&&(n=devicePixelRatio);var s=document.createElement("canvas");s.width=e*n,s.height=t*n,s.style.width=e+"px";var i=s.getContext("2d");return i.scale(n,n),i}function Xp(e,t="untitled",n="Save"){const s=document.createElement("a"),i=s.appendChild(document.createElement("button"));i.textContent=n,s.download=t;async function r(){await new Promise(requestAnimationFrame),URL.revokeObjectURL(s.href),s.removeAttribute("href"),i.textContent=n,i.disabled=!1}return s.onclick=async a=>{if(i.disabled=!0,s.href)return r();i.textContent="Saving…";try{const o=await(typeof e=="function"?e():e);i.textContent="Download",s.href=URL.createObjectURL(o)}catch{i.textContent=n}if(a.eventPhase)return r();i.disabled=!1},s}var Et={math:"http://www.w3.org/1998/Math/MathML",svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function ed(e,t){var n=e+="",s=n.indexOf(":"),i;s>=0&&(n=e.slice(0,s))!=="xmlns"&&(e=e.slice(s+1));var r=Et.hasOwnProperty(n)?document.createElementNS(Et[n],e):document.createElement(e);if(t)for(var a in t)n=a,s=n.indexOf(":"),i=t[a],s>=0&&(n=a.slice(0,s))!=="xmlns"&&(a=a.slice(s+1)),Et.hasOwnProperty(n)?r.setAttributeNS(Et[n],a,i):r.setAttribute(a,i);return r}function td(e){var t=document.createElement("input");return e!=null&&(t.type=e),t}function nd(e,t,n){arguments.length===1&&(t=e,e=null);var s=document.createElement("input");return s.min=e=e==null?0:+e,s.max=t=t==null?1:+t,s.step=n==null?"any":n=+n,s.type="range",s}function sd(e){var t=document.createElement("select");return Array.prototype.forEach.call(e,function(n){var s=document.createElement("option");s.value=s.textContent=n,t.appendChild(s)}),t}function id(e,t){var n=document.createElementNS("http://www.w3.org/2000/svg","svg");return n.setAttribute("viewBox",[0,0,e,t]),n.setAttribute("width",e),n.setAttribute("height",t),n}function rd(e){return document.createTextNode(e)}var ad=0;function $i(e){return new Bi("O-"+(e==null?"":e+"-")+ ++ad)}function Bi(e){this.id=e,this.href=new URL(`#${e}`,location)+""}Bi.prototype.toString=function(){return"url("+this.href+")"};const od=Object.freeze(Object.defineProperty({__proto__:null,canvas:Jp,context2d:Zp,download:Xp,element:ed,input:td,range:nd,select:sd,svg:id,text:rd,uid:$i},Symbol.toStringTag,{value:"Module"}));function ud(e){return new Promise(function(t,n){var s=new FileReader;s.onload=function(){t(s.result)},s.onerror=n,s.readAsArrayBuffer(e)})}function ld(e){return new Promise(function(t,n){var s=new FileReader;s.onload=function(){t(s.result)},s.onerror=n,s.readAsText(e)})}function cd(e){return new Promise(function(t,n){var s=new FileReader;s.onload=function(){t(s.result)},s.onerror=n,s.readAsDataURL(e)})}const hd=Object.freeze(Object.defineProperty({__proto__:null,buffer:ud,text:ld,url:cd},Symbol.toStringTag,{value:"Module"}));function Xn(){return this}function Vi(e,t){let n=!1;if(typeof t!="function")throw new Error("dispose is not a function");return{[Symbol.iterator]:Xn,next:()=>n?{done:!0}:(n=!0,{done:!1,value:e}),return:()=>(n=!0,t(e),{done:!0}),throw:()=>({done:n=!0})}}function*pd(e,t){for(var n,s=-1;!(n=e.next()).done;)t(n.value,++s)&&(yield n.value)}function Zt(e){let t=!1,n,s;const i=e(r);if(i!=null&&typeof i!="function")throw new Error(typeof i.then=="function"?"async initializers are not supported":"initializer returned something, but not a dispose function");function r(o){return s?(s(o),s=null):t=!0,n=o}function a(){return{done:!1,value:t?(t=!1,Promise.resolve(n)):new Promise(o=>s=o)}}return{[Symbol.iterator]:Xn,throw:()=>({done:!0}),return:()=>(i!=null&&i(),{done:!0}),next:a}}function dd(e){return Zt(function(t){var n=fd(e),s=Rs(e);function i(){t(Rs(e))}return e.addEventListener(n,i),s!==void 0&&t(s),function(){e.removeEventListener(n,i)}})}function Rs(e){switch(e.type){case"range":case"number":return e.valueAsNumber;case"date":return e.valueAsDate;case"checkbox":return e.checked;case"file":return e.multiple?e.files:e.files[0];case"select-multiple":return Array.from(e.selectedOptions,t=>t.value);default:return e.value}}function fd(e){switch(e.type){case"button":case"submit":case"checkbox":return"click";case"file":return"change";default:return"input"}}function*md(e,t){for(var n,s=-1;!(n=e.next()).done;)yield t(n.value,++s)}function vd(e){let t;const n=[],s=e(i);if(s!=null&&typeof s!="function")throw new Error(typeof s.then=="function"?"async initializers are not supported":"initializer returned something, but not a dispose function");function i(a){return n.push(a),t&&(t(n.shift()),t=null),a}function r(){return{done:!1,value:n.length?Promise.resolve(n.shift()):new Promise(a=>t=a)}}return{[Symbol.iterator]:Xn,throw:()=>({done:!0}),return:()=>(s!=null&&s(),{done:!0}),next:r}}function*gd(e,t,n){e=+e,t=+t,n=(i=arguments.length)<2?(t=e,e=0,1):i<3?1:+n;for(var s=-1,i=Math.max(0,Math.ceil((t-e)/n))|0;++s{n.terminate(),URL.revokeObjectURL(t)})}const _d=Object.freeze(Object.defineProperty({__proto__:null,disposable:Vi,filter:pd,input:dd,map:md,observe:Zt,queue:vd,range:gd,valueAt:bd,worker:yd},Symbol.toStringTag,{value:"Module"}));function es(e,t){return function(n){var s=n[0],i=[],r,a=null,o,u,c,h,p,m,b,w=-1;for(h=1,p=arguments.length;h";else if(Array.isArray(r)){for(m=0,b=r.length;m"),a.appendChild(o)):(a=null,s+=o);a=null}else s+=r;s+=n[h]}if(a=e(s),++w>0){for(u=new Array(w),c=document.createTreeWalker(a,NodeFilter.SHOW_COMMENT,null,!1);c.nextNode();)o=c.currentNode,/^o:/.test(o.nodeValue)&&(u[+o.nodeValue.slice(2)]=o);for(h=0;h0&&e(hn.resolve()).then(function(r){i.forEach(function(a){function o(){r.highlightBlock(a),a.parentNode.classList.add("observablehq--md-pre")}r.getLanguage(a.className)?o():e(hn.resolve("async-languages/index.js")).then(u=>{if(u.has(a.className))return e(hn.resolve("async-languages/"+u.get(a.className))).then(c=>{r.registerLanguage(a.className,c)})}).then(o,o)})}),s},function(){return document.createElement("div")})})}async function Ad(e){const t=await e(Rh.resolve());return t.initialize({securityLevel:"loose",theme:"neutral"}),function(){const s=document.createElement("div");return s.innerHTML=t.render($i().id,String.raw.apply(String,arguments)),s.removeChild(s.firstChild)}}function kd(e){let t;Object.defineProperties(this,{generator:{value:Zt(n=>void(t=n))},value:{get:()=>e,set:n=>t(e=n)}}),e!==void 0&&t(e)}function*Cd(){for(;;)yield Date.now()}function Sd(e,t){return new Promise(function(n){setTimeout(function(){n(t)},e)})}var Tn=new Map;function Pd(e,t){var n=new Promise(function(s){Tn.delete(t);var i=t-e;if(!(i>0))throw new Error("invalid time");if(i>2147483647)throw new Error("too long to wait");setTimeout(s,i)});return Tn.set(t,n),n}function Oi(e,t){var n;return(n=Tn.get(e=+e))?n.then(()=>t):(n=Date.now())>=e?Promise.resolve(t):Pd(n,e).then(()=>t)}function Id(e,t){return Oi(Math.ceil((Date.now()+1)/e)*e,t)}const Td=Object.freeze(Object.defineProperty({__proto__:null,delay:Sd,tick:Id,when:Oi},Symbol.toStringTag,{value:"Module"}));function Nd(e,t){if(/^(\w+:)|\/\//i.test(e))return e;if(/^[.]{0,2}\//i.test(e))return new URL(e,t??location).href;if(!e.length||/^[\s._]/.test(e)||/\s$/.test(e))throw new Error("illegal name");return"https://unpkg.com/"+e}const Fd=es(function(e){var t=document.createElementNS("http://www.w3.org/2000/svg","g");return t.innerHTML=e.trim(),t},function(){return document.createElementNS("http://www.w3.org/2000/svg","g")});var Ld=String.raw;function Dd(e){return new Promise(function(t,n){var s=document.createElement("link");s.rel="stylesheet",s.href=e,s.onerror=n,s.onload=t,document.head.appendChild(s)})}function $d(e){return Promise.all([e(ks.resolve()),e.resolve(ks.resolve("dist/katex.min.css")).then(Dd)]).then(function(t){var n=t[0],s=i();function i(r){return function(){var a=document.createElement("div");return n.render(Ld.apply(String,arguments),a,r),a.removeChild(a.firstChild)}}return s.options=i,s.block=i({displayMode:!0}),s})}async function Bd(e){const[t,n,s]=await Promise.all([Lh,Dh,$h].map(i=>e(i.resolve())));return s.register(t,n)}function Vd(){return Zt(function(e){var t=e(document.body.clientWidth);function n(){var s=document.body.clientWidth;s!==t&&e(t=s)}return window.addEventListener("resize",n),function(){window.removeEventListener("resize",n)}})}const Mi=Object.assign(Object.defineProperties(function(t){const n=Qh(t);Object.defineProperties(this,Od({FileAttachment:()=>Wp,Mutable:()=>kd,now:Cd,width:Vd,dot:()=>n(Ph.resolve()),htl:()=>n(Th.resolve()),html:()=>wd,md:()=>Ed(n),svg:()=>Fd,tex:()=>$d(n),_:()=>n(Ih.resolve()),aq:()=>n.alias({"apache-arrow":Cn.resolve()})(Vh.resolve()),Arrow:()=>n(Cn.resolve()),d3:()=>n(kh.resolve()),DuckDBClient:()=>mt,Inputs:()=>n(Ch.resolve()).then(s=>({...s,file:s.fileOf(Zn)})),L:()=>xd(n),mermaid:()=>Ad(n),Plot:()=>n(Sh.resolve()),__query:()=>Ti,require:()=>n,resolve:()=>Nd,SQLite:()=>Ei(n),SQLiteDatabaseClient:()=>ft,topojson:()=>n(Oh.resolve()),vl:()=>Bd(n),aapl:()=>new Q("https://static.observableusercontent.com/files/3ccff97fd2d93da734e76829b2b066eafdaac6a1fafdec0faf6ebc443271cfc109d29e80dd217468fcb2aff1e6bffdc73f356cc48feb657f35378e6abbbb63b9").csv({typed:!0}),alphabet:()=>new Q("https://static.observableusercontent.com/files/75d52e6c3130b1cae83cda89305e17b50f33e7420ef205587a135e8562bcfd22e483cf4fa2fb5df6dff66f9c5d19740be1cfaf47406286e2eb6574b49ffc685d").csv({typed:!0}),cars:()=>new Q("https://static.observableusercontent.com/files/048ec3dfd528110c0665dfa363dd28bc516ffb7247231f3ab25005036717f5c4c232a5efc7bb74bc03037155cb72b1abe85a33d86eb9f1a336196030443be4f6").csv({typed:!0}),citywages:()=>new Q("https://static.observableusercontent.com/files/39837ec5121fcc163131dbc2fe8c1a2e0b3423a5d1e96b5ce371e2ac2e20a290d78b71a4fb08b9fa6a0107776e17fb78af313b8ea70f4cc6648fad68ddf06f7a").csv({typed:!0}),diamonds:()=>new Q("https://static.observableusercontent.com/files/87942b1f5d061a21fa4bb8f2162db44e3ef0f7391301f867ab5ba718b225a63091af20675f0bfe7f922db097b217b377135203a7eab34651e21a8d09f4e37252").csv({typed:!0}),flare:()=>new Q("https://static.observableusercontent.com/files/a6b0d94a7f5828fd133765a934f4c9746d2010e2f342d335923991f31b14120de96b5cb4f160d509d8dc627f0107d7f5b5070d2516f01e4c862b5b4867533000").csv({typed:!0}),industries:()=>new Q("https://static.observableusercontent.com/files/76f13741128340cc88798c0a0b7fa5a2df8370f57554000774ab8ee9ae785ffa2903010cad670d4939af3e9c17e5e18e7e05ed2b38b848ac2fc1a0066aa0005f").csv({typed:!0}),miserables:()=>new Q("https://static.observableusercontent.com/files/31d904f6e21d42d4963ece9c8cc4fbd75efcbdc404bf511bc79906f0a1be68b5a01e935f65123670ed04e35ca8cae3c2b943f82bf8db49c5a67c85cbb58db052").json(),olympians:()=>new Q("https://static.observableusercontent.com/files/31ca24545a0603dce099d10ee89ee5ae72d29fa55e8fc7c9ffb5ded87ac83060d80f1d9e21f4ae8eb04c1e8940b7287d179fe8060d887fb1f055f430e210007c").csv({typed:!0}),penguins:()=>new Q("https://static.observableusercontent.com/files/715db1223e067f00500780077febc6cebbdd90c151d3d78317c802732252052ab0e367039872ab9c77d6ef99e5f55a0724b35ddc898a1c99cb14c31a379af80a").csv({typed:!0}),pizza:()=>new Q("https://static.observableusercontent.com/files/c653108ab176088cacbb338eaf2344c4f5781681702bd6afb55697a3f91b511c6686ff469f3e3a27c75400001a2334dbd39a4499fe46b50a8b3c278b7d2f7fb5").csv({typed:!0}),weather:()=>new Q("https://static.observableusercontent.com/files/693a46b22b33db0f042728700e0c73e836fa13d55446df89120682d55339c6db7cc9e574d3d73f24ecc9bc7eb9ac9a1e7e104a1ee52c00aab1e77eb102913c1f").csv({typed:!0}),DOM:od,Files:hd,Generators:_d,Promises:Td}))},{resolve:{get:()=>Ne.resolve,enumerable:!0,configurable:!0},require:{get:()=>Ne,set:Yh,enumerable:!0,configurable:!0}}),{resolveFrom:wi,requireFrom:Qt});function Od(e){return Object.fromEntries(Object.entries(e).map(Md))}function Md([e,t]){return[e,{value:t,writable:!0,enumerable:!0}]}class be extends Error{constructor(t,n){super(t),this.input=n}}be.prototype.name="RuntimeError";function Rd(e){return e&&typeof e.next=="function"&&typeof e.return=="function"}function Nn(e){return()=>e}function Vt(e){return e}function jd(e){return()=>{throw e}}const Ud=Array.prototype,qd=Ud.map;function we(){}const ts=1,vt=2,Pt=3,ut=Symbol("no-observer");function Le(e,t,n,s){n||(n=ut),Object.defineProperties(this,{_observer:{value:n,writable:!0},_definition:{value:ns,writable:!0},_duplicate:{value:void 0,writable:!0},_duplicates:{value:void 0,writable:!0},_indegree:{value:NaN,writable:!0},_inputs:{value:[],writable:!0},_invalidate:{value:we,writable:!0},_module:{value:t},_name:{value:null,writable:!0},_outputs:{value:new Set,writable:!0},_promise:{value:Promise.resolve(void 0),writable:!0},_reachable:{value:n!==ut,writable:!0},_rejector:{value:Wd(this)},_shadow:{value:Hd(t,s)},_type:{value:e},_value:{value:void 0,writable:!0},_version:{value:0,writable:!0}})}Object.defineProperties(Le.prototype,{_pending:{value:Zd,writable:!0,configurable:!0},_fulfilled:{value:Xd,writable:!0,configurable:!0},_rejected:{value:ef,writable:!0,configurable:!0},_resolve:{value:Yd,writable:!0,configurable:!0},define:{value:Kd,writable:!0,configurable:!0},delete:{value:Jd,writable:!0,configurable:!0},import:{value:Qd,writable:!0,configurable:!0}});function Hd(e,t){return t!=null&&t.shadow?new Map(Object.entries(t.shadow).map(([n,s])=>[n,new Le(vt,e).define([],s)])):null}function zd(e){e._module._runtime._dirty.add(e),e._outputs.add(this)}function Gd(e){e._module._runtime._dirty.add(e),e._outputs.delete(this)}function ns(){throw ns}function ye(){throw ye}function Wd(e){return t=>{throw t===ye?t:t===ns?new be(`${e._name} is not defined`,e._name):t instanceof Error&&t.message?new be(t.message,e._name):new be(`${e._name} could not be resolved`,e._name)}}function js(e){return()=>{throw new be(`${e} is defined more than once`)}}function Kd(e,t,n){switch(arguments.length){case 1:{n=e,e=t=null;break}case 2:{n=t,typeof e=="string"?t=null:(t=e,e=null);break}}return ss.call(this,e==null?null:String(e),t==null?[]:qd.call(t,this._resolve,this),typeof n=="function"?n:Nn(n))}function Yd(e){var t;return((t=this._shadow)==null?void 0:t.get(e))??this._module._resolve(e)}function ss(e,t,n){const s=this._module._scope,i=this._module._runtime;if(this._inputs.forEach(Gd,this),t.forEach(zd,this),this._inputs=t,this._definition=n,this._value=void 0,n===we?i._variables.delete(this):i._variables.add(this),e!==this._name||s.get(e)!==this){let r,a;if(this._name)if(this._outputs.size)s.delete(this._name),a=this._module._resolve(this._name),a._outputs=this._outputs,this._outputs=new Set,a._outputs.forEach(function(o){o._inputs[o._inputs.indexOf(this)]=a},this),a._outputs.forEach(i._updates.add,i._updates),i._dirty.add(a).add(this),s.set(this._name,a);else if((a=s.get(this._name))===this)s.delete(this._name);else if(a._type===Pt)a._duplicates.delete(this),this._duplicate=void 0,a._duplicates.size===1&&(a=a._duplicates.keys().next().value,r=s.get(this._name),a._outputs=r._outputs,r._outputs=new Set,a._outputs.forEach(function(o){o._inputs[o._inputs.indexOf(r)]=a}),a._definition=a._duplicate,a._duplicate=void 0,i._dirty.add(r).add(a),i._updates.add(a),s.set(this._name,a));else throw new Error;if(this._outputs.size)throw new Error;e&&((a=s.get(e))?a._type===Pt?(this._definition=js(e),this._duplicate=n,a._duplicates.add(this)):a._type===vt?(this._outputs=a._outputs,a._outputs=new Set,this._outputs.forEach(function(o){o._inputs[o._inputs.indexOf(a)]=this},this),i._dirty.add(a).add(this),s.set(e,this)):(a._duplicate=a._definition,this._duplicate=n,r=new Le(Pt,this._module),r._name=e,r._definition=this._definition=a._definition=js(e),r._outputs=a._outputs,a._outputs=new Set,r._outputs.forEach(function(o){o._inputs[o._inputs.indexOf(a)]=r}),r._duplicates=new Set([this,a]),i._dirty.add(a).add(r),i._updates.add(a).add(r),s.set(e,r)):s.set(e,this)),this._name=e}return this._version>0&&++this._version,i._updates.add(this),i._compute(),this}function Qd(e,t,n){return arguments.length<3&&(n=t,t=e),ss.call(this,String(t),[n._resolve(String(e))],Vt)}function Jd(){return ss.call(this,null,[],we)}function Zd(){this._observer.pending&&this._observer.pending()}function Xd(e){this._observer.fulfilled&&this._observer.fulfilled(e,this._name)}function ef(e){this._observer.rejected&&this._observer.rejected(e,this._name)}const Ri=Symbol("variable"),ji=Symbol("invalidation"),Ui=Symbol("visibility");function Ot(e,t=[]){Object.defineProperties(this,{_runtime:{value:e},_scope:{value:new Map},_builtins:{value:new Map([["@variable",Ri],["invalidation",ji],["visibility",Ui],...t])},_source:{value:null,writable:!0}})}Object.defineProperties(Ot.prototype,{_resolve:{value:uf,writable:!0,configurable:!0},redefine:{value:tf,writable:!0,configurable:!0},define:{value:nf,writable:!0,configurable:!0},derive:{value:of,writable:!0,configurable:!0},import:{value:sf,writable:!0,configurable:!0},value:{value:af,writable:!0,configurable:!0},variable:{value:rf,writable:!0,configurable:!0},builtin:{value:lf,writable:!0,configurable:!0}});function tf(e){const t=this._scope.get(e);if(!t)throw new be(`${e} is not defined`);if(t._type===Pt)throw new be(`${e} is defined more than once`);return t.define.apply(t,arguments)}function nf(){const e=new Le(ts,this);return e.define.apply(e,arguments)}function sf(){const e=new Le(ts,this);return e.import.apply(e,arguments)}function rf(e,t){return new Le(ts,this,e,t)}async function af(e){let t=this._scope.get(e);if(!t)throw new be(`${e} is not defined`);if(t._observer===ut){t=this.variable(!0).define([e],Vt);try{return await Fn(this._runtime,t)}finally{t.delete()}}else return Fn(this._runtime,t)}async function Fn(e,t){await e._compute();try{return await t._promise}catch(n){if(n===ye)return Fn(e,t);throw n}}function of(e,t){const n=new Map,s=new Set,i=[];function r(o){let u=n.get(o);return u||(u=new Ot(o._runtime,o._builtins),u._source=o,n.set(o,u),i.push([u,o]),s.add(o),u)}const a=r(this);for(const o of e){const{alias:u,name:c}=typeof o=="object"?o:{name:o};a.import(c,u??c,t)}for(const o of s)for(const[u,c]of o._scope)if(c._definition===Vt){if(o===this&&a._scope.has(u))continue;const h=c._inputs[0]._module;h._source&&r(h)}for(const[o,u]of i)for(const[c,h]of u._scope){const p=o._scope.get(c);if(!(p&&p._type!==vt))if(h._definition===Vt){const m=h._inputs[0],b=m._module;o.import(m._name,c,n.get(b)||b)}else o.define(c,h._inputs.map(cf),h._definition)}return a}function uf(e){let t=this._scope.get(e),n;if(!t)if(t=new Le(vt,this),this._builtins.has(e))t.define(e,Nn(this._builtins.get(e)));else if(this._runtime._builtin._scope.has(e))t.import(e,this._runtime._builtin);else{try{n=this._runtime._global(e)}catch(s){return t.define(e,jd(s))}n===void 0?this._scope.set(t._name=e,t):t.define(e,Nn(n))}return t}function lf(e,t){this._builtins.set(e,t)}function cf(e){return e._name}const hf=typeof requestAnimationFrame=="function"?requestAnimationFrame:typeof setImmediate=="function"?setImmediate:e=>setTimeout(e,0);function qi(e=new Mi,t=If){const n=this.module();if(Object.defineProperties(this,{_dirty:{value:new Set},_updates:{value:new Set},_precomputes:{value:[],writable:!0},_computing:{value:null,writable:!0},_init:{value:null,writable:!0},_modules:{value:new Map},_variables:{value:new Set},_disposed:{value:!1,writable:!0},_builtin:{value:n},_global:{value:t}}),e)for(const s in e)new Le(vt,n).define(s,[],e[s])}Object.defineProperties(qi.prototype,{_precompute:{value:ff,writable:!0,configurable:!0},_compute:{value:mf,writable:!0,configurable:!0},_computeSoon:{value:vf,writable:!0,configurable:!0},_computeNow:{value:gf,writable:!0,configurable:!0},dispose:{value:pf,writable:!0,configurable:!0},module:{value:df,writable:!0,configurable:!0},fileAttachments:{value:Kp,writable:!0,configurable:!0}});function pf(){this._computing=Promise.resolve(),this._disposed=!0,this._variables.forEach(e=>{e._invalidate(),e._version=NaN})}function df(e,t=we){let n;if(e===void 0)return(n=this._init)?(this._init=null,n):new Ot(this);if(n=this._modules.get(e),n)return n;this._init=n=new Ot(this),this._modules.set(e,n);try{e(this,t)}finally{this._init=null}return n}function ff(e){this._precomputes.push(e),this._compute()}function mf(){return this._computing||(this._computing=this._computeSoon())}function vf(){return new Promise(hf).then(()=>this._disposed?void 0:this._computeNow())}async function gf(){let e=[],t,n,s=this._precomputes;if(s.length){this._precomputes=[];for(const r of s)r();await bf(3)}t=new Set(this._dirty),t.forEach(function(r){r._inputs.forEach(t.add,t);const a=Pf(r);a>r._reachable?this._updates.add(r):a{});return t}function yf(e){const t=new Set(e._inputs);for(const n of t){if(n===e)return!0;n._inputs.forEach(t.add,t)}return!1}function _f(e){++e._indegree}function wf(e){--e._indegree}function xf(e){return e._promise.catch(e._rejector)}function mn(e){return new Promise(function(t){e._invalidate=t})}function Ef(e,t){let n=typeof IntersectionObserver=="function"&&t._observer&&t._observer._node,s=!n,i=we,r=we,a,o;return n&&(o=new IntersectionObserver(([u])=>(s=u.isIntersecting)&&(a=null,i())),o.observe(n),e.then(()=>(o.disconnect(),o=null,r()))),function(u){return s?Promise.resolve(u):o?(a||(a=new Promise((c,h)=>(i=c,r=h))),a.then(()=>u)):Promise.reject()}}function Af(e){e._invalidate(),e._invalidate=we,e._pending();const t=e._value,n=++e._version;let s=null;const i=e._promise=(e._inputs.length?Promise.all(e._inputs.map(xf)).then(r):new Promise(o=>o(e._definition.call(t)))).then(a);function r(o){if(e._version!==n)throw ye;for(let u=0,c=o.length;u{e._value=o,e._fulfilled(o)},o=>{o===ye||e._version!==n||(e._value=void 0,e._rejected(o))})}function kf(e,t,n){const s=e._module._runtime;let i;function r(u){return new Promise(c=>c(n.next(i))).then(({done:c,value:h})=>c?void 0:Promise.resolve(h).then(u))}function a(){const u=r(c=>{if(e._version!==t)throw ye;return i=c,o(c,u).then(()=>s._precompute(a)),e._fulfilled(c),c});u.catch(c=>{c===ye||e._version!==t||(o(void 0,u),e._rejected(c))})}function o(u,c){return e._value=u,e._promise=c,e._outputs.forEach(s._updates.add,s._updates),s._compute()}return r(u=>{if(e._version!==t)throw ye;return i=u,s._precompute(a),u})}function Cf(e,t){e._invalidate(),e._invalidate=we,e._pending(),++e._version,e._indegree=NaN,(e._promise=Promise.reject(t)).catch(we),e._value=void 0,e._rejected(t)}function Sf(e){return function(){e.return()}}function Pf(e){if(e._observer!==ut)return!0;const t=new Set(e._outputs);for(const n of t){if(n._observer!==ut)return!0;n._outputs.forEach(t.add,t)}return!1}function If(e){return globalThis[e]}var Tf=[509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,574,3,9,9,7,9,32,4,318,1,80,3,71,10,50,3,123,2,54,14,32,10,3,1,11,3,46,10,8,0,46,9,7,2,37,13,2,9,6,1,45,0,13,2,49,13,9,3,2,11,83,11,7,0,3,0,158,11,6,9,7,3,56,1,2,6,3,1,3,2,10,0,11,1,3,6,4,4,68,8,2,0,3,0,2,3,2,4,2,0,15,1,83,17,10,9,5,0,82,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,7,19,58,14,5,9,243,14,166,9,71,5,2,1,3,3,2,0,2,1,13,9,120,6,3,6,4,0,29,9,41,6,2,3,9,0,10,10,47,15,343,9,54,7,2,7,17,9,57,21,2,13,123,5,4,0,2,1,2,6,2,0,9,9,49,4,2,1,2,4,9,9,330,3,10,1,2,0,49,6,4,4,14,10,5350,0,7,14,11465,27,2343,9,87,9,39,4,60,6,26,9,535,9,470,0,2,54,8,3,82,0,12,1,19628,1,4178,9,519,45,3,22,543,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,101,0,161,6,10,9,357,0,62,13,499,13,245,1,2,9,726,6,110,6,6,9,4759,9,787719,239],Hi=[0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,14,29,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,13,10,2,14,2,6,2,1,2,10,2,14,2,6,2,1,4,51,13,310,10,21,11,7,25,5,2,41,2,8,70,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,28,43,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,14,35,39,27,10,22,251,41,7,1,17,2,60,28,11,0,9,21,43,17,47,20,28,22,13,52,58,1,3,0,14,44,33,24,27,35,30,0,3,0,9,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,20,1,64,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,31,9,2,0,3,0,2,37,2,0,26,0,2,0,45,52,19,3,21,2,31,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,14,0,72,26,38,6,186,43,117,63,32,7,3,0,3,7,2,1,2,23,16,0,2,0,95,7,3,38,17,0,2,0,29,0,11,39,8,0,22,0,12,45,20,0,19,72,200,32,32,8,2,36,18,0,50,29,113,6,2,1,2,37,22,0,26,5,2,1,2,31,15,0,328,18,16,0,2,12,2,33,125,0,80,921,103,110,18,195,2637,96,16,1071,18,5,26,3994,6,582,6842,29,1763,568,8,30,18,78,18,29,19,47,17,3,32,20,6,18,433,44,212,63,129,74,6,0,67,12,65,1,2,0,29,6135,9,1237,42,9,8936,3,2,6,2,1,2,290,16,0,30,2,3,0,15,3,9,395,2309,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,1845,30,7,5,262,61,147,44,11,6,17,0,322,29,19,43,485,27,229,29,3,0,496,6,2,3,2,1,2,14,2,196,60,67,8,0,1205,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42719,33,4153,7,221,3,5761,15,7472,16,621,2467,541,1507,4938,6,4191],Nf="‌‍·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࢗ-࢟࣊-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍୕-ୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఄ఼ా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ೳഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ඁ-ඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ຼ່-໎໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜕ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠏-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᪿ-ᫎᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭᳴᳷-᳹᷀-᷿‌‍‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯・꘠-꘩꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧ꠬ꢀꢁꢴ-ꣅ꣐-꣙꣠-꣱ꣿ-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︯︳︴﹍-﹏0-9_・",zi="ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙՠ-ֈא-תׯ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࡠ-ࡪࡰ-ࢇࢉ-ࢎࢠ-ࣉऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱৼਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౝౠౡಀಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೝೞೠೡೱೲഄ-ഌഎ-ഐഒ-ഺഽൎൔ-ൖൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄຆ-ຊຌ-ຣລວ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜑᜟ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡸᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭌᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᲀ-ᲊᲐ-ᲺᲽ-Ჿᳩ-ᳬᳮ-ᳳᳵᳶᳺᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄯㄱ-ㆎㆠ-ㆿㇰ-ㇿ㐀-䶿一-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꟍꟐꟑꟓꟕ-Ƛꟲ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꣾꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭩꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",vn={3:"abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile",5:"class enum extends super const export import",6:"enum",strict:"implements interface let package private protected public static yield",strictBind:"eval arguments"},gn="break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this",Ff={5:gn,"5module":gn+" export import",6:gn+" const class extends export import super"},Lf=/^in(stanceof)?$/,Df=new RegExp("["+zi+"]"),$f=new RegExp("["+zi+Nf+"]");function Ln(e,t){for(var n=65536,s=0;se)return!1;if(n+=t[s+1],n>=e)return!0}return!1}function xe(e,t){return e<65?e===36:e<91?!0:e<97?e===95:e<123?!0:e<=65535?e>=170&&Df.test(String.fromCharCode(e)):t===!1?!1:Ln(e,Hi)}function Ge(e,t){return e<48?e===36:e<58?!0:e<65?!1:e<91?!0:e<97?e===95:e<123?!0:e<=65535?e>=170&&$f.test(String.fromCharCode(e)):t===!1?!1:Ln(e,Hi)||Ln(e,Tf)}var j=function(e,t){t===void 0&&(t={}),this.label=e,this.keyword=t.keyword,this.beforeExpr=!!t.beforeExpr,this.startsExpr=!!t.startsExpr,this.isLoop=!!t.isLoop,this.isAssign=!!t.isAssign,this.prefix=!!t.prefix,this.postfix=!!t.postfix,this.binop=t.binop||null,this.updateContext=null};function ne(e,t){return new j(e,{beforeExpr:!0,binop:t})}var se={beforeExpr:!0},X={startsExpr:!0},is={};function M(e,t){return t===void 0&&(t={}),t.keyword=e,is[e]=new j(e,t)}var l={num:new j("num",X),regexp:new j("regexp",X),string:new j("string",X),name:new j("name",X),privateId:new j("privateId",X),eof:new j("eof"),bracketL:new j("[",{beforeExpr:!0,startsExpr:!0}),bracketR:new j("]"),braceL:new j("{",{beforeExpr:!0,startsExpr:!0}),braceR:new j("}"),parenL:new j("(",{beforeExpr:!0,startsExpr:!0}),parenR:new j(")"),comma:new j(",",se),semi:new j(";",se),colon:new j(":",se),dot:new j("."),question:new j("?",se),questionDot:new j("?."),arrow:new j("=>",se),template:new j("template"),invalidTemplate:new j("invalidTemplate"),ellipsis:new j("...",se),backQuote:new j("`",X),dollarBraceL:new j("${",{beforeExpr:!0,startsExpr:!0}),eq:new j("=",{beforeExpr:!0,isAssign:!0}),assign:new j("_=",{beforeExpr:!0,isAssign:!0}),incDec:new j("++/--",{prefix:!0,postfix:!0,startsExpr:!0}),prefix:new j("!/~",{beforeExpr:!0,prefix:!0,startsExpr:!0}),logicalOR:ne("||",1),logicalAND:ne("&&",2),bitwiseOR:ne("|",3),bitwiseXOR:ne("^",4),bitwiseAND:ne("&",5),equality:ne("==/!=/===/!==",6),relational:ne("/<=/>=",7),bitShift:ne("<>/>>>",8),plusMin:new j("+/-",{beforeExpr:!0,binop:9,prefix:!0,startsExpr:!0}),modulo:ne("%",10),star:ne("*",10),slash:ne("/",10),starstar:new j("**",{beforeExpr:!0}),coalesce:ne("??",1),_break:M("break"),_case:M("case",se),_catch:M("catch"),_continue:M("continue"),_debugger:M("debugger"),_default:M("default",se),_do:M("do",{isLoop:!0,beforeExpr:!0}),_else:M("else",se),_finally:M("finally"),_for:M("for",{isLoop:!0}),_function:M("function",X),_if:M("if"),_return:M("return",se),_switch:M("switch"),_throw:M("throw",se),_try:M("try"),_var:M("var"),_const:M("const"),_while:M("while",{isLoop:!0}),_with:M("with"),_new:M("new",{beforeExpr:!0,startsExpr:!0}),_this:M("this",X),_super:M("super",X),_class:M("class",X),_extends:M("extends",se),_export:M("export"),_import:M("import",X),_null:M("null",X),_true:M("true",X),_false:M("false",X),_in:M("in",{beforeExpr:!0,binop:7}),_instanceof:M("instanceof",{beforeExpr:!0,binop:7}),_typeof:M("typeof",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_void:M("void",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_delete:M("delete",{beforeExpr:!0,prefix:!0,startsExpr:!0})},ue=/\r\n?|\n|\u2028|\u2029/,Bf=new RegExp(ue.source,"g");function Ke(e){return e===10||e===13||e===8232||e===8233}function Gi(e,t,n){n===void 0&&(n=e.length);for(var s=t;s>10)+55296,(e&1023)+56320))}var Mf=/(?:[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/,lt=function(e,t){this.line=e,this.column=t};lt.prototype.offset=function(e){return new lt(this.line,this.column+e)};var Xt=function(e,t,n){this.start=t,this.end=n,e.sourceFile!==null&&(this.source=e.sourceFile)};function en(e,t){for(var n=1,s=0;;){var i=Gi(e,s,t);if(i<0)return new lt(n,t-s);++n,s=i}}var Dn={ecmaVersion:null,sourceType:"script",onInsertedSemicolon:null,onTrailingComma:null,allowReserved:null,allowReturnOutsideFunction:!1,allowImportExportEverywhere:!1,allowAwaitOutsideFunction:null,allowSuperOutsideMethod:null,allowHashBang:!1,checkPrivateFields:!0,locations:!1,onToken:null,onComment:null,ranges:!1,program:null,sourceFile:null,directSourceFile:null,preserveParens:!1},Hs=!1;function Rf(e){var t={};for(var n in Dn)t[n]=e&>(e,n)?e[n]:Dn[n];if(t.ecmaVersion==="latest"?t.ecmaVersion=1e8:t.ecmaVersion==null?(!Hs&&typeof console=="object"&&console.warn&&(Hs=!0,console.warn(`Since Acorn 8.0.0, options.ecmaVersion is required. +Defaulting to 2020, but this will stop working in the future.`)),t.ecmaVersion=11):t.ecmaVersion>=2015&&(t.ecmaVersion-=2009),t.allowReserved==null&&(t.allowReserved=t.ecmaVersion<5),(!e||e.allowHashBang==null)&&(t.allowHashBang=t.ecmaVersion>=14),Us(t.onToken)){var s=t.onToken;t.onToken=function(i){return s.push(i)}}return Us(t.onComment)&&(t.onComment=jf(t,t.onComment)),t}function jf(e,t){return function(n,s,i,r,a,o){var u={type:n?"Block":"Line",value:s,start:i,end:r};e.locations&&(u.loc=new Xt(this,a,o)),e.ranges&&(u.range=[i,r]),t.push(u)}}var ct=1,Ye=2,rs=4,Yi=8,Qi=16,Ji=32,as=64,Zi=128,bt=256,os=ct|Ye|bt;function us(e,t){return Ye|(e?rs:0)|(t?Yi:0)}var Mt=0,ls=1,ke=2,Xi=3,er=4,tr=5,Y=function(e,t,n){this.options=e=Rf(e),this.sourceFile=e.sourceFile,this.keywords=Ie(Ff[e.ecmaVersion>=6?6:e.sourceType==="module"?"5module":5]);var s="";e.allowReserved!==!0&&(s=vn[e.ecmaVersion>=6?6:e.ecmaVersion===5?5:3],e.sourceType==="module"&&(s+=" await")),this.reservedWords=Ie(s);var i=(s?s+" ":"")+vn.strict;this.reservedWordsStrict=Ie(i),this.reservedWordsStrictBind=Ie(i+" "+vn.strictBind),this.input=String(t),this.containsEsc=!1,n?(this.pos=n,this.lineStart=this.input.lastIndexOf(` +`,n-1)+1,this.curLine=this.input.slice(0,this.lineStart).split(ue).length):(this.pos=this.lineStart=0,this.curLine=1),this.type=l.eof,this.value=null,this.start=this.end=this.pos,this.startLoc=this.endLoc=this.curPosition(),this.lastTokEndLoc=this.lastTokStartLoc=null,this.lastTokStart=this.lastTokEnd=this.pos,this.context=this.initialContext(),this.exprAllowed=!0,this.inModule=e.sourceType==="module",this.strict=this.inModule||this.strictDirective(this.pos),this.potentialArrowAt=-1,this.potentialArrowInForAwait=!1,this.yieldPos=this.awaitPos=this.awaitIdentPos=0,this.labels=[],this.undefinedExports=Object.create(null),this.pos===0&&e.allowHashBang&&this.input.slice(0,2)==="#!"&&this.skipLineComment(2),this.scopeStack=[],this.enterScope(ct),this.regexpState=null,this.privateNameStack=[]},de={inFunction:{configurable:!0},inGenerator:{configurable:!0},inAsync:{configurable:!0},canAwait:{configurable:!0},allowSuper:{configurable:!0},allowDirectSuper:{configurable:!0},treatFunctionsAsVar:{configurable:!0},allowNewDotTarget:{configurable:!0},inClassStaticBlock:{configurable:!0}};Y.prototype.parse=function(){var e=this.options.program||this.startNode();return this.nextToken(),this.parseTopLevel(e)};de.inFunction.get=function(){return(this.currentVarScope().flags&Ye)>0};de.inGenerator.get=function(){return(this.currentVarScope().flags&Yi)>0&&!this.currentVarScope().inClassFieldInit};de.inAsync.get=function(){return(this.currentVarScope().flags&rs)>0&&!this.currentVarScope().inClassFieldInit};de.canAwait.get=function(){for(var e=this.scopeStack.length-1;e>=0;e--){var t=this.scopeStack[e];if(t.inClassFieldInit||t.flags&bt)return!1;if(t.flags&Ye)return(t.flags&rs)>0}return this.inModule&&this.options.ecmaVersion>=13||this.options.allowAwaitOutsideFunction};de.allowSuper.get=function(){var e=this.currentThisScope(),t=e.flags,n=e.inClassFieldInit;return(t&as)>0||n||this.options.allowSuperOutsideMethod};de.allowDirectSuper.get=function(){return(this.currentThisScope().flags&Zi)>0};de.treatFunctionsAsVar.get=function(){return this.treatFunctionsAsVarInScope(this.currentScope())};de.allowNewDotTarget.get=function(){var e=this.currentThisScope(),t=e.flags,n=e.inClassFieldInit;return(t&(Ye|bt))>0||n};de.inClassStaticBlock.get=function(){return(this.currentVarScope().flags&bt)>0};Y.extend=function(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];for(var n=this,s=0;s=,?^&]/.test(i)||i==="!"&&this.input.charAt(s+1)==="=")}e+=t[0].length,re.lastIndex=e,e+=re.exec(this.input)[0].length,this.input[e]===";"&&e++}};Z.eat=function(e){return this.type===e?(this.next(),!0):!1};Z.isContextual=function(e){return this.type===l.name&&this.value===e&&!this.containsEsc};Z.eatContextual=function(e){return this.isContextual(e)?(this.next(),!0):!1};Z.expectContextual=function(e){this.eatContextual(e)||this.unexpected()};Z.canInsertSemicolon=function(){return this.type===l.eof||this.type===l.braceR||ue.test(this.input.slice(this.lastTokEnd,this.start))};Z.insertSemicolon=function(){if(this.canInsertSemicolon())return this.options.onInsertedSemicolon&&this.options.onInsertedSemicolon(this.lastTokEnd,this.lastTokEndLoc),!0};Z.semicolon=function(){!this.eat(l.semi)&&!this.insertSemicolon()&&this.unexpected()};Z.afterTrailingComma=function(e,t){if(this.type===e)return this.options.onTrailingComma&&this.options.onTrailingComma(this.lastTokStart,this.lastTokStartLoc),t||this.next(),!0};Z.expect=function(e){this.eat(e)||this.unexpected()};Z.unexpected=function(e){this.raise(e??this.start,"Unexpected token")};var tn=function(){this.shorthandAssign=this.trailingComma=this.parenthesizedAssign=this.parenthesizedBind=this.doubleProto=-1};Z.checkPatternErrors=function(e,t){if(e){e.trailingComma>-1&&this.raiseRecoverable(e.trailingComma,"Comma is not permitted after the rest element");var n=t?e.parenthesizedAssign:e.parenthesizedBind;n>-1&&this.raiseRecoverable(n,t?"Assigning to rvalue":"Parenthesized pattern")}};Z.checkExpressionErrors=function(e,t){if(!e)return!1;var n=e.shorthandAssign,s=e.doubleProto;if(!t)return n>=0||s>=0;n>=0&&this.raise(n,"Shorthand property assignments are valid only in destructuring patterns"),s>=0&&this.raiseRecoverable(s,"Redefinition of __proto__ property")};Z.checkYieldAwaitInDefaultParams=function(){this.yieldPos&&(!this.awaitPos||this.yieldPos55295&&s<56320)return!0;if(xe(s,!0)){for(var i=n+1;Ge(s=this.input.charCodeAt(i),!0);)++i;if(s===92||s>55295&&s<56320)return!0;var r=this.input.slice(n,i);if(!Lf.test(r))return!0}return!1};P.isAsyncFunction=function(){if(this.options.ecmaVersion<8||!this.isContextual("async"))return!1;re.lastIndex=this.pos;var e=re.exec(this.input),t=this.pos+e[0].length,n;return!ue.test(this.input.slice(this.pos,t))&&this.input.slice(t,t+8)==="function"&&(t+8===this.input.length||!(Ge(n=this.input.charCodeAt(t+8))||n>55295&&n<56320))};P.parseStatement=function(e,t,n){var s=this.type,i=this.startNode(),r;switch(this.isLet(e)&&(s=l._var,r="let"),s){case l._break:case l._continue:return this.parseBreakContinueStatement(i,s.keyword);case l._debugger:return this.parseDebuggerStatement(i);case l._do:return this.parseDoStatement(i);case l._for:return this.parseForStatement(i);case l._function:return e&&(this.strict||e!=="if"&&e!=="label")&&this.options.ecmaVersion>=6&&this.unexpected(),this.parseFunctionStatement(i,!1,!e);case l._class:return e&&this.unexpected(),this.parseClass(i,!0);case l._if:return this.parseIfStatement(i);case l._return:return this.parseReturnStatement(i);case l._switch:return this.parseSwitchStatement(i);case l._throw:return this.parseThrowStatement(i);case l._try:return this.parseTryStatement(i);case l._const:case l._var:return r=r||this.value,e&&r!=="var"&&this.unexpected(),this.parseVarStatement(i,r);case l._while:return this.parseWhileStatement(i);case l._with:return this.parseWithStatement(i);case l.braceL:return this.parseBlock(!0,i);case l.semi:return this.parseEmptyStatement(i);case l._export:case l._import:if(this.options.ecmaVersion>10&&s===l._import){re.lastIndex=this.pos;var a=re.exec(this.input),o=this.pos+a[0].length,u=this.input.charCodeAt(o);if(u===40||u===46)return this.parseExpressionStatement(i,this.parseExpression())}return this.options.allowImportExportEverywhere||(t||this.raise(this.start,"'import' and 'export' may only appear at the top level"),this.inModule||this.raise(this.start,"'import' and 'export' may appear only with 'sourceType: module'")),s===l._import?this.parseImport(i):this.parseExport(i,n);default:if(this.isAsyncFunction())return e&&this.unexpected(),this.next(),this.parseFunctionStatement(i,!0,!e);var c=this.value,h=this.parseExpression();return s===l.name&&h.type==="Identifier"&&this.eat(l.colon)?this.parseLabeledStatement(i,c,h,e):this.parseExpressionStatement(i,h)}};P.parseBreakContinueStatement=function(e,t){var n=t==="break";this.next(),this.eat(l.semi)||this.insertSemicolon()?e.label=null:this.type!==l.name?this.unexpected():(e.label=this.parseIdent(),this.semicolon());for(var s=0;s=6?this.eat(l.semi):this.semicolon(),this.finishNode(e,"DoWhileStatement")};P.parseForStatement=function(e){this.next();var t=this.options.ecmaVersion>=9&&this.canAwait&&this.eatContextual("await")?this.lastTokStart:-1;if(this.labels.push(cs),this.enterScope(0),this.expect(l.parenL),this.type===l.semi)return t>-1&&this.unexpected(t),this.parseFor(e,null);var n=this.isLet();if(this.type===l._var||this.type===l._const||n){var s=this.startNode(),i=n?"let":this.value;return this.next(),this.parseVar(s,!0,i),this.finishNode(s,"VariableDeclaration"),(this.type===l._in||this.options.ecmaVersion>=6&&this.isContextual("of"))&&s.declarations.length===1?(this.options.ecmaVersion>=9&&(this.type===l._in?t>-1&&this.unexpected(t):e.await=t>-1),this.parseForIn(e,s)):(t>-1&&this.unexpected(t),this.parseFor(e,s))}var r=this.isContextual("let"),a=!1,o=this.containsEsc,u=new tn,c=this.start,h=t>-1?this.parseExprSubscripts(u,"await"):this.parseExpression(!0,u);return this.type===l._in||(a=this.options.ecmaVersion>=6&&this.isContextual("of"))?(t>-1?(this.type===l._in&&this.unexpected(t),e.await=!0):a&&this.options.ecmaVersion>=8&&(h.start===c&&!o&&h.type==="Identifier"&&h.name==="async"?this.unexpected():this.options.ecmaVersion>=9&&(e.await=!1)),r&&a&&this.raise(h.start,"The left-hand side of a for-of loop may not start with 'let'."),this.toAssignable(h,!1,u),this.checkLValPattern(h),this.parseForIn(e,h)):(this.checkExpressionErrors(u,!0),t>-1&&this.unexpected(t),this.parseFor(e,h))};P.parseFunctionStatement=function(e,t,n){return this.next(),this.parseFunction(e,it|(n?0:$n),!1,t)};P.parseIfStatement=function(e){return this.next(),e.test=this.parseParenExpression(),e.consequent=this.parseStatement("if"),e.alternate=this.eat(l._else)?this.parseStatement("if"):null,this.finishNode(e,"IfStatement")};P.parseReturnStatement=function(e){return!this.inFunction&&!this.options.allowReturnOutsideFunction&&this.raise(this.start,"'return' outside of function"),this.next(),this.eat(l.semi)||this.insertSemicolon()?e.argument=null:(e.argument=this.parseExpression(),this.semicolon()),this.finishNode(e,"ReturnStatement")};P.parseSwitchStatement=function(e){this.next(),e.discriminant=this.parseParenExpression(),e.cases=[],this.expect(l.braceL),this.labels.push(qf),this.enterScope(0);for(var t,n=!1;this.type!==l.braceR;)if(this.type===l._case||this.type===l._default){var s=this.type===l._case;t&&this.finishNode(t,"SwitchCase"),e.cases.push(t=this.startNode()),t.consequent=[],this.next(),s?t.test=this.parseExpression():(n&&this.raiseRecoverable(this.lastTokStart,"Multiple default clauses"),n=!0,t.test=null),this.expect(l.colon)}else t||this.unexpected(),t.consequent.push(this.parseStatement(null));return this.exitScope(),t&&this.finishNode(t,"SwitchCase"),this.next(),this.labels.pop(),this.finishNode(e,"SwitchStatement")};P.parseThrowStatement=function(e){return this.next(),ue.test(this.input.slice(this.lastTokEnd,this.start))&&this.raise(this.lastTokEnd,"Illegal newline after throw"),e.argument=this.parseExpression(),this.semicolon(),this.finishNode(e,"ThrowStatement")};var Hf=[];P.parseCatchClauseParam=function(){var e=this.parseBindingAtom(),t=e.type==="Identifier";return this.enterScope(t?Ji:0),this.checkLValPattern(e,t?er:ke),this.expect(l.parenR),e};P.parseTryStatement=function(e){if(this.next(),e.block=this.parseBlock(),e.handler=null,this.type===l._catch){var t=this.startNode();this.next(),this.eat(l.parenL)?t.param=this.parseCatchClauseParam():(this.options.ecmaVersion<10&&this.unexpected(),t.param=null,this.enterScope(0)),t.body=this.parseBlock(!1),this.exitScope(),e.handler=this.finishNode(t,"CatchClause")}return e.finalizer=this.eat(l._finally)?this.parseBlock():null,!e.handler&&!e.finalizer&&this.raise(e.start,"Missing catch or finally clause"),this.finishNode(e,"TryStatement")};P.parseVarStatement=function(e,t,n){return this.next(),this.parseVar(e,!1,t,n),this.semicolon(),this.finishNode(e,"VariableDeclaration")};P.parseWhileStatement=function(e){return this.next(),e.test=this.parseParenExpression(),this.labels.push(cs),e.body=this.parseStatement("while"),this.labels.pop(),this.finishNode(e,"WhileStatement")};P.parseWithStatement=function(e){return this.strict&&this.raise(this.start,"'with' in strict mode"),this.next(),e.object=this.parseParenExpression(),e.body=this.parseStatement("with"),this.finishNode(e,"WithStatement")};P.parseEmptyStatement=function(e){return this.next(),this.finishNode(e,"EmptyStatement")};P.parseLabeledStatement=function(e,t,n,s){for(var i=0,r=this.labels;i=0;u--){var c=this.labels[u];if(c.statementStart===e.start)c.statementStart=this.start,c.kind=o;else break}return this.labels.push({name:t,kind:o,statementStart:this.start}),e.body=this.parseStatement(s?s.indexOf("label")===-1?s+"label":s:"label"),this.labels.pop(),e.label=n,this.finishNode(e,"LabeledStatement")};P.parseExpressionStatement=function(e,t){return e.expression=t,this.semicolon(),this.finishNode(e,"ExpressionStatement")};P.parseBlock=function(e,t,n){for(e===void 0&&(e=!0),t===void 0&&(t=this.startNode()),t.body=[],this.expect(l.braceL),e&&this.enterScope(0);this.type!==l.braceR;){var s=this.parseStatement(null);t.body.push(s)}return n&&(this.strict=!1),this.next(),e&&this.exitScope(),this.finishNode(t,"BlockStatement")};P.parseFor=function(e,t){return e.init=t,this.expect(l.semi),e.test=this.type===l.semi?null:this.parseExpression(),this.expect(l.semi),e.update=this.type===l.parenR?null:this.parseExpression(),this.expect(l.parenR),e.body=this.parseStatement("for"),this.exitScope(),this.labels.pop(),this.finishNode(e,"ForStatement")};P.parseForIn=function(e,t){var n=this.type===l._in;return this.next(),t.type==="VariableDeclaration"&&t.declarations[0].init!=null&&(!n||this.options.ecmaVersion<8||this.strict||t.kind!=="var"||t.declarations[0].id.type!=="Identifier")&&this.raise(t.start,(n?"for-in":"for-of")+" loop variable declaration may not have an initializer"),e.left=t,e.right=n?this.parseExpression():this.parseMaybeAssign(),this.expect(l.parenR),e.body=this.parseStatement("for"),this.exitScope(),this.labels.pop(),this.finishNode(e,n?"ForInStatement":"ForOfStatement")};P.parseVar=function(e,t,n,s){for(e.declarations=[],e.kind=n;;){var i=this.startNode();if(this.parseVarId(i,n),this.eat(l.eq)?i.init=this.parseMaybeAssign(t):!s&&n==="const"&&!(this.type===l._in||this.options.ecmaVersion>=6&&this.isContextual("of"))?this.unexpected():!s&&i.id.type!=="Identifier"&&!(t&&(this.type===l._in||this.isContextual("of")))?this.raise(this.lastTokEnd,"Complex binding patterns require an initialization value"):i.init=null,e.declarations.push(this.finishNode(i,"VariableDeclarator")),!this.eat(l.comma))break}return e};P.parseVarId=function(e,t){e.id=this.parseBindingAtom(),this.checkLValPattern(e.id,t==="var"?ls:ke,!1)};var it=1,$n=2,nr=4;P.parseFunction=function(e,t,n,s,i){this.initFunction(e),(this.options.ecmaVersion>=9||this.options.ecmaVersion>=6&&!s)&&(this.type===l.star&&t&$n&&this.unexpected(),e.generator=this.eat(l.star)),this.options.ecmaVersion>=8&&(e.async=!!s),t&it&&(e.id=t&nr&&this.type!==l.name?null:this.parseIdent(),e.id&&!(t&$n)&&this.checkLValSimple(e.id,this.strict||e.generator||e.async?this.treatFunctionsAsVar?ls:ke:Xi));var r=this.yieldPos,a=this.awaitPos,o=this.awaitIdentPos;return this.yieldPos=0,this.awaitPos=0,this.awaitIdentPos=0,this.enterScope(us(e.async,e.generator)),t&it||(e.id=this.type===l.name?this.parseIdent():null),this.parseFunctionParams(e),this.parseFunctionBody(e,n,!1,i),this.yieldPos=r,this.awaitPos=a,this.awaitIdentPos=o,this.finishNode(e,t&it?"FunctionDeclaration":"FunctionExpression")};P.parseFunctionParams=function(e){this.expect(l.parenL),e.params=this.parseBindingList(l.parenR,!1,this.options.ecmaVersion>=8),this.checkYieldAwaitInDefaultParams()};P.parseClass=function(e,t){this.next();var n=this.strict;this.strict=!0,this.parseClassId(e,t),this.parseClassSuper(e);var s=this.enterClassBody(),i=this.startNode(),r=!1;for(i.body=[],this.expect(l.braceL);this.type!==l.braceR;){var a=this.parseClassElement(e.superClass!==null);a&&(i.body.push(a),a.type==="MethodDefinition"&&a.kind==="constructor"?(r&&this.raiseRecoverable(a.start,"Duplicate constructor in the same class"),r=!0):a.key&&a.key.type==="PrivateIdentifier"&&zf(s,a)&&this.raiseRecoverable(a.key.start,"Identifier '#"+a.key.name+"' has already been declared"))}return this.strict=n,this.next(),e.body=this.finishNode(i,"ClassBody"),this.exitClassBody(),this.finishNode(e,t?"ClassDeclaration":"ClassExpression")};P.parseClassElement=function(e){if(this.eat(l.semi))return null;var t=this.options.ecmaVersion,n=this.startNode(),s="",i=!1,r=!1,a="method",o=!1;if(this.eatContextual("static")){if(t>=13&&this.eat(l.braceL))return this.parseClassStaticBlock(n),n;this.isClassElementNameStart()||this.type===l.star?o=!0:s="static"}if(n.static=o,!s&&t>=8&&this.eatContextual("async")&&((this.isClassElementNameStart()||this.type===l.star)&&!this.canInsertSemicolon()?r=!0:s="async"),!s&&(t>=9||!r)&&this.eat(l.star)&&(i=!0),!s&&!r&&!i){var u=this.value;(this.eatContextual("get")||this.eatContextual("set"))&&(this.isClassElementNameStart()?a=u:s=u)}if(s?(n.computed=!1,n.key=this.startNodeAt(this.lastTokStart,this.lastTokStartLoc),n.key.name=s,this.finishNode(n.key,"Identifier")):this.parseClassElementName(n),t<13||this.type===l.parenL||a!=="method"||i||r){var c=!n.static&&Rt(n,"constructor"),h=c&&e;c&&a!=="method"&&this.raise(n.key.start,"Constructor can't have get/set modifier"),n.kind=c?"constructor":a,this.parseClassMethod(n,i,r,h)}else this.parseClassField(n);return n};P.isClassElementNameStart=function(){return this.type===l.name||this.type===l.privateId||this.type===l.num||this.type===l.string||this.type===l.bracketL||this.type.keyword};P.parseClassElementName=function(e){this.type===l.privateId?(this.value==="constructor"&&this.raise(this.start,"Classes can't have an element named '#constructor'"),e.computed=!1,e.key=this.parsePrivateIdent()):this.parsePropertyName(e)};P.parseClassMethod=function(e,t,n,s){var i=e.key;e.kind==="constructor"?(t&&this.raise(i.start,"Constructor can't be a generator"),n&&this.raise(i.start,"Constructor can't be an async method")):e.static&&Rt(e,"prototype")&&this.raise(i.start,"Classes may not have a static property named prototype");var r=e.value=this.parseMethod(t,n,s);return e.kind==="get"&&r.params.length!==0&&this.raiseRecoverable(r.start,"getter should have no params"),e.kind==="set"&&r.params.length!==1&&this.raiseRecoverable(r.start,"setter should have exactly one param"),e.kind==="set"&&r.params[0].type==="RestElement"&&this.raiseRecoverable(r.params[0].start,"Setter cannot use rest params"),this.finishNode(e,"MethodDefinition")};P.parseClassField=function(e){if(Rt(e,"constructor")?this.raise(e.key.start,"Classes can't have a field named 'constructor'"):e.static&&Rt(e,"prototype")&&this.raise(e.key.start,"Classes can't have a static field named 'prototype'"),this.eat(l.eq)){var t=this.currentThisScope(),n=t.inClassFieldInit;t.inClassFieldInit=!0,e.value=this.parseMaybeAssign(),t.inClassFieldInit=n}else e.value=null;return this.semicolon(),this.finishNode(e,"PropertyDefinition")};P.parseClassStaticBlock=function(e){e.body=[];var t=this.labels;for(this.labels=[],this.enterScope(bt|as);this.type!==l.braceR;){var n=this.parseStatement(null);e.body.push(n)}return this.next(),this.exitScope(),this.labels=t,this.finishNode(e,"StaticBlock")};P.parseClassId=function(e,t){this.type===l.name?(e.id=this.parseIdent(),t&&this.checkLValSimple(e.id,ke,!1)):(t===!0&&this.unexpected(),e.id=null)};P.parseClassSuper=function(e){e.superClass=this.eat(l._extends)?this.parseExprSubscripts(null,!1):null};P.enterClassBody=function(){var e={declared:Object.create(null),used:[]};return this.privateNameStack.push(e),e.declared};P.exitClassBody=function(){var e=this.privateNameStack.pop(),t=e.declared,n=e.used;if(this.options.checkPrivateFields)for(var s=this.privateNameStack.length,i=s===0?null:this.privateNameStack[s-1],r=0;r=11&&(this.eatContextual("as")?(e.exported=this.parseModuleExportName(),this.checkExport(t,e.exported,this.lastTokStart)):e.exported=null),this.expectContextual("from"),this.type!==l.string&&this.unexpected(),e.source=this.parseExprAtom(),this.semicolon(),this.finishNode(e,"ExportAllDeclaration")};P.parseExport=function(e,t){if(this.next(),this.eat(l.star))return this.parseExportAllDeclaration(e,t);if(this.eat(l._default))return this.checkExport(t,"default",this.lastTokStart),e.declaration=this.parseExportDefaultDeclaration(),this.finishNode(e,"ExportDefaultDeclaration");if(this.shouldParseExportStatement())e.declaration=this.parseExportDeclaration(e),e.declaration.type==="VariableDeclaration"?this.checkVariableExport(t,e.declaration.declarations):this.checkExport(t,e.declaration.id,e.declaration.id.start),e.specifiers=[],e.source=null;else{if(e.declaration=null,e.specifiers=this.parseExportSpecifiers(t),this.eatContextual("from"))this.type!==l.string&&this.unexpected(),e.source=this.parseExprAtom();else{for(var n=0,s=e.specifiers;n=13&&this.type===l.string){var e=this.parseLiteral(this.value);return Mf.test(e.value)&&this.raise(e.start,"An export name cannot include a lone surrogate."),e}return this.parseIdent(!0)};P.adaptDirectivePrologue=function(e){for(var t=0;t=5&&e.type==="ExpressionStatement"&&e.expression.type==="Literal"&&typeof e.expression.value=="string"&&(this.input[e.start]==='"'||this.input[e.start]==="'")};var le=Y.prototype;le.toAssignable=function(e,t,n){if(this.options.ecmaVersion>=6&&e)switch(e.type){case"Identifier":this.inAsync&&e.name==="await"&&this.raise(e.start,"Cannot use 'await' as identifier inside an async function");break;case"ObjectPattern":case"ArrayPattern":case"AssignmentPattern":case"RestElement":break;case"ObjectExpression":e.type="ObjectPattern",n&&this.checkPatternErrors(n,!0);for(var s=0,i=e.properties;s=8&&!o&&u.name==="async"&&!this.canInsertSemicolon()&&this.eat(l._function))return this.overrideContext(G.f_expr),this.parseFunction(this.startNodeAt(r,a),0,!1,!0,t);if(i&&!this.canInsertSemicolon()){if(this.eat(l.arrow))return this.parseArrowExpression(this.startNodeAt(r,a),[u],!1,t);if(this.options.ecmaVersion>=8&&u.name==="async"&&this.type===l.name&&!o&&(!this.potentialArrowInForAwait||this.value!=="of"||this.containsEsc))return u=this.parseIdent(!1),(this.canInsertSemicolon()||!this.eat(l.arrow))&&this.unexpected(),this.parseArrowExpression(this.startNodeAt(r,a),[u],!0,t)}return u;case l.regexp:var c=this.value;return s=this.parseLiteral(c.value),s.regex={pattern:c.pattern,flags:c.flags},s;case l.num:case l.string:return this.parseLiteral(this.value);case l._null:case l._true:case l._false:return s=this.startNode(),s.value=this.type===l._null?null:this.type===l._true,s.raw=this.type.keyword,this.next(),this.finishNode(s,"Literal");case l.parenL:var h=this.start,p=this.parseParenAndDistinguishExpression(i,t);return e&&(e.parenthesizedAssign<0&&!this.isSimpleAssignTarget(p)&&(e.parenthesizedAssign=h),e.parenthesizedBind<0&&(e.parenthesizedBind=h)),p;case l.bracketL:return s=this.startNode(),this.next(),s.elements=this.parseExprList(l.bracketR,!0,!0,e),this.finishNode(s,"ArrayExpression");case l.braceL:return this.overrideContext(G.b_expr),this.parseObj(!1,e);case l._function:return s=this.startNode(),this.next(),this.parseFunction(s,0);case l._class:return this.parseClass(this.startNode(),!1);case l._new:return this.parseNew();case l.backQuote:return this.parseTemplate();case l._import:return this.options.ecmaVersion>=11?this.parseExprImport(n):this.unexpected();default:return this.parseExprAtomDefault()}};$.parseExprAtomDefault=function(){this.unexpected()};$.parseExprImport=function(e){var t=this.startNode();if(this.containsEsc&&this.raiseRecoverable(this.start,"Escape sequence in keyword import"),this.next(),this.type===l.parenL&&!e)return this.parseDynamicImport(t);if(this.type===l.dot){var n=this.startNodeAt(t.start,t.loc&&t.loc.start);return n.name="import",t.meta=this.finishNode(n,"Identifier"),this.parseImportMeta(t)}else this.unexpected()};$.parseDynamicImport=function(e){if(this.next(),e.source=this.parseMaybeAssign(),!this.eat(l.parenR)){var t=this.start;this.eat(l.comma)&&this.eat(l.parenR)?this.raiseRecoverable(t,"Trailing comma is not allowed in import()"):this.unexpected(t)}return this.finishNode(e,"ImportExpression")};$.parseImportMeta=function(e){this.next();var t=this.containsEsc;return e.property=this.parseIdent(!0),e.property.name!=="meta"&&this.raiseRecoverable(e.property.start,"The only valid meta property for import is 'import.meta'"),t&&this.raiseRecoverable(e.start,"'import.meta' must not contain escaped characters"),this.options.sourceType!=="module"&&!this.options.allowImportExportEverywhere&&this.raiseRecoverable(e.start,"Cannot use 'import.meta' outside a module"),this.finishNode(e,"MetaProperty")};$.parseLiteral=function(e){var t=this.startNode();return t.value=e,t.raw=this.input.slice(this.start,this.end),t.raw.charCodeAt(t.raw.length-1)===110&&(t.bigint=t.raw.slice(0,-1).replace(/_/g,"")),this.next(),this.finishNode(t,"Literal")};$.parseParenExpression=function(){this.expect(l.parenL);var e=this.parseExpression();return this.expect(l.parenR),e};$.shouldParseArrow=function(e){return!this.canInsertSemicolon()};$.parseParenAndDistinguishExpression=function(e,t){var n=this.start,s=this.startLoc,i,r=this.options.ecmaVersion>=8;if(this.options.ecmaVersion>=6){this.next();var a=this.start,o=this.startLoc,u=[],c=!0,h=!1,p=new tn,m=this.yieldPos,b=this.awaitPos,w;for(this.yieldPos=0,this.awaitPos=0;this.type!==l.parenR;)if(c?c=!1:this.expect(l.comma),r&&this.afterTrailingComma(l.parenR,!0)){h=!0;break}else if(this.type===l.ellipsis){w=this.start,u.push(this.parseParenItem(this.parseRestBinding())),this.type===l.comma&&this.raiseRecoverable(this.start,"Comma is not permitted after the rest element");break}else u.push(this.parseMaybeAssign(!1,p,this.parseParenItem));var N=this.lastTokEnd,I=this.lastTokEndLoc;if(this.expect(l.parenR),e&&this.shouldParseArrow(u)&&this.eat(l.arrow))return this.checkPatternErrors(p,!1),this.checkYieldAwaitInDefaultParams(),this.yieldPos=m,this.awaitPos=b,this.parseParenArrowList(n,s,u,t);(!u.length||h)&&this.unexpected(this.lastTokStart),w&&this.unexpected(w),this.checkExpressionErrors(p,!0),this.yieldPos=m||this.yieldPos,this.awaitPos=b||this.awaitPos,u.length>1?(i=this.startNodeAt(a,o),i.expressions=u,this.finishNodeAt(i,"SequenceExpression",N,I)):i=u[0]}else i=this.parseParenExpression();if(this.options.preserveParens){var x=this.startNodeAt(n,s);return x.expression=i,this.finishNode(x,"ParenthesizedExpression")}else return i};$.parseParenItem=function(e){return e};$.parseParenArrowList=function(e,t,n,s){return this.parseArrowExpression(this.startNodeAt(e,t),n,!1,s)};var Gf=[];$.parseNew=function(){this.containsEsc&&this.raiseRecoverable(this.start,"Escape sequence in keyword new");var e=this.startNode();if(this.next(),this.options.ecmaVersion>=6&&this.type===l.dot){var t=this.startNodeAt(e.start,e.loc&&e.loc.start);t.name="new",e.meta=this.finishNode(t,"Identifier"),this.next();var n=this.containsEsc;return e.property=this.parseIdent(!0),e.property.name!=="target"&&this.raiseRecoverable(e.property.start,"The only valid meta property for new is 'new.target'"),n&&this.raiseRecoverable(e.start,"'new.target' must not contain escaped characters"),this.allowNewDotTarget||this.raiseRecoverable(e.start,"'new.target' can only be used in functions and class static block"),this.finishNode(e,"MetaProperty")}var s=this.start,i=this.startLoc;return e.callee=this.parseSubscripts(this.parseExprAtom(null,!1,!0),s,i,!0,!1),this.eat(l.parenL)?e.arguments=this.parseExprList(l.parenR,this.options.ecmaVersion>=8,!1):e.arguments=Gf,this.finishNode(e,"NewExpression")};$.parseTemplateElement=function(e){var t=e.isTagged,n=this.startNode();return this.type===l.invalidTemplate?(t||this.raiseRecoverable(this.start,"Bad escape sequence in untagged template literal"),n.value={raw:this.value.replace(/\r\n?/g,` +`),cooked:null}):n.value={raw:this.input.slice(this.start,this.end).replace(/\r\n?/g,` +`),cooked:this.value},this.next(),n.tail=this.type===l.backQuote,this.finishNode(n,"TemplateElement")};$.parseTemplate=function(e){e===void 0&&(e={});var t=e.isTagged;t===void 0&&(t=!1);var n=this.startNode();this.next(),n.expressions=[];var s=this.parseTemplateElement({isTagged:t});for(n.quasis=[s];!s.tail;)this.type===l.eof&&this.raise(this.pos,"Unterminated template literal"),this.expect(l.dollarBraceL),n.expressions.push(this.parseExpression()),this.expect(l.braceR),n.quasis.push(s=this.parseTemplateElement({isTagged:t}));return this.next(),this.finishNode(n,"TemplateLiteral")};$.isAsyncProp=function(e){return!e.computed&&e.key.type==="Identifier"&&e.key.name==="async"&&(this.type===l.name||this.type===l.num||this.type===l.string||this.type===l.bracketL||this.type.keyword||this.options.ecmaVersion>=9&&this.type===l.star)&&!ue.test(this.input.slice(this.lastTokEnd,this.start))};$.parseObj=function(e,t){var n=this.startNode(),s=!0,i={};for(n.properties=[],this.next();!this.eat(l.braceR);){if(s)s=!1;else if(this.expect(l.comma),this.options.ecmaVersion>=5&&this.afterTrailingComma(l.braceR))break;var r=this.parseProperty(e,t);e||this.checkPropClash(r,i,t),n.properties.push(r)}return this.finishNode(n,e?"ObjectPattern":"ObjectExpression")};$.parseProperty=function(e,t){var n=this.startNode(),s,i,r,a;if(this.options.ecmaVersion>=9&&this.eat(l.ellipsis))return e?(n.argument=this.parseIdent(!1),this.type===l.comma&&this.raiseRecoverable(this.start,"Comma is not permitted after the rest element"),this.finishNode(n,"RestElement")):(n.argument=this.parseMaybeAssign(!1,t),this.type===l.comma&&t&&t.trailingComma<0&&(t.trailingComma=this.start),this.finishNode(n,"SpreadElement"));this.options.ecmaVersion>=6&&(n.method=!1,n.shorthand=!1,(e||t)&&(r=this.start,a=this.startLoc),e||(s=this.eat(l.star)));var o=this.containsEsc;return this.parsePropertyName(n),!e&&!o&&this.options.ecmaVersion>=8&&!s&&this.isAsyncProp(n)?(i=!0,s=this.options.ecmaVersion>=9&&this.eat(l.star),this.parsePropertyName(n)):i=!1,this.parsePropertyValue(n,e,s,i,r,a,t,o),this.finishNode(n,"Property")};$.parseGetterSetter=function(e){e.kind=e.key.name,this.parsePropertyName(e),e.value=this.parseMethod(!1);var t=e.kind==="get"?0:1;if(e.value.params.length!==t){var n=e.value.start;e.kind==="get"?this.raiseRecoverable(n,"getter should have no params"):this.raiseRecoverable(n,"setter should have exactly one param")}else e.kind==="set"&&e.value.params[0].type==="RestElement"&&this.raiseRecoverable(e.value.params[0].start,"Setter cannot use rest params")};$.parsePropertyValue=function(e,t,n,s,i,r,a,o){(n||s)&&this.type===l.colon&&this.unexpected(),this.eat(l.colon)?(e.value=t?this.parseMaybeDefault(this.start,this.startLoc):this.parseMaybeAssign(!1,a),e.kind="init"):this.options.ecmaVersion>=6&&this.type===l.parenL?(t&&this.unexpected(),e.kind="init",e.method=!0,e.value=this.parseMethod(n,s)):!t&&!o&&this.options.ecmaVersion>=5&&!e.computed&&e.key.type==="Identifier"&&(e.key.name==="get"||e.key.name==="set")&&this.type!==l.comma&&this.type!==l.braceR&&this.type!==l.eq?((n||s)&&this.unexpected(),this.parseGetterSetter(e)):this.options.ecmaVersion>=6&&!e.computed&&e.key.type==="Identifier"?((n||s)&&this.unexpected(),this.checkUnreserved(e.key),e.key.name==="await"&&!this.awaitIdentPos&&(this.awaitIdentPos=i),e.kind="init",t?e.value=this.parseMaybeDefault(i,r,this.copyNode(e.key)):this.type===l.eq&&a?(a.shorthandAssign<0&&(a.shorthandAssign=this.start),e.value=this.parseMaybeDefault(i,r,this.copyNode(e.key))):e.value=this.copyNode(e.key),e.shorthand=!0):this.unexpected()};$.parsePropertyName=function(e){if(this.options.ecmaVersion>=6){if(this.eat(l.bracketL))return e.computed=!0,e.key=this.parseMaybeAssign(),this.expect(l.bracketR),e.key;e.computed=!1}return e.key=this.type===l.num||this.type===l.string?this.parseExprAtom():this.parseIdent(this.options.allowReserved!=="never")};$.initFunction=function(e){e.id=null,this.options.ecmaVersion>=6&&(e.generator=e.expression=!1),this.options.ecmaVersion>=8&&(e.async=!1)};$.parseMethod=function(e,t,n){var s=this.startNode(),i=this.yieldPos,r=this.awaitPos,a=this.awaitIdentPos;return this.initFunction(s),this.options.ecmaVersion>=6&&(s.generator=e),this.options.ecmaVersion>=8&&(s.async=!!t),this.yieldPos=0,this.awaitPos=0,this.awaitIdentPos=0,this.enterScope(us(t,s.generator)|as|(n?Zi:0)),this.expect(l.parenL),s.params=this.parseBindingList(l.parenR,!1,this.options.ecmaVersion>=8),this.checkYieldAwaitInDefaultParams(),this.parseFunctionBody(s,!1,!0,!1),this.yieldPos=i,this.awaitPos=r,this.awaitIdentPos=a,this.finishNode(s,"FunctionExpression")};$.parseArrowExpression=function(e,t,n,s){var i=this.yieldPos,r=this.awaitPos,a=this.awaitIdentPos;return this.enterScope(us(n,!1)|Qi),this.initFunction(e),this.options.ecmaVersion>=8&&(e.async=!!n),this.yieldPos=0,this.awaitPos=0,this.awaitIdentPos=0,e.params=this.toAssignableList(t,!0),this.parseFunctionBody(e,!0,!1,s),this.yieldPos=i,this.awaitPos=r,this.awaitIdentPos=a,this.finishNode(e,"ArrowFunctionExpression")};$.parseFunctionBody=function(e,t,n,s){var i=t&&this.type!==l.braceL,r=this.strict,a=!1;if(i)e.body=this.parseMaybeAssign(s),e.expression=!0,this.checkParams(e,!1);else{var o=this.options.ecmaVersion>=7&&!this.isSimpleParamList(e.params);(!r||o)&&(a=this.strictDirective(this.end),a&&o&&this.raiseRecoverable(e.start,"Illegal 'use strict' directive in function with non-simple parameter list"));var u=this.labels;this.labels=[],a&&(this.strict=!0),this.checkParams(e,!r&&!a&&!t&&!n&&this.isSimpleParamList(e.params)),this.strict&&e.id&&this.checkLValSimple(e.id,tr),e.body=this.parseBlock(!1,void 0,a&&!r),e.expression=!1,this.adaptDirectivePrologue(e.body.body),this.labels=u}this.exitScope()};$.isSimpleParamList=function(e){for(var t=0,n=e;t-1||i.functions.indexOf(e)>-1||i.var.indexOf(e)>-1,i.lexical.push(e),this.inModule&&i.flags&ct&&delete this.undefinedExports[e]}else if(t===er){var r=this.currentScope();r.lexical.push(e)}else if(t===Xi){var a=this.currentScope();this.treatFunctionsAsVar?s=a.lexical.indexOf(e)>-1:s=a.lexical.indexOf(e)>-1||a.var.indexOf(e)>-1,a.functions.push(e)}else for(var o=this.scopeStack.length-1;o>=0;--o){var u=this.scopeStack[o];if(u.lexical.indexOf(e)>-1&&!(u.flags&Ji&&u.lexical[0]===e)||!this.treatFunctionsAsVarInScope(u)&&u.functions.indexOf(e)>-1){s=!0;break}if(u.var.push(e),this.inModule&&u.flags&ct&&delete this.undefinedExports[e],u.flags&os)break}s&&this.raiseRecoverable(n,"Identifier '"+e+"' has already been declared")};De.checkLocalExport=function(e){this.scopeStack[0].lexical.indexOf(e.name)===-1&&this.scopeStack[0].var.indexOf(e.name)===-1&&(this.undefinedExports[e.name]=e)};De.currentScope=function(){return this.scopeStack[this.scopeStack.length-1]};De.currentVarScope=function(){for(var e=this.scopeStack.length-1;;e--){var t=this.scopeStack[e];if(t.flags&os)return t}};De.currentThisScope=function(){for(var e=this.scopeStack.length-1;;e--){var t=this.scopeStack[e];if(t.flags&os&&!(t.flags&Qi))return t}};var nn=function(e,t,n){this.type="",this.start=t,this.end=0,e.options.locations&&(this.loc=new Xt(e,n)),e.options.directSourceFile&&(this.sourceFile=e.options.directSourceFile),e.options.ranges&&(this.range=[t,0])},yt=Y.prototype;yt.startNode=function(){return new nn(this,this.start,this.startLoc)};yt.startNodeAt=function(e,t){return new nn(this,e,t)};function ir(e,t,n,s){return e.type=t,e.end=n,this.options.locations&&(e.loc.end=s),this.options.ranges&&(e.range[1]=n),e}yt.finishNode=function(e,t){return ir.call(this,e,t,this.lastTokEnd,this.lastTokEndLoc)};yt.finishNodeAt=function(e,t,n,s){return ir.call(this,e,t,n,s)};yt.copyNode=function(e){var t=new nn(this,e.start,this.startLoc);for(var n in e)t[n]=e[n];return t};var rr="ASCII ASCII_Hex_Digit AHex Alphabetic Alpha Any Assigned Bidi_Control Bidi_C Bidi_Mirrored Bidi_M Case_Ignorable CI Cased Changes_When_Casefolded CWCF Changes_When_Casemapped CWCM Changes_When_Lowercased CWL Changes_When_NFKC_Casefolded CWKCF Changes_When_Titlecased CWT Changes_When_Uppercased CWU Dash Default_Ignorable_Code_Point DI Deprecated Dep Diacritic Dia Emoji Emoji_Component Emoji_Modifier Emoji_Modifier_Base Emoji_Presentation Extender Ext Grapheme_Base Gr_Base Grapheme_Extend Gr_Ext Hex_Digit Hex IDS_Binary_Operator IDSB IDS_Trinary_Operator IDST ID_Continue IDC ID_Start IDS Ideographic Ideo Join_Control Join_C Logical_Order_Exception LOE Lowercase Lower Math Noncharacter_Code_Point NChar Pattern_Syntax Pat_Syn Pattern_White_Space Pat_WS Quotation_Mark QMark Radical Regional_Indicator RI Sentence_Terminal STerm Soft_Dotted SD Terminal_Punctuation Term Unified_Ideograph UIdeo Uppercase Upper Variation_Selector VS White_Space space XID_Continue XIDC XID_Start XIDS",ar=rr+" Extended_Pictographic",or=ar,ur=or+" EBase EComp EMod EPres ExtPict",lr=ur,Kf=lr,Yf={9:rr,10:ar,11:or,12:ur,13:lr,14:Kf},Qf="Basic_Emoji Emoji_Keycap_Sequence RGI_Emoji_Modifier_Sequence RGI_Emoji_Flag_Sequence RGI_Emoji_Tag_Sequence RGI_Emoji_ZWJ_Sequence RGI_Emoji",Jf={9:"",10:"",11:"",12:"",13:"",14:Qf},zs="Cased_Letter LC Close_Punctuation Pe Connector_Punctuation Pc Control Cc cntrl Currency_Symbol Sc Dash_Punctuation Pd Decimal_Number Nd digit Enclosing_Mark Me Final_Punctuation Pf Format Cf Initial_Punctuation Pi Letter L Letter_Number Nl Line_Separator Zl Lowercase_Letter Ll Mark M Combining_Mark Math_Symbol Sm Modifier_Letter Lm Modifier_Symbol Sk Nonspacing_Mark Mn Number N Open_Punctuation Ps Other C Other_Letter Lo Other_Number No Other_Punctuation Po Other_Symbol So Paragraph_Separator Zp Private_Use Co Punctuation P punct Separator Z Space_Separator Zs Spacing_Mark Mc Surrogate Cs Symbol S Titlecase_Letter Lt Unassigned Cn Uppercase_Letter Lu",cr="Adlam Adlm Ahom Anatolian_Hieroglyphs Hluw Arabic Arab Armenian Armn Avestan Avst Balinese Bali Bamum Bamu Bassa_Vah Bass Batak Batk Bengali Beng Bhaiksuki Bhks Bopomofo Bopo Brahmi Brah Braille Brai Buginese Bugi Buhid Buhd Canadian_Aboriginal Cans Carian Cari Caucasian_Albanian Aghb Chakma Cakm Cham Cham Cherokee Cher Common Zyyy Coptic Copt Qaac Cuneiform Xsux Cypriot Cprt Cyrillic Cyrl Deseret Dsrt Devanagari Deva Duployan Dupl Egyptian_Hieroglyphs Egyp Elbasan Elba Ethiopic Ethi Georgian Geor Glagolitic Glag Gothic Goth Grantha Gran Greek Grek Gujarati Gujr Gurmukhi Guru Han Hani Hangul Hang Hanunoo Hano Hatran Hatr Hebrew Hebr Hiragana Hira Imperial_Aramaic Armi Inherited Zinh Qaai Inscriptional_Pahlavi Phli Inscriptional_Parthian Prti Javanese Java Kaithi Kthi Kannada Knda Katakana Kana Kayah_Li Kali Kharoshthi Khar Khmer Khmr Khojki Khoj Khudawadi Sind Lao Laoo Latin Latn Lepcha Lepc Limbu Limb Linear_A Lina Linear_B Linb Lisu Lisu Lycian Lyci Lydian Lydi Mahajani Mahj Malayalam Mlym Mandaic Mand Manichaean Mani Marchen Marc Masaram_Gondi Gonm Meetei_Mayek Mtei Mende_Kikakui Mend Meroitic_Cursive Merc Meroitic_Hieroglyphs Mero Miao Plrd Modi Mongolian Mong Mro Mroo Multani Mult Myanmar Mymr Nabataean Nbat New_Tai_Lue Talu Newa Newa Nko Nkoo Nushu Nshu Ogham Ogam Ol_Chiki Olck Old_Hungarian Hung Old_Italic Ital Old_North_Arabian Narb Old_Permic Perm Old_Persian Xpeo Old_South_Arabian Sarb Old_Turkic Orkh Oriya Orya Osage Osge Osmanya Osma Pahawh_Hmong Hmng Palmyrene Palm Pau_Cin_Hau Pauc Phags_Pa Phag Phoenician Phnx Psalter_Pahlavi Phlp Rejang Rjng Runic Runr Samaritan Samr Saurashtra Saur Sharada Shrd Shavian Shaw Siddham Sidd SignWriting Sgnw Sinhala Sinh Sora_Sompeng Sora Soyombo Soyo Sundanese Sund Syloti_Nagri Sylo Syriac Syrc Tagalog Tglg Tagbanwa Tagb Tai_Le Tale Tai_Tham Lana Tai_Viet Tavt Takri Takr Tamil Taml Tangut Tang Telugu Telu Thaana Thaa Thai Thai Tibetan Tibt Tifinagh Tfng Tirhuta Tirh Ugaritic Ugar Vai Vaii Warang_Citi Wara Yi Yiii Zanabazar_Square Zanb",hr=cr+" Dogra Dogr Gunjala_Gondi Gong Hanifi_Rohingya Rohg Makasar Maka Medefaidrin Medf Old_Sogdian Sogo Sogdian Sogd",pr=hr+" Elymaic Elym Nandinagari Nand Nyiakeng_Puachue_Hmong Hmnp Wancho Wcho",dr=pr+" Chorasmian Chrs Diak Dives_Akuru Khitan_Small_Script Kits Yezi Yezidi",fr=dr+" Cypro_Minoan Cpmn Old_Uyghur Ougr Tangsa Tnsa Toto Vithkuqi Vith",Zf=fr+" Hrkt Katakana_Or_Hiragana Kawi Nag_Mundari Nagm Unknown Zzzz",Xf={9:cr,10:hr,11:pr,12:dr,13:fr,14:Zf},mr={};function em(e){var t=mr[e]={binary:Ie(Yf[e]+" "+zs),binaryOfStrings:Ie(Jf[e]),nonBinary:{General_Category:Ie(zs),Script:Ie(Xf[e])}};t.nonBinary.Script_Extensions=t.nonBinary.Script,t.nonBinary.gc=t.nonBinary.General_Category,t.nonBinary.sc=t.nonBinary.Script,t.nonBinary.scx=t.nonBinary.Script_Extensions}for(At=0,bn=[9,10,11,12,13,14];At=6?"uy":"")+(e.options.ecmaVersion>=9?"s":"")+(e.options.ecmaVersion>=13?"d":"")+(e.options.ecmaVersion>=15?"v":""),this.unicodeProperties=mr[e.options.ecmaVersion>=14?14:e.options.ecmaVersion],this.source="",this.flags="",this.start=0,this.switchU=!1,this.switchV=!1,this.switchN=!1,this.pos=0,this.lastIntValue=0,this.lastStringValue="",this.lastAssertionIsQuantifiable=!1,this.numCapturingParens=0,this.maxBackReference=0,this.groupNames=Object.create(null),this.backReferenceNames=[],this.branchID=null};fe.prototype.reset=function(e,t,n){var s=n.indexOf("v")!==-1,i=n.indexOf("u")!==-1;this.start=e|0,this.source=t+"",this.flags=n,s&&this.parser.options.ecmaVersion>=15?(this.switchU=!0,this.switchV=!0,this.switchN=!0):(this.switchU=i&&this.parser.options.ecmaVersion>=6,this.switchV=!1,this.switchN=i&&this.parser.options.ecmaVersion>=9)};fe.prototype.raise=function(e){this.parser.raiseRecoverable(this.start,"Invalid regular expression: /"+this.source+"/: "+e)};fe.prototype.at=function(e,t){t===void 0&&(t=!1);var n=this.source,s=n.length;if(e>=s)return-1;var i=n.charCodeAt(e);if(!(t||this.switchU)||i<=55295||i>=57344||e+1>=s)return i;var r=n.charCodeAt(e+1);return r>=56320&&r<=57343?(i<<10)+r-56613888:i};fe.prototype.nextIndex=function(e,t){t===void 0&&(t=!1);var n=this.source,s=n.length;if(e>=s)return s;var i=n.charCodeAt(e),r;return!(t||this.switchU)||i<=55295||i>=57344||e+1>=s||(r=n.charCodeAt(e+1))<56320||r>57343?e+1:e+2};fe.prototype.current=function(e){return e===void 0&&(e=!1),this.at(this.pos,e)};fe.prototype.lookahead=function(e){return e===void 0&&(e=!1),this.at(this.nextIndex(this.pos,e),e)};fe.prototype.advance=function(e){e===void 0&&(e=!1),this.pos=this.nextIndex(this.pos,e)};fe.prototype.eat=function(e,t){return t===void 0&&(t=!1),this.current(t)===e?(this.advance(t),!0):!1};fe.prototype.eatChars=function(e,t){t===void 0&&(t=!1);for(var n=this.pos,s=0,i=e;s-1&&this.raise(e.start,"Duplicate regular expression flag"),a==="u"&&(s=!0),a==="v"&&(i=!0)}this.options.ecmaVersion>=15&&s&&i&&this.raise(e.start,"Invalid regular expression flag")};function tm(e){for(var t in e)return!0;return!1}A.validateRegExpPattern=function(e){this.regexp_pattern(e),!e.switchN&&this.options.ecmaVersion>=9&&tm(e.groupNames)&&(e.switchN=!0,this.regexp_pattern(e))};A.regexp_pattern=function(e){e.pos=0,e.lastIntValue=0,e.lastStringValue="",e.lastAssertionIsQuantifiable=!1,e.numCapturingParens=0,e.maxBackReference=0,e.groupNames=Object.create(null),e.backReferenceNames.length=0,e.branchID=null,this.regexp_disjunction(e),e.pos!==e.source.length&&(e.eat(41)&&e.raise("Unmatched ')'"),(e.eat(93)||e.eat(125))&&e.raise("Lone quantifier brackets")),e.maxBackReference>e.numCapturingParens&&e.raise("Invalid escape");for(var t=0,n=e.backReferenceNames;t=16;for(t&&(e.branchID=new Ut(e.branchID,null)),this.regexp_alternative(e);e.eat(124);)t&&(e.branchID=e.branchID.sibling()),this.regexp_alternative(e);t&&(e.branchID=e.branchID.parent),this.regexp_eatQuantifier(e,!0)&&e.raise("Nothing to repeat"),e.eat(123)&&e.raise("Lone quantifier brackets")};A.regexp_alternative=function(e){for(;e.pos=9&&(n=e.eat(60)),e.eat(61)||e.eat(33))return this.regexp_disjunction(e),e.eat(41)||e.raise("Unterminated group"),e.lastAssertionIsQuantifiable=!n,!0}return e.pos=t,!1};A.regexp_eatQuantifier=function(e,t){return t===void 0&&(t=!1),this.regexp_eatQuantifierPrefix(e,t)?(e.eat(63),!0):!1};A.regexp_eatQuantifierPrefix=function(e,t){return e.eat(42)||e.eat(43)||e.eat(63)||this.regexp_eatBracedQuantifier(e,t)};A.regexp_eatBracedQuantifier=function(e,t){var n=e.pos;if(e.eat(123)){var s=0,i=-1;if(this.regexp_eatDecimalDigits(e)&&(s=e.lastIntValue,e.eat(44)&&this.regexp_eatDecimalDigits(e)&&(i=e.lastIntValue),e.eat(125)))return i!==-1&&i=9?this.regexp_groupSpecifier(e):e.current()===63&&e.raise("Invalid group"),this.regexp_disjunction(e),e.eat(41))return e.numCapturingParens+=1,!0;e.raise("Unterminated group")}return!1};A.regexp_eatExtendedAtom=function(e){return e.eat(46)||this.regexp_eatReverseSolidusAtomEscape(e)||this.regexp_eatCharacterClass(e)||this.regexp_eatUncapturingGroup(e)||this.regexp_eatCapturingGroup(e)||this.regexp_eatInvalidBracedQuantifier(e)||this.regexp_eatExtendedPatternCharacter(e)};A.regexp_eatInvalidBracedQuantifier=function(e){return this.regexp_eatBracedQuantifier(e,!0)&&e.raise("Nothing to repeat"),!1};A.regexp_eatSyntaxCharacter=function(e){var t=e.current();return vr(t)?(e.lastIntValue=t,e.advance(),!0):!1};function vr(e){return e===36||e>=40&&e<=43||e===46||e===63||e>=91&&e<=94||e>=123&&e<=125}A.regexp_eatPatternCharacters=function(e){for(var t=e.pos,n=0;(n=e.current())!==-1&&!vr(n);)e.advance();return e.pos!==t};A.regexp_eatExtendedPatternCharacter=function(e){var t=e.current();return t!==-1&&t!==36&&!(t>=40&&t<=43)&&t!==46&&t!==63&&t!==91&&t!==94&&t!==124?(e.advance(),!0):!1};A.regexp_groupSpecifier=function(e){if(e.eat(63)){this.regexp_eatGroupName(e)||e.raise("Invalid group");var t=this.options.ecmaVersion>=16,n=e.groupNames[e.lastStringValue];if(n)if(t)for(var s=0,i=n;s=11,s=e.current(n);return e.advance(n),s===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e,n)&&(s=e.lastIntValue),nm(s)?(e.lastIntValue=s,!0):(e.pos=t,!1)};function nm(e){return xe(e,!0)||e===36||e===95}A.regexp_eatRegExpIdentifierPart=function(e){var t=e.pos,n=this.options.ecmaVersion>=11,s=e.current(n);return e.advance(n),s===92&&this.regexp_eatRegExpUnicodeEscapeSequence(e,n)&&(s=e.lastIntValue),sm(s)?(e.lastIntValue=s,!0):(e.pos=t,!1)};function sm(e){return Ge(e,!0)||e===36||e===95||e===8204||e===8205}A.regexp_eatAtomEscape=function(e){return this.regexp_eatBackReference(e)||this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)||e.switchN&&this.regexp_eatKGroupName(e)?!0:(e.switchU&&(e.current()===99&&e.raise("Invalid unicode escape"),e.raise("Invalid escape")),!1)};A.regexp_eatBackReference=function(e){var t=e.pos;if(this.regexp_eatDecimalEscape(e)){var n=e.lastIntValue;if(e.switchU)return n>e.maxBackReference&&(e.maxBackReference=n),!0;if(n<=e.numCapturingParens)return!0;e.pos=t}return!1};A.regexp_eatKGroupName=function(e){if(e.eat(107)){if(this.regexp_eatGroupName(e))return e.backReferenceNames.push(e.lastStringValue),!0;e.raise("Invalid named reference")}return!1};A.regexp_eatCharacterEscape=function(e){return this.regexp_eatControlEscape(e)||this.regexp_eatCControlLetter(e)||this.regexp_eatZero(e)||this.regexp_eatHexEscapeSequence(e)||this.regexp_eatRegExpUnicodeEscapeSequence(e,!1)||!e.switchU&&this.regexp_eatLegacyOctalEscapeSequence(e)||this.regexp_eatIdentityEscape(e)};A.regexp_eatCControlLetter=function(e){var t=e.pos;if(e.eat(99)){if(this.regexp_eatControlLetter(e))return!0;e.pos=t}return!1};A.regexp_eatZero=function(e){return e.current()===48&&!sn(e.lookahead())?(e.lastIntValue=0,e.advance(),!0):!1};A.regexp_eatControlEscape=function(e){var t=e.current();return t===116?(e.lastIntValue=9,e.advance(),!0):t===110?(e.lastIntValue=10,e.advance(),!0):t===118?(e.lastIntValue=11,e.advance(),!0):t===102?(e.lastIntValue=12,e.advance(),!0):t===114?(e.lastIntValue=13,e.advance(),!0):!1};A.regexp_eatControlLetter=function(e){var t=e.current();return gr(t)?(e.lastIntValue=t%32,e.advance(),!0):!1};function gr(e){return e>=65&&e<=90||e>=97&&e<=122}A.regexp_eatRegExpUnicodeEscapeSequence=function(e,t){t===void 0&&(t=!1);var n=e.pos,s=t||e.switchU;if(e.eat(117)){if(this.regexp_eatFixedHexDigits(e,4)){var i=e.lastIntValue;if(s&&i>=55296&&i<=56319){var r=e.pos;if(e.eat(92)&&e.eat(117)&&this.regexp_eatFixedHexDigits(e,4)){var a=e.lastIntValue;if(a>=56320&&a<=57343)return e.lastIntValue=(i-55296)*1024+(a-56320)+65536,!0}e.pos=r,e.lastIntValue=i}return!0}if(s&&e.eat(123)&&this.regexp_eatHexDigits(e)&&e.eat(125)&&im(e.lastIntValue))return!0;s&&e.raise("Invalid unicode escape"),e.pos=n}return!1};function im(e){return e>=0&&e<=1114111}A.regexp_eatIdentityEscape=function(e){if(e.switchU)return this.regexp_eatSyntaxCharacter(e)?!0:e.eat(47)?(e.lastIntValue=47,!0):!1;var t=e.current();return t!==99&&(!e.switchN||t!==107)?(e.lastIntValue=t,e.advance(),!0):!1};A.regexp_eatDecimalEscape=function(e){e.lastIntValue=0;var t=e.current();if(t>=49&&t<=57){do e.lastIntValue=10*e.lastIntValue+(t-48),e.advance();while((t=e.current())>=48&&t<=57);return!0}return!1};var br=0,Ee=1,ae=2;A.regexp_eatCharacterClassEscape=function(e){var t=e.current();if(rm(t))return e.lastIntValue=-1,e.advance(),Ee;var n=!1;if(e.switchU&&this.options.ecmaVersion>=9&&((n=t===80)||t===112)){e.lastIntValue=-1,e.advance();var s;if(e.eat(123)&&(s=this.regexp_eatUnicodePropertyValueExpression(e))&&e.eat(125))return n&&s===ae&&e.raise("Invalid property name"),s;e.raise("Invalid property name")}return br};function rm(e){return e===100||e===68||e===115||e===83||e===119||e===87}A.regexp_eatUnicodePropertyValueExpression=function(e){var t=e.pos;if(this.regexp_eatUnicodePropertyName(e)&&e.eat(61)){var n=e.lastStringValue;if(this.regexp_eatUnicodePropertyValue(e)){var s=e.lastStringValue;return this.regexp_validateUnicodePropertyNameAndValue(e,n,s),Ee}}if(e.pos=t,this.regexp_eatLoneUnicodePropertyNameOrValue(e)){var i=e.lastStringValue;return this.regexp_validateUnicodePropertyNameOrValue(e,i)}return br};A.regexp_validateUnicodePropertyNameAndValue=function(e,t,n){gt(e.unicodeProperties.nonBinary,t)||e.raise("Invalid property name"),e.unicodeProperties.nonBinary[t].test(n)||e.raise("Invalid property value")};A.regexp_validateUnicodePropertyNameOrValue=function(e,t){if(e.unicodeProperties.binary.test(t))return Ee;if(e.switchV&&e.unicodeProperties.binaryOfStrings.test(t))return ae;e.raise("Invalid property name")};A.regexp_eatUnicodePropertyName=function(e){var t=0;for(e.lastStringValue="";yr(t=e.current());)e.lastStringValue+=Fe(t),e.advance();return e.lastStringValue!==""};function yr(e){return gr(e)||e===95}A.regexp_eatUnicodePropertyValue=function(e){var t=0;for(e.lastStringValue="";am(t=e.current());)e.lastStringValue+=Fe(t),e.advance();return e.lastStringValue!==""};function am(e){return yr(e)||sn(e)}A.regexp_eatLoneUnicodePropertyNameOrValue=function(e){return this.regexp_eatUnicodePropertyValue(e)};A.regexp_eatCharacterClass=function(e){if(e.eat(91)){var t=e.eat(94),n=this.regexp_classContents(e);return e.eat(93)||e.raise("Unterminated character class"),t&&n===ae&&e.raise("Negated character class may contain strings"),!0}return!1};A.regexp_classContents=function(e){return e.current()===93?Ee:e.switchV?this.regexp_classSetExpression(e):(this.regexp_nonEmptyClassRanges(e),Ee)};A.regexp_nonEmptyClassRanges=function(e){for(;this.regexp_eatClassAtom(e);){var t=e.lastIntValue;if(e.eat(45)&&this.regexp_eatClassAtom(e)){var n=e.lastIntValue;e.switchU&&(t===-1||n===-1)&&e.raise("Invalid character class"),t!==-1&&n!==-1&&t>n&&e.raise("Range out of order in character class")}}};A.regexp_eatClassAtom=function(e){var t=e.pos;if(e.eat(92)){if(this.regexp_eatClassEscape(e))return!0;if(e.switchU){var n=e.current();(n===99||xr(n))&&e.raise("Invalid class escape"),e.raise("Invalid escape")}e.pos=t}var s=e.current();return s!==93?(e.lastIntValue=s,e.advance(),!0):!1};A.regexp_eatClassEscape=function(e){var t=e.pos;if(e.eat(98))return e.lastIntValue=8,!0;if(e.switchU&&e.eat(45))return e.lastIntValue=45,!0;if(!e.switchU&&e.eat(99)){if(this.regexp_eatClassControlLetter(e))return!0;e.pos=t}return this.regexp_eatCharacterClassEscape(e)||this.regexp_eatCharacterEscape(e)};A.regexp_classSetExpression=function(e){var t=Ee,n;if(!this.regexp_eatClassSetRange(e))if(n=this.regexp_eatClassSetOperand(e)){n===ae&&(t=ae);for(var s=e.pos;e.eatChars([38,38]);){if(e.current()!==38&&(n=this.regexp_eatClassSetOperand(e))){n!==ae&&(t=Ee);continue}e.raise("Invalid character in character class")}if(s!==e.pos)return t;for(;e.eatChars([45,45]);)this.regexp_eatClassSetOperand(e)||e.raise("Invalid character in character class");if(s!==e.pos)return t}else e.raise("Invalid character in character class");for(;;)if(!this.regexp_eatClassSetRange(e)){if(n=this.regexp_eatClassSetOperand(e),!n)return t;n===ae&&(t=ae)}};A.regexp_eatClassSetRange=function(e){var t=e.pos;if(this.regexp_eatClassSetCharacter(e)){var n=e.lastIntValue;if(e.eat(45)&&this.regexp_eatClassSetCharacter(e)){var s=e.lastIntValue;return n!==-1&&s!==-1&&n>s&&e.raise("Range out of order in character class"),!0}e.pos=t}return!1};A.regexp_eatClassSetOperand=function(e){return this.regexp_eatClassSetCharacter(e)?Ee:this.regexp_eatClassStringDisjunction(e)||this.regexp_eatNestedClass(e)};A.regexp_eatNestedClass=function(e){var t=e.pos;if(e.eat(91)){var n=e.eat(94),s=this.regexp_classContents(e);if(e.eat(93))return n&&s===ae&&e.raise("Negated character class may contain strings"),s;e.pos=t}if(e.eat(92)){var i=this.regexp_eatCharacterClassEscape(e);if(i)return i;e.pos=t}return null};A.regexp_eatClassStringDisjunction=function(e){var t=e.pos;if(e.eatChars([92,113])){if(e.eat(123)){var n=this.regexp_classStringDisjunctionContents(e);if(e.eat(125))return n}else e.raise("Invalid escape");e.pos=t}return null};A.regexp_classStringDisjunctionContents=function(e){for(var t=this.regexp_classString(e);e.eat(124);)this.regexp_classString(e)===ae&&(t=ae);return t};A.regexp_classString=function(e){for(var t=0;this.regexp_eatClassSetCharacter(e);)t++;return t===1?Ee:ae};A.regexp_eatClassSetCharacter=function(e){var t=e.pos;if(e.eat(92))return this.regexp_eatCharacterEscape(e)||this.regexp_eatClassSetReservedPunctuator(e)?!0:e.eat(98)?(e.lastIntValue=8,!0):(e.pos=t,!1);var n=e.current();return n<0||n===e.lookahead()&&om(n)||um(n)?!1:(e.advance(),e.lastIntValue=n,!0)};function om(e){return e===33||e>=35&&e<=38||e>=42&&e<=44||e===46||e>=58&&e<=64||e===94||e===96||e===126}function um(e){return e===40||e===41||e===45||e===47||e>=91&&e<=93||e>=123&&e<=125}A.regexp_eatClassSetReservedPunctuator=function(e){var t=e.current();return lm(t)?(e.lastIntValue=t,e.advance(),!0):!1};function lm(e){return e===33||e===35||e===37||e===38||e===44||e===45||e>=58&&e<=62||e===64||e===96||e===126}A.regexp_eatClassControlLetter=function(e){var t=e.current();return sn(t)||t===95?(e.lastIntValue=t%32,e.advance(),!0):!1};A.regexp_eatHexEscapeSequence=function(e){var t=e.pos;if(e.eat(120)){if(this.regexp_eatFixedHexDigits(e,2))return!0;e.switchU&&e.raise("Invalid escape"),e.pos=t}return!1};A.regexp_eatDecimalDigits=function(e){var t=e.pos,n=0;for(e.lastIntValue=0;sn(n=e.current());)e.lastIntValue=10*e.lastIntValue+(n-48),e.advance();return e.pos!==t};function sn(e){return e>=48&&e<=57}A.regexp_eatHexDigits=function(e){var t=e.pos,n=0;for(e.lastIntValue=0;_r(n=e.current());)e.lastIntValue=16*e.lastIntValue+wr(n),e.advance();return e.pos!==t};function _r(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function wr(e){return e>=65&&e<=70?10+(e-65):e>=97&&e<=102?10+(e-97):e-48}A.regexp_eatLegacyOctalEscapeSequence=function(e){if(this.regexp_eatOctalDigit(e)){var t=e.lastIntValue;if(this.regexp_eatOctalDigit(e)){var n=e.lastIntValue;t<=3&&this.regexp_eatOctalDigit(e)?e.lastIntValue=t*64+n*8+e.lastIntValue:e.lastIntValue=t*8+n}else e.lastIntValue=t;return!0}return!1};A.regexp_eatOctalDigit=function(e){var t=e.current();return xr(t)?(e.lastIntValue=t-48,e.advance(),!0):(e.lastIntValue=0,!1)};function xr(e){return e>=48&&e<=55}A.regexp_eatFixedHexDigits=function(e,t){var n=e.pos;e.lastIntValue=0;for(var s=0;s=this.input.length)return this.finishToken(l.eof);if(e.override)return e.override(this);this.readToken(this.fullCharCodeAtPos())};O.readToken=function(e){return xe(e,this.options.ecmaVersion>=6)||e===92?this.readWord():this.getTokenFromCode(e)};O.fullCharCodeAtPos=function(){var e=this.input.charCodeAt(this.pos);if(e<=55295||e>=56320)return e;var t=this.input.charCodeAt(this.pos+1);return t<=56319||t>=57344?e:(e<<10)+t-56613888};O.skipBlockComment=function(){var e=this.options.onComment&&this.curPosition(),t=this.pos,n=this.input.indexOf("*/",this.pos+=2);if(n===-1&&this.raise(this.pos-2,"Unterminated comment"),this.pos=n+2,this.options.locations)for(var s=void 0,i=t;(s=Gi(this.input,i,this.pos))>-1;)++this.curLine,i=this.lineStart=s;this.options.onComment&&this.options.onComment(!0,this.input.slice(t+2,n),t,this.pos,e,this.curPosition())};O.skipLineComment=function(e){for(var t=this.pos,n=this.options.onComment&&this.curPosition(),s=this.input.charCodeAt(this.pos+=e);this.pos8&&e<14||e>=5760&&Wi.test(String.fromCharCode(e)))++this.pos;else break e}}};O.finishToken=function(e,t){this.end=this.pos,this.options.locations&&(this.endLoc=this.curPosition());var n=this.type;this.type=e,this.value=t,this.updateContext(n)};O.readToken_dot=function(){var e=this.input.charCodeAt(this.pos+1);if(e>=48&&e<=57)return this.readNumber(!0);var t=this.input.charCodeAt(this.pos+2);return this.options.ecmaVersion>=6&&e===46&&t===46?(this.pos+=3,this.finishToken(l.ellipsis)):(++this.pos,this.finishToken(l.dot))};O.readToken_slash=function(){var e=this.input.charCodeAt(this.pos+1);return this.exprAllowed?(++this.pos,this.readRegexp()):e===61?this.finishOp(l.assign,2):this.finishOp(l.slash,1)};O.readToken_mult_modulo_exp=function(e){var t=this.input.charCodeAt(this.pos+1),n=1,s=e===42?l.star:l.modulo;return this.options.ecmaVersion>=7&&e===42&&t===42&&(++n,s=l.starstar,t=this.input.charCodeAt(this.pos+2)),t===61?this.finishOp(l.assign,n+1):this.finishOp(s,n)};O.readToken_pipe_amp=function(e){var t=this.input.charCodeAt(this.pos+1);if(t===e){if(this.options.ecmaVersion>=12){var n=this.input.charCodeAt(this.pos+2);if(n===61)return this.finishOp(l.assign,3)}return this.finishOp(e===124?l.logicalOR:l.logicalAND,2)}return t===61?this.finishOp(l.assign,2):this.finishOp(e===124?l.bitwiseOR:l.bitwiseAND,1)};O.readToken_caret=function(){var e=this.input.charCodeAt(this.pos+1);return e===61?this.finishOp(l.assign,2):this.finishOp(l.bitwiseXOR,1)};O.readToken_plus_min=function(e){var t=this.input.charCodeAt(this.pos+1);return t===e?t===45&&!this.inModule&&this.input.charCodeAt(this.pos+2)===62&&(this.lastTokEnd===0||ue.test(this.input.slice(this.lastTokEnd,this.pos)))?(this.skipLineComment(3),this.skipSpace(),this.nextToken()):this.finishOp(l.incDec,2):t===61?this.finishOp(l.assign,2):this.finishOp(l.plusMin,1)};O.readToken_lt_gt=function(e){var t=this.input.charCodeAt(this.pos+1),n=1;return t===e?(n=e===62&&this.input.charCodeAt(this.pos+2)===62?3:2,this.input.charCodeAt(this.pos+n)===61?this.finishOp(l.assign,n+1):this.finishOp(l.bitShift,n)):t===33&&e===60&&!this.inModule&&this.input.charCodeAt(this.pos+2)===45&&this.input.charCodeAt(this.pos+3)===45?(this.skipLineComment(4),this.skipSpace(),this.nextToken()):(t===61&&(n=2),this.finishOp(l.relational,n))};O.readToken_eq_excl=function(e){var t=this.input.charCodeAt(this.pos+1);return t===61?this.finishOp(l.equality,this.input.charCodeAt(this.pos+2)===61?3:2):e===61&&t===62&&this.options.ecmaVersion>=6?(this.pos+=2,this.finishToken(l.arrow)):this.finishOp(e===61?l.eq:l.prefix,1)};O.readToken_question=function(){var e=this.options.ecmaVersion;if(e>=11){var t=this.input.charCodeAt(this.pos+1);if(t===46){var n=this.input.charCodeAt(this.pos+2);if(n<48||n>57)return this.finishOp(l.questionDot,2)}if(t===63){if(e>=12){var s=this.input.charCodeAt(this.pos+2);if(s===61)return this.finishOp(l.assign,3)}return this.finishOp(l.coalesce,2)}}return this.finishOp(l.question,1)};O.readToken_numberSign=function(){var e=this.options.ecmaVersion,t=35;if(e>=13&&(++this.pos,t=this.fullCharCodeAtPos(),xe(t,!0)||t===92))return this.finishToken(l.privateId,this.readWord1());this.raise(this.pos,"Unexpected character '"+Fe(t)+"'")};O.getTokenFromCode=function(e){switch(e){case 46:return this.readToken_dot();case 40:return++this.pos,this.finishToken(l.parenL);case 41:return++this.pos,this.finishToken(l.parenR);case 59:return++this.pos,this.finishToken(l.semi);case 44:return++this.pos,this.finishToken(l.comma);case 91:return++this.pos,this.finishToken(l.bracketL);case 93:return++this.pos,this.finishToken(l.bracketR);case 123:return++this.pos,this.finishToken(l.braceL);case 125:return++this.pos,this.finishToken(l.braceR);case 58:return++this.pos,this.finishToken(l.colon);case 96:if(this.options.ecmaVersion<6)break;return++this.pos,this.finishToken(l.backQuote);case 48:var t=this.input.charCodeAt(this.pos+1);if(t===120||t===88)return this.readRadixNumber(16);if(this.options.ecmaVersion>=6){if(t===111||t===79)return this.readRadixNumber(8);if(t===98||t===66)return this.readRadixNumber(2)}case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return this.readNumber(!1);case 34:case 39:return this.readString(e);case 47:return this.readToken_slash();case 37:case 42:return this.readToken_mult_modulo_exp(e);case 124:case 38:return this.readToken_pipe_amp(e);case 94:return this.readToken_caret();case 43:case 45:return this.readToken_plus_min(e);case 60:case 62:return this.readToken_lt_gt(e);case 61:case 33:return this.readToken_eq_excl(e);case 63:return this.readToken_question();case 126:return this.finishOp(l.prefix,1);case 35:return this.readToken_numberSign()}this.raise(this.pos,"Unexpected character '"+Fe(e)+"'")};O.finishOp=function(e,t){var n=this.input.slice(this.pos,this.pos+t);return this.pos+=t,this.finishToken(e,n)};O.readRegexp=function(){for(var e,t,n=this.pos;;){this.pos>=this.input.length&&this.raise(n,"Unterminated regular expression");var s=this.input.charAt(this.pos);if(ue.test(s)&&this.raise(n,"Unterminated regular expression"),e)e=!1;else{if(s==="[")t=!0;else if(s==="]"&&t)t=!1;else if(s==="/"&&!t)break;e=s==="\\"}++this.pos}var i=this.input.slice(n,this.pos);++this.pos;var r=this.pos,a=this.readWord1();this.containsEsc&&this.unexpected(r);var o=this.regexpState||(this.regexpState=new fe(this));o.reset(n,i,a),this.validateRegExpFlags(o),this.validateRegExpPattern(o);var u=null;try{u=new RegExp(i,a)}catch{}return this.finishToken(l.regexp,{pattern:i,flags:a,value:u})};O.readInt=function(e,t,n){for(var s=this.options.ecmaVersion>=12&&t===void 0,i=n&&this.input.charCodeAt(this.pos)===48,r=this.pos,a=0,o=0,u=0,c=t??1/0;u=97?p=h-97+10:h>=65?p=h-65+10:h>=48&&h<=57?p=h-48:p=1/0,p>=e)break;o=h,a=a*e+p}return s&&o===95&&this.raiseRecoverable(this.pos-1,"Numeric separator is not allowed at the last of digits"),this.pos===r||t!=null&&this.pos-r!==t?null:a};function cm(e,t){return t?parseInt(e,8):parseFloat(e.replace(/_/g,""))}function Er(e){return typeof BigInt!="function"?null:BigInt(e.replace(/_/g,""))}O.readRadixNumber=function(e){var t=this.pos;this.pos+=2;var n=this.readInt(e);return n==null&&this.raise(this.start+2,"Expected number in radix "+e),this.options.ecmaVersion>=11&&this.input.charCodeAt(this.pos)===110?(n=Er(this.input.slice(t,this.pos)),++this.pos):xe(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number"),this.finishToken(l.num,n)};O.readNumber=function(e){var t=this.pos;!e&&this.readInt(10,void 0,!0)===null&&this.raise(t,"Invalid number");var n=this.pos-t>=2&&this.input.charCodeAt(t)===48;n&&this.strict&&this.raise(t,"Invalid number");var s=this.input.charCodeAt(this.pos);if(!n&&!e&&this.options.ecmaVersion>=11&&s===110){var i=Er(this.input.slice(t,this.pos));return++this.pos,xe(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number"),this.finishToken(l.num,i)}n&&/[89]/.test(this.input.slice(t,this.pos))&&(n=!1),s===46&&!n&&(++this.pos,this.readInt(10),s=this.input.charCodeAt(this.pos)),(s===69||s===101)&&!n&&(s=this.input.charCodeAt(++this.pos),(s===43||s===45)&&++this.pos,this.readInt(10)===null&&this.raise(t,"Invalid number")),xe(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number");var r=cm(this.input.slice(t,this.pos),n);return this.finishToken(l.num,r)};O.readCodePoint=function(){var e=this.input.charCodeAt(this.pos),t;if(e===123){this.options.ecmaVersion<6&&this.unexpected();var n=++this.pos;t=this.readHexChar(this.input.indexOf("}",this.pos)-this.pos),++this.pos,t>1114111&&this.invalidStringToken(n,"Code point out of bounds")}else t=this.readHexChar(4);return t};O.readString=function(e){for(var t="",n=++this.pos;;){this.pos>=this.input.length&&this.raise(this.start,"Unterminated string constant");var s=this.input.charCodeAt(this.pos);if(s===e)break;s===92?(t+=this.input.slice(n,this.pos),t+=this.readEscapedChar(!1),n=this.pos):s===8232||s===8233?(this.options.ecmaVersion<10&&this.raise(this.start,"Unterminated string constant"),++this.pos,this.options.locations&&(this.curLine++,this.lineStart=this.pos)):(Ke(s)&&this.raise(this.start,"Unterminated string constant"),++this.pos)}return t+=this.input.slice(n,this.pos++),this.finishToken(l.string,t)};var Ar={};O.tryReadTemplateToken=function(){this.inTemplateElement=!0;try{this.readTmplToken()}catch(e){if(e===Ar)this.readInvalidTemplateToken();else throw e}this.inTemplateElement=!1};O.invalidStringToken=function(e,t){if(this.inTemplateElement&&this.options.ecmaVersion>=9)throw Ar;this.raise(e,t)};O.readTmplToken=function(){for(var e="",t=this.pos;;){this.pos>=this.input.length&&this.raise(this.start,"Unterminated template");var n=this.input.charCodeAt(this.pos);if(n===96||n===36&&this.input.charCodeAt(this.pos+1)===123)return this.pos===this.start&&(this.type===l.template||this.type===l.invalidTemplate)?n===36?(this.pos+=2,this.finishToken(l.dollarBraceL)):(++this.pos,this.finishToken(l.backQuote)):(e+=this.input.slice(t,this.pos),this.finishToken(l.template,e));if(n===92)e+=this.input.slice(t,this.pos),e+=this.readEscapedChar(!0),t=this.pos;else if(Ke(n)){switch(e+=this.input.slice(t,this.pos),++this.pos,n){case 13:this.input.charCodeAt(this.pos)===10&&++this.pos;case 10:e+=` +`;break;default:e+=String.fromCharCode(n);break}this.options.locations&&(++this.curLine,this.lineStart=this.pos),t=this.pos}else++this.pos}};O.readInvalidTemplateToken=function(){for(;this.pos=48&&t<=55){var s=this.input.substr(this.pos-1,3).match(/^[0-7]+/)[0],i=parseInt(s,8);return i>255&&(s=s.slice(0,-1),i=parseInt(s,8)),this.pos+=s.length-1,t=this.input.charCodeAt(this.pos),(s!=="0"||t===56||t===57)&&(this.strict||e)&&this.invalidStringToken(this.pos-1-s.length,e?"Octal literal in template string":"Octal literal in strict mode"),String.fromCharCode(i)}return Ke(t)?(this.options.locations&&(this.lineStart=this.pos,++this.curLine),""):String.fromCharCode(t)}};O.readHexChar=function(e){var t=this.pos,n=this.readInt(16,e);return n===null&&this.invalidStringToken(t,"Bad character escape sequence"),n};O.readWord1=function(){this.containsEsc=!1;for(var e="",t=!0,n=this.pos,s=this.options.ecmaVersion>=6;this.posp(C,x)),x.id&&o(x,x.id)}function h(x){x.param&&p(x.param,x)}function p(x,C){switch(x.type){case"Identifier":o(C,x);break;case"ObjectPattern":x.properties.forEach(D=>p(D,C));break;case"ArrayPattern":x.elements.forEach(D=>D&&p(D,C));break;case"Property":p(x.value,C);break;case"RestElement":p(x.argument,C);break;case"AssignmentPattern":p(x.left,C);break;default:throw new Error("Unrecognized pattern type: "+x.type)}}function m(x){o(n,x.local)}It(n,{VariableDeclaration:(x,C)=>{let D=null;for(let R=C.length-1;R>=0&&D===null;--R)(x.kind==="var"?Tt(C[R]):fm(C[R]))&&(D=C[R]);x.declarations.forEach(R=>p(R.id,D))},FunctionDeclaration:(x,C)=>{let D=null;for(let R=C.length-2;R>=0&&D===null;--R)Tt(C[R])&&(D=C[R]);o(D,x.id),c(x)},Function:c,ClassDeclaration:(x,C)=>{let D=null;for(let R=C.length-2;R>=0&&D===null;R--)Tt(C[R])&&(D=C[R]);o(D,x.id)},Class:u,CatchClause:h,ImportDefaultSpecifier:m,ImportSpecifier:m,ImportNamespaceSpecifier:m},rt);function b(x,C){let D=x.name;if(D!=="undefined"){for(let R=C.length-2;R>=0;--R){if(D==="arguments"&&mm(C[R])||a(C[R],D))return;C[R].type==="ViewExpression"&&(x=C[R],D=`viewof ${x.id.name}`),C[R].type==="MutableExpression"&&(x=C[R],D=`mutable ${x.id.name}`)}if(!i.has(D)){if(D==="arguments")throw Object.assign(new SyntaxError("arguments is not allowed"),{node:x});r.push(x)}}}It(n,{VariablePattern:b,Identifier:b},rt);function w(x,C){if(x)switch(x.type){case"Identifier":case"VariablePattern":{for(let D of C)if(a(D,x.name))return;if(C[C.length-2].type==="MutableExpression")return;throw Object.assign(new SyntaxError(`Assignment to constant variable ${x.name}`),{node:x})}case"ArrayPattern":{for(let D of x.elements)w(D,C);return}case"ObjectPattern":{for(let D of x.properties)w(D,C);return}case"Property":{w(x.value,C);return}case"RestElement":{w(x.argument,C);return}}}function N(x,C){w(x.argument,C)}function I(x,C){w(x.left,C)}return It(n,{AssignmentExpression:I,AssignmentPattern:I,UpdateExpression:N,ForOfStatement:I,ForInStatement:I},rt),r}function kt(e,t){let n={type:"Program",body:[e.body]},s=new Map,{references:i}=e;return pm(n,{CallExpression:r=>{let{callee:a,arguments:o}=r;if(a.type!=="Identifier"||a.name!==t||i.indexOf(a)<0)return;if(o.length!==1||!(o[0].type==="Literal"&&/^['"]/.test(o[0].raw)||o[0].type==="TemplateLiteral"&&o[0].expressions.length===0))throw Object.assign(new SyntaxError(`${t} requires a single literal string argument`),{node:r});let[u]=o,c=u.type==="Literal"?u.value:u.quasis[0].value.cooked,h={start:u.start,end:u.end};s.has(c)?s.get(c).push(h):s.set(c,[h])}},rt),s}var He=2,Cr=4,Sr=8;function gm(e,{tag:t,raw:n,globals:s,...i}={}){let r;if(t!=null&&e){r=ym.parse(e,i);let a=wm.parse(t,i);Ws(a,t,s),Ks(a,t),r.tag=a,r.raw=!!n}else r=ds.parse(e,i);return Ws(r,e,s),Ks(r,e),r}var ds=class Pr extends Y{constructor(t,...n){super(Object.assign({ecmaVersion:13},t),...n)}enterScope(t){return t&He&&++this.O_function,super.enterScope(t)}exitScope(){return this.currentScope().flags&He&&--this.O_function,super.exitScope()}parseForIn(t,n){return this.O_function===1&&t.await&&(this.O_async=!0),super.parseForIn(t,n)}parseAwait(){return this.O_function===1&&(this.O_async=!0),super.parseAwait()}parseYield(t){return this.O_function===1&&(this.O_generator=!0),super.parseYield(t)}parseImport(t){return this.next(),t.specifiers=this.parseImportSpecifiers(),this.type===l._with&&(this.next(),t.injections=this.parseImportSpecifiers()),this.expectContextual("from"),t.source=this.type===l.string?this.parseExprAtom():this.unexpected(),this.finishNode(t,"ImportDeclaration")}parseImportSpecifiers(){let t=[],n=new Set,s=!0;for(this.expect(l.braceL);!this.eat(l.braceR);){if(s)s=!1;else if(this.expect(l.comma),this.afterTrailingComma(l.braceR))break;let i=this.startNode();i.view=this.eatContextual("viewof"),i.mutable=i.view?!1:this.eatContextual("mutable"),i.imported=this.parseIdent(),this.checkUnreserved(i.imported),this.checkLocal(i.imported),this.eatContextual("as")?(i.local=this.parseIdent(),this.checkUnreserved(i.local),this.checkLocal(i.local)):i.local=i.imported,this.checkLValSimple(i.local,"let"),n.has(i.local.name)&&this.raise(i.local.start,`Identifier '${i.local.name}' has already been declared`),n.add(i.local.name),t.push(this.finishNode(i,"ImportSpecifier"))}return t}parseExprAtom(t){return this.parseMaybeKeywordExpression("viewof","ViewExpression")||this.parseMaybeKeywordExpression("mutable","MutableExpression")||super.parseExprAtom(t)}startCell(){this.O_function=0,this.O_async=!1,this.O_generator=!1,this.strict=!0,this.enterScope(He|Cr|Sr)}finishCell(t,n,s){return s&&this.checkLocal(s),t.id=s,t.body=n,t.async=this.O_async,t.generator=this.O_generator,this.exitScope(),this.finishNode(t,"Cell")}parseCell(t,n){let s=new Pr({},this.input,this.start),i=s.getToken(),r=null,a=null;return this.startCell(),i.type===l._import&&s.getToken().type!==l.parenL?r=this.parseImport(this.startNode()):i.type!==l.eof&&i.type!==l.semi&&(i.type===l.name&&((i.value==="viewof"||i.value==="mutable")&&(i=s.getToken(),i.type!==l.name&&s.unexpected()),i=s.getToken(),i.type===l.eq&&(a=this.parseMaybeKeywordExpression("viewof","ViewExpression")||this.parseMaybeKeywordExpression("mutable","MutableExpression")||this.parseIdent(),i=s.getToken(),this.expect(l.eq))),i.type===l.braceL?r=this.parseBlock():(r=this.parseExpression(),a===null&&(r.type==="FunctionExpression"||r.type==="ClassExpression")&&(a=r.id))),this.semicolon(),n&&this.expect(l.eof),this.finishCell(t,r,a)}parseTopLevel(t){return this.parseCell(t,!0)}toAssignable(t,n,s){return t.type==="MutableExpression"?t:super.toAssignable(t,n,s)}checkLocal(t){let n=t.id||t;(kr.has(n.name)||n.name==="arguments")&&this.raise(n.start,`Identifier '${n.name}' is reserved`)}checkUnreserved(t){return(t.name==="viewof"||t.name==="mutable")&&this.raise(t.start,`Unexpected keyword '${t.name}'`),super.checkUnreserved(t)}checkLValSimple(t,n,s){return super.checkLValSimple(t.type==="MutableExpression"?t.id:t,n,s)}unexpected(t){this.raise(t??this.start,this.type===l.eof?"Unexpected end of input":"Unexpected token")}parseMaybeKeywordExpression(t,n){if(this.isContextual(t)){let s=this.startNode();return this.next(),s.id=this.parseIdent(),this.finishNode(s,n)}}},bm=new ie("`",!0,!0,e=>_m.call(e)),ym=class extends ds{constructor(...e){super(...e),this.type=l.backQuote,this.exprAllowed=!1}initialContext(){return[bm]}parseCell(e){this.startCell(),this.type===l.eof&&(this.value="");let t=!0,n=this.startNode();n.expressions=[];let s=this.parseTemplateElement({isTagged:t});for(n.quasis=[s];this.type!==l.eof;)this.expect(l.dollarBraceL),n.expressions.push(this.parseExpression()),this.expect(l.braceR),n.quasis.push(s=this.parseTemplateElement({isTagged:t}));return s.tail=!0,this.next(),this.finishNode(n,"TemplateLiteral"),this.expect(l.eof),this.finishCell(e,n,null)}};function _m(){e:for(;this.poss.imported):[];else try{e.references=vm(e,n)}catch(s){if(s.node){let i=en(t,s.node.start);s.message+=` (${i.line}:${i.column})`,s.pos=s.node.start,s.loc=i,delete s.node}throw s}return e}function Ks(e,t){if(e.body&&e.body.type!=="ImportDeclaration")try{e.fileAttachments=kt(e,"FileAttachment"),e.databaseClients=kt(e,"DatabaseClient"),e.secrets=kt(e,"Secret"),e.notificationClients=kt(e,"NotificationClient")}catch(n){if(n.node){let s=en(t,n.node.start);n.message+=` (${s.line}:${s.column})`,n.pos=n.node.start,n.loc=s,delete n.node}throw n}else e.fileAttachments=new Map,e.databaseClients=new Map,e.secrets=new Map,e.notificationClients=new Map;return e}var xm=class extends ds{parseTopLevel(e){for(e.cells||(e.cells=[]);this.type!==l.eof;){let t=this.parseCell(this.startNode());t.input=this.input,e.cells.push(t)}return this.next(),this.finishNode(e,"Program")}};function Ir(e){return xm.parse(e,{ecmaVersion:"latest"}).cells.map(t=>({type:"Cell",text:e.substring(t.start,t.end),start:t.start,end:t.end}))}function Tr(e){return gm(e)}var Em=rt,tt={functionType:Object.getPrototypeOf(function(){}).constructor,asyncFunctionType:Object.getPrototypeOf(async function(){}).constructor,generatorFunctionType:Object.getPrototypeOf(function*(){}).constructor,asyncGeneratorFunctionType:Object.getPrototypeOf(async function*(){}).constructor};function Am(e=!1,t=!1){return!e&&!t?tt.functionType:e&&!t?tt.asyncFunctionType:!e&&t?tt.generatorFunctionType:tt.asyncGeneratorFunctionType}function fs(e,t=!1,n=!1,s=!1,i){if(i!==void 0)return e.patches.sort((r,a)=>a.start-r.start),e.patches.forEach(r=>{i=i.substring(0,r.start)+r.newText+i.substring(r.end)}),new(Am(t,n))(...e.args,s?i.substring(1,i.length-1).trim():`return ( +${i} +);`)}function km(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}var Nr=e=>e[0]===".",rn=(e,t)=>Nr(e)?km(t,e):e;async function Cm(e){return new tt.asyncFunctionType("url","return import(url)")(e)}function Sm(e){return e.split("`").join("\\`")}function Ct(e,t,n){let s,i;try{s=Tr(e)}catch(r){i=r}return{ojs:e,offset:t,inlineMD:n,cell:s,error:i}}function Pm(e){let t=[],n=/(```(?:\s|\S)[\s\S]*?```)/g,s=0,i=n.exec(e);for(;i!==null;){i.index>s&&t.push(Ct(e.substring(s,i.index),s,!0));let r=i[0];if(r.indexOf("``` ")===0||r.indexOf("```\n")===0||r.indexOf("```\r\n")===0){let a=r.substring(3,r.length-3);t.push(Ct(a,i.index+3,!1))}else t.push(Ct(r,i.index,!0));s=i.index+i[0].length,i=n.exec(e)}return e.length>s&&t.push(Ct(e.substring(s,e.length),s,!0)),t}function Fr(e){let t=Ir(e);return{files:[],nodes:t.map((n,s)=>({id:s,mode:"js",value:n.text,start:n.start,end:n.end}))}}function Im(e){let t=Pm(e);return{files:[],nodes:t.map((n,s)=>({id:s,mode:n.inlineMD?"md":"js",value:n.ojs,start:n.offset,end:n.offset+n.ojs.length}))}}function ms(e,t="https://api.codetabs.com/v1/proxy/?quest=",n=""){let s=e.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)/img);if(!s||s.length===0)throw new Error(`Invalid URL: ${e}`);return fetch(e,{headers:{origin:s[0],referer:e}}).then(i=>{if(i.ok)return i;throw new Error("CORS?")}).catch(i=>(e=`${t}${e}${n}`,fetch(e,{headers:{origin:s[0],referer:e}})))}function Tm(e,t){if(e.references===void 0)return{inputs:[],args:[],patches:[]};let n={};e.references.forEach(r=>n[t.substring(r.start,r.end)]=!0);let s={inputs:Object.keys(n),args:Object.keys(n).map(r=>r.split(" ").join("_")),patches:[]},i=(r,a)=>{var o,u;return s.patches.push({start:r.start-(((o=e.body)==null?void 0:o.start)??0),end:r.end-(((u=e.body)==null?void 0:u.start)??0),newText:a})};return e.body&&It(e.body,{Identifier(r){t.substring(r.start,r.end)},MutableExpression(r){let a=t.substring(r.start,r.end).split(" ").join("_")+".value";i(r,a)},ViewExpression(r){let a=t.substring(r.start,r.end).split(" ").join("_");i(r,a)},ThisExpression(r,a){t.substring(r.start,r.end)==="this"&&!a.find(o=>o.type==="FunctionExpression")&&i(r,"((this === globalThis || this === globalThis.window)? undefined : this?.valueOf())")}},Em),s}function Nm(e){var t,n;return{type:"import",src:e.body.source.value,specifiers:((t=e.body.specifiers)==null?void 0:t.map(s=>{var i;return{view:s.view,name:s.imported.name,alias:(i=s.local)!=null&&i.name&&s.imported.name!==s.local.name?s.local.name:s.imported.name}}))??[],injections:((n=e.body.injections)==null?void 0:n.map(s=>{var i;return{name:s.imported.name,alias:((i=s.local)==null?void 0:i.name)??s.imported.name}}))??[]}}function Fm(e,t,n,s){var r,a;let i=t.id&&e.substring(t.id.start,t.id.end);return{type:"viewof",variable:{type:"variable",id:i,inputs:n.inputs,func:fs(n,t.async,t.generator,t.body.type==="BlockStatement",s)},variableValue:{type:"variable",id:(a=(r=t==null?void 0:t.id)==null?void 0:r.id)==null?void 0:a.name,inputs:["Generators",i],func:(o,u)=>o.input(u)}}}function Lm(e,t,n,s){var o,u;let i=t.id&&e.substring(t.id.start,t.id.end),r=(u=(o=t==null?void 0:t.id)==null?void 0:o.id)==null?void 0:u.name,a=`initial ${r}`;return{type:"mutable",initial:{type:"variable",id:a,inputs:n.inputs,func:fs(n,t.async,t.generator,t.body.type==="BlockStatement",s)},variable:{type:"variable",id:i,inputs:["Mutable",a],func:(c,h)=>new c(h)},variableValue:{type:"variable",id:r,inputs:[i],func:c=>c.generator}}}function Dm(e,t,n,s){var i,r;return{type:"variable",id:t.id&&e.substring((i=t.id)==null?void 0:i.start,(r=t.id)==null?void 0:r.end),inputs:n.inputs,func:fs(n,t.async,t.generator,t.body.type==="BlockStatement",s)}}function $m(e,t){var r,a;let n=Tr(e),s=n.body&&e.substring(n.body.start,n.body.end);switch((r=n.body)==null?void 0:r.type){case"ImportDeclaration":return Nm(n);case"ImportExpression":switch(n.body.source.type){case"Literal":s=`import("${rn(""+n.body.source.value,t)}")`;break;default:console.error("Unexpected import value")}}let i=Tm(n,e);switch((a=n.id)==null?void 0:a.type){case"ViewExpression":return Fm(e,n,i,s);case"MutableExpression":return Lm(e,n,i,s);default:return Dm(e,n,i,s)}}var Bm=class{constructor(){$e(this,"_files",[]);$e(this,"_imports",[]);$e(this,"_functions",[]);$e(this,"_defines",[]);$e(this,"_defineUid",0);$e(this,"_functionUid",0)}toString(){return`${this._imports.join(` +`)} + +${this._functions.join(` +`).split(` +) {`).join("){")} + +export default function define(runtime, observer) { + const main = runtime.module(); + + function toString() { return this.url; } + const fileAttachments = new Map([ + ${this._files.map(e=>`["${e.name}", { url: new URL("${e.url}"), mimeType: ${JSON.stringify(e.mime_type)}, toString }]`).join(`, + `)} + ]); + main.builtin("FileAttachment", runtime.fileAttachments(name => fileAttachments.get(name))); + + ${this._defines.join(` + `)} + + return main; +} +`}files(e){this._files=[...this._files,...e]}import(e){this._imports.push(`import define${++this._defineUid} from "${e}"; `)}importDefine(e){let t=e.injections??[],n=t.map(i=>i.name===i.alias?`"${i.name}"`:`{name: "${i.name}", alias: "${i.alias}"}`),s=t.length?`.derive([${n.join(", ")}], main)`:"";this._defines.push(`const child${this._defineUid} = runtime.module(define${this._defineUid})${s};`),(e.specifiers??[]).forEach(i=>{this._defines.push(`main.import("${i.name}"${i.alias&&i.alias!==i.name?`, "${i.alias}"`:""}, child${this._defineUid}); `)})}function(e){var s,i;let t=e.id??`${++this._functionUid}`,n=t.split(" ");return t=`_${n[n.length-1]}`,this._functions.push(`${(i=(s=e.func)==null?void 0:s.toString())==null?void 0:i.replace("anonymous",`${t}`)}`),t}define(e,t=!0,n=!1,s){var c;s=s??e.id;let i=t?`.variable(observer(${e.id?JSON.stringify(e.id):""}))`:"",r=e.id?`${JSON.stringify(e.id)}, `:"",a=e.inputs??[],o=a.length?`[${a.map(h=>JSON.stringify(h)).join(", ")}], `:"",u=n?(c=e.func)==null?void 0:c.toString():s;this._defines.push(`main${i}.define(${r}${o}${u});`)}error(e){}};async function Vm(e,t){let n=rn(e,t),s=await ms(n).then(a=>a.text()),i;e.endsWith(".ojsnb")?i=JSON.parse(s):e.endsWith(".ojs")?i=Fr(s):e.endsWith(".omd")?i=Im(s):(console.warn(`Unknown file type: ${e}, assuming .ojsnb`),i=JSON.parse(s));let r=vs(i,{baseUrl:t});return r.delete=()=>{},r.write=a=>{a.import(n)},r}async function Om(e){let t=`https://api.observablehq.com/${e[0]==="@"?e:`d/${e}`}.js?v=3`,n={default:function(i,r){}};try{n=await Cm(t)}catch{}let s=n.default;return s.delete=()=>{},s.write=i=>{i.import(t)},s}async function Mm(e){let t=`https://api.observablehq.com/document/${e}`,n=ms(t).then(i=>i.json()).catch(i=>{console.error(t),console.error(i)}),s=vs(await n);return s.delete=()=>{},s.write=i=>{i.import(t)},s}async function Rm(e,t,n,{baseUrl:s,importMode:i}){let r=Nr(t.src)?await Vm(t.src,s??""):i==="recursive"?await Mm(t.src):await Om(t.src),a=[],o=[];t.specifiers.forEach(c=>{let h=c.view?"viewof ":"";a.push(Ys(h+c.name,h+c.alias)),c.view&&a.push(Ys(c.name,c.alias))});let u=(c,h,p)=>{let m=c.module(r);return t.injections.length&&(m=m.derive(t.injections,h)),o.forEach(b=>b(h,p)),a.forEach(b=>b(h,m)),m};return u.importVariables=a,u.variables=o,u.delete=()=>{a.forEach(c=>c.delete()),o.forEach(c=>c.delete()),r.delete()},u.write=c=>{r.write(c),c.importDefine(t)},u}function Be(e,t,n,s,i,r=!1){let a,o,u=(c,h)=>{if(t&&h&&(a=h(n,e.id)),o=c.variable(a),arguments.length>1)try{o.define(n,s,i)}catch(p){console.error(p==null?void 0:p.message)}if(e.pinned){o=h?c.variable(h(n,e.id)):c.variable();try{o.define(void 0,["md"],p=>p`\`\`\`js +${e.value} +\`\`\``)}catch(p){console.error(p==null?void 0:p.message)}}return o};return u.delete=()=>{var c;try{(c=a==null?void 0:a._node)==null||c.remove()}catch{}a=void 0;try{o==null||o.delete()}catch{}o=void 0},u.write=c=>{if(r)c.define({id:n,inputs:s,func:i},t,!0);else{let h=c.function({id:n,func:i});c.define({id:n,inputs:s,func:i},t,!1,h)}},u}function Ys(e,t){let n,s=(i,r)=>{n=i.variable(),t===void 0?n.import(e,r):n.import(e,t,r)};return s.delete=()=>{n==null||n.delete()},s}async function Lr(e,t){let n=[],s=[];try{let r=e.mode&&e.mode!=="js"?`${e.mode}\`${Sm(e.value)}\``:e.value,a=Ir(r);for(let o of a){let u=$m(o.text,t.baseUrl??"");switch(u.type){case"import":n.push(await Rm(e,u,o.text,t));break;case"viewof":s.push(Be(e,!0,u.variable.id,u.variable.inputs,u.variable.func)),s.push(Be(e,!1,u.variableValue.id,u.variableValue.inputs,u.variableValue.func,!0));break;case"mutable":s.push(Be(e,!1,u.initial.id,u.initial.inputs,u.initial.func)),s.push(Be(e,!1,u.variable.id,u.variable.inputs,u.variable.func)),s.push(Be(e,!0,u.variableValue.id,u.variableValue.inputs,u.variableValue.func,!0));break;case"variable":s.push(Be(e,!0,u.id,u.inputs,u.func));break}}}catch(r){s.push(Be(e,!0,void 0,[],r.message??"Unkown error"))}let i=(r,a,o)=>{n.forEach(u=>u(r,a,o)),s.forEach(u=>u(a,o))};return i.id=e.id,i.modules=n,i.variables=s,i.delete=()=>{s.forEach(r=>r.delete()),n.forEach(r=>r.delete())},i.write=r=>{n.forEach(a=>a.write(r)),s.forEach(a=>a.write(r))},i}function jm(e,t){function n(){return globalThis.url??""}return[e.name,{url:new URL(rn(e.url,t.baseUrl??"")),mimeType:e.mime_type,toString:n}]}function Um(e=[],t=[],{baseUrl:n=".",importMode:s="precompiled"}={}){let i=e.map(u=>jm(u,{baseUrl:n,importMode:s})),r=new Map(i),a=new Map(t.map(u=>[u.id,u])),o=(u,c)=>{let h=u.module();return h.builtin("FileAttachment",u.fileAttachments(p=>r.get(p)??{url:new URL(rn(p,n)),mimeType:null})),h.builtin("fetchEx",ms),a.forEach(p=>{p(u,h,c)}),h};return o.fileAttachments=r,o.cells=a,o.set=async u=>{let c=await Lr(u,{baseUrl:n,importMode:s});return o.delete(c.id),a.set(c.id,c),c},o.get=u=>a.get(u),o.delete=u=>{let c=a.get(u);return c?(c.delete(),a.delete(u)):!1},o.clear=()=>{a.forEach(u=>u.delete()),a.clear()},o.write=u=>{u.files(e),a.forEach(c=>c.write(u))},o.toString=(u=new Bm)=>(o.write(u),u.toString().trim()),o}async function vs(e,{baseUrl:t=".",importMode:n="precompiled"}={}){let s=typeof e=="string"?Fr(e):e,i=await Promise.all(s.nodes.map(r=>Lr(r,{baseUrl:t,importMode:n})));return Um(s.files,i,{baseUrl:t,importMode:n})}function qm(e){return e.exec==!0&&e.hide!==!0}var Hm=class extends kn{constructor(e){super(e),this.placeholder=e}pending(){super.pending()}fulfilled(e,t){switch(typeof e){case"string":this.placeholder.innerText=e;break;default:super.fulfilled(e)}}rejected(e){super.rejected(e)}};async function zm(e){let t={},n={nodes:[],files:[]};for(let r of e)n.nodes.push({id:r.id,name:r.id,mode:"js",value:r.content}),t[r.id]=qm(r);let s=await vs(n),i=new qi(new Mi);i.module(s,()=>{s(i,(r,a)=>{var u;let o=(u=globalThis==null?void 0:globalThis.document)==null?void 0:u.getElementById(""+a);return o&&t[a]?new Hm(o):{pending(){},fulfilled(c,h){},rejected(c,h){}}})})}const Gm={name:"RenderComponent",props:{content:{type:String,required:!0}},data(){return{loading:!0}},async created(){setTimeout(()=>{this.loading=!1,zm(JSON.parse(decodeURI(this.content)))},0)}};function Wm(e,t,n,s,i,r){return d(),g("div")}const Km=L(Gm,[["render",Wm],["__scopeId","data-v-a43401c6"]]),Jm={extends:bs,Layout:()=>Qr(bs.Layout,null,{}),enhanceApp({app:e}){e.component("RenderComponent",Km)}};export{Jm as R,dl as c,V as u}; diff --git a/assets/cmake_modules_DOCUMENTATION.md.BLw9X_2f.js b/assets/cmake_modules_DOCUMENTATION.md.BLw9X_2f.js new file mode 100644 index 00000000000..bb15406c97e --- /dev/null +++ b/assets/cmake_modules_DOCUMENTATION.md.BLw9X_2f.js @@ -0,0 +1,52 @@ +import{_ as a,c as i,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const f=JSON.parse('{"title":"CMake files structure and usage","description":"","frontmatter":{},"headers":[],"relativePath":"cmake_modules/DOCUMENTATION.md","filePath":"cmake_modules/DOCUMENTATION.md","lastUpdated":1731340314000}'),o={name:"cmake_modules/DOCUMENTATION.md"};function r(s,e,l,c,d,m){return n(),i("div",null,e[0]||(e[0]=[t(`

CMake files structure and usage

Directory structure of CMake files

- /

: - CMakeLists.txt - Root CMake file - version.cmake - common cmake file where version variables are set - build-config.h.cmake - cmake generation template for build-config.h

\\- cmake\\_modules/ - Directory storing modules and configurations for CMake
+
+:   -   FindXXXXX.cmake - CMake find files used to locate libraries,
+        headers, and binaries
+    -   commonSetup.cmake - common configuration settings for the
+        entire project (contains configure time options)
+    -   docMacros.cmake - common documentation macros used for
+        generating fop and pdf files
+    -   optionDefaults.cmake - contains common variables for the
+        platform build
+    -   distrocheck.sh - script that determines if the OS uses DEB
+        or RPM
+    -   getpackagerevisionarch.sh - script that returns OS version
+        and arch in format used for packaging
+
+    \\- dependencies/ - Directory storing dependency files used for package dependencies
+
+    :   -   \\<OS\\>.cmake - File containing either DEB or RPM
+            dependencies for the given OS
+
+\\- build-utils/ - Directory for build related utilities
+
+:   -   cleanDeb.sh - script that unpacks a deb file and rebuilds
+        with fakeroot to clean up lintain errors/warnings
+

Common Macros

  • MACRO_ENSURE_OUT_OF_SOURCE_BUILD - prevents building from with in source tree
  • HPCC_ADD_EXECUTABLE - simple wrapper around add_executable
  • HPCC_ADD_LIBRARY - simple wrapper around add_library
  • PARSE_ARGUMENTS - macro that can be used by other macros and functions for arg list parsing
  • HPCC_ADD_SUBDIRECTORY - argument controlled add subdirectory wrapper
  • HPCC_ADD_SUBDIRECTORY(test t1 t2 t3) - Will add the subdirectory test if t1,t2, or t3 are set to any value other then False/OFF/0
  • LOG_PLUGIN - used to log any code based plugin for the platform
  • ADD_PLUGIN - adds a plugin with optional build dependencies to the build if dependencies are found

Documentation Macros

  • RUN_XSLTPROC - Runs xsltproc using given args
  • RUN_FOP - Runs fop using given args
  • CLEAN_REL_BOOK - Uses a custom xsl and xsltproc to clean relative book paths from given xml file
  • CLEAN_REL_SET - Uses a custom xsl and xsltproc to clean relative set paths from given xml file
  • DOCBOOK_TO_PDF - Master macro used to generate pdf, uses above macros

Initfiles macro

  • GENERATE_BASH - used to run processor program on given files to replace ###<REPLACE>### with given variables FindXXXXX.cmake

Some standard techniques used in Cmake project files

Common looping

Use FOREACH:

FOREACH( oITEMS
+  item1
+  item2
+  item3
+  item4
+  item5
+)
+  Actions on each item here.
+ENDFOREACH ( oITEMS )
+

Common installs over just install

  • install ( FILES ... ) - installs item with 664 permissions
  • install ( PROGRAMS ... ) - installs runable item with 755 permissions
  • install ( TARGETS ... ) - installs built target with 755 permissions
  • install ( DIRECTORY ... ) - installs directory with 777 permissions

Common settings for generated source files

  • set_source_files_properties(<file> PROPERTIES GENERATED TRUE) - Must be set on generated source files or dependency generation fails and increases build time.

Using custom commands between multiple cmake files

  • GET_TARGET_PROPERTY(<VAR from other cmake file> <var for this file> LOCATION)
  • GET_TARGET_PROPERTY(ESDL_EXE esdl LOCATION) - will get from the top level cache the ESDL_EXE value and set it in esdl for your current cmake file

USE add_custom_command only when 100% needed.

All directories in a cmake project should have a CMakeLists.txt file and be called from the upper level project with an add_subdirectory or HPCC_ADD_SUBDIRECTORY

When you have a property that will be shared between cmake projects use define_property to set it in the top level cache.

  • define_property(GLOBAL PROPERTY TEST_TARGET BRIEF_DOCS "test doc" FULL_DOCS "Full test doc")
  • mark_as_advanced(TEST_TARGET) - this is required to force the property into the top level cache.CMake Layout:

FindXXXXX.cmake format

All of our Find scripts use the following format:

NOT XXXXX_FOUND
+  Externals set
+    define needed vars for finding external based libraries/headers
+
+    Use Native set
+      use FIND_PATH to locate headers
+      use FIND_LIBRARY to find libs
+
+Include Cmake macros file for package handling
+define package handling args for find return  (This will set XXXXX_FOUND)
+
+XXXXX_FOUND
+  perform any modifications you feel is needed for the find
+
+Mark defined variables used in package handling args as advanced for return
+

Will define when done:

XXXXX_FOUND
+XXXXX_INCLUDE_DIR
+XXXXX_LIBRARIES
+

(more can be defined, but must be at min the previous unless looking for only a binary)

For an example, see FindAPR.cmake

`,32)]))}const h=a(o,[["render",r]]);export{f as __pageData,h as default}; diff --git a/assets/cmake_modules_DOCUMENTATION.md.BLw9X_2f.lean.js b/assets/cmake_modules_DOCUMENTATION.md.BLw9X_2f.lean.js new file mode 100644 index 00000000000..bb15406c97e --- /dev/null +++ b/assets/cmake_modules_DOCUMENTATION.md.BLw9X_2f.lean.js @@ -0,0 +1,52 @@ +import{_ as a,c as i,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const f=JSON.parse('{"title":"CMake files structure and usage","description":"","frontmatter":{},"headers":[],"relativePath":"cmake_modules/DOCUMENTATION.md","filePath":"cmake_modules/DOCUMENTATION.md","lastUpdated":1731340314000}'),o={name:"cmake_modules/DOCUMENTATION.md"};function r(s,e,l,c,d,m){return n(),i("div",null,e[0]||(e[0]=[t(`

CMake files structure and usage

Directory structure of CMake files

- /

: - CMakeLists.txt - Root CMake file - version.cmake - common cmake file where version variables are set - build-config.h.cmake - cmake generation template for build-config.h

\\- cmake\\_modules/ - Directory storing modules and configurations for CMake
+
+:   -   FindXXXXX.cmake - CMake find files used to locate libraries,
+        headers, and binaries
+    -   commonSetup.cmake - common configuration settings for the
+        entire project (contains configure time options)
+    -   docMacros.cmake - common documentation macros used for
+        generating fop and pdf files
+    -   optionDefaults.cmake - contains common variables for the
+        platform build
+    -   distrocheck.sh - script that determines if the OS uses DEB
+        or RPM
+    -   getpackagerevisionarch.sh - script that returns OS version
+        and arch in format used for packaging
+
+    \\- dependencies/ - Directory storing dependency files used for package dependencies
+
+    :   -   \\<OS\\>.cmake - File containing either DEB or RPM
+            dependencies for the given OS
+
+\\- build-utils/ - Directory for build related utilities
+
+:   -   cleanDeb.sh - script that unpacks a deb file and rebuilds
+        with fakeroot to clean up lintain errors/warnings
+

Common Macros

  • MACRO_ENSURE_OUT_OF_SOURCE_BUILD - prevents building from with in source tree
  • HPCC_ADD_EXECUTABLE - simple wrapper around add_executable
  • HPCC_ADD_LIBRARY - simple wrapper around add_library
  • PARSE_ARGUMENTS - macro that can be used by other macros and functions for arg list parsing
  • HPCC_ADD_SUBDIRECTORY - argument controlled add subdirectory wrapper
  • HPCC_ADD_SUBDIRECTORY(test t1 t2 t3) - Will add the subdirectory test if t1,t2, or t3 are set to any value other then False/OFF/0
  • LOG_PLUGIN - used to log any code based plugin for the platform
  • ADD_PLUGIN - adds a plugin with optional build dependencies to the build if dependencies are found

Documentation Macros

  • RUN_XSLTPROC - Runs xsltproc using given args
  • RUN_FOP - Runs fop using given args
  • CLEAN_REL_BOOK - Uses a custom xsl and xsltproc to clean relative book paths from given xml file
  • CLEAN_REL_SET - Uses a custom xsl and xsltproc to clean relative set paths from given xml file
  • DOCBOOK_TO_PDF - Master macro used to generate pdf, uses above macros

Initfiles macro

  • GENERATE_BASH - used to run processor program on given files to replace ###<REPLACE>### with given variables FindXXXXX.cmake

Some standard techniques used in Cmake project files

Common looping

Use FOREACH:

FOREACH( oITEMS
+  item1
+  item2
+  item3
+  item4
+  item5
+)
+  Actions on each item here.
+ENDFOREACH ( oITEMS )
+

Common installs over just install

  • install ( FILES ... ) - installs item with 664 permissions
  • install ( PROGRAMS ... ) - installs runable item with 755 permissions
  • install ( TARGETS ... ) - installs built target with 755 permissions
  • install ( DIRECTORY ... ) - installs directory with 777 permissions

Common settings for generated source files

  • set_source_files_properties(<file> PROPERTIES GENERATED TRUE) - Must be set on generated source files or dependency generation fails and increases build time.

Using custom commands between multiple cmake files

  • GET_TARGET_PROPERTY(<VAR from other cmake file> <var for this file> LOCATION)
  • GET_TARGET_PROPERTY(ESDL_EXE esdl LOCATION) - will get from the top level cache the ESDL_EXE value and set it in esdl for your current cmake file

USE add_custom_command only when 100% needed.

All directories in a cmake project should have a CMakeLists.txt file and be called from the upper level project with an add_subdirectory or HPCC_ADD_SUBDIRECTORY

When you have a property that will be shared between cmake projects use define_property to set it in the top level cache.

  • define_property(GLOBAL PROPERTY TEST_TARGET BRIEF_DOCS "test doc" FULL_DOCS "Full test doc")
  • mark_as_advanced(TEST_TARGET) - this is required to force the property into the top level cache.CMake Layout:

FindXXXXX.cmake format

All of our Find scripts use the following format:

NOT XXXXX_FOUND
+  Externals set
+    define needed vars for finding external based libraries/headers
+
+    Use Native set
+      use FIND_PATH to locate headers
+      use FIND_LIBRARY to find libs
+
+Include Cmake macros file for package handling
+define package handling args for find return  (This will set XXXXX_FOUND)
+
+XXXXX_FOUND
+  perform any modifications you feel is needed for the find
+
+Mark defined variables used in package handling args as advanced for return
+

Will define when done:

XXXXX_FOUND
+XXXXX_INCLUDE_DIR
+XXXXX_LIBRARIES
+

(more can be defined, but must be at min the previous unless looking for only a binary)

For an example, see FindAPR.cmake

`,32)]))}const h=a(o,[["render",r]]);export{f as __pageData,h as default}; diff --git a/assets/devdoc_CodeGenerator.md.DjbPmndU.js b/assets/devdoc_CodeGenerator.md.DjbPmndU.js new file mode 100644 index 00000000000..abcb9a68e71 --- /dev/null +++ b/assets/devdoc_CodeGenerator.md.DjbPmndU.js @@ -0,0 +1,13 @@ +import{_ as t,c as a,a3 as i,o}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Eclcc/Code generator","description":"","frontmatter":{"title":"Eclcc/Code generator"},"headers":[],"relativePath":"devdoc/CodeGenerator.md","filePath":"devdoc/CodeGenerator.md","lastUpdated":1731340314000}'),r={name:"devdoc/CodeGenerator.md"};function s(n,e,l,h,d,c){return o(),a("div",null,e[0]||(e[0]=[i(`

Introduction

Purpose

The primary purpose of the code generator is to take an ECL query and convert it into a work unit that is suitable for running by one of the engines.

Aims

The code generator has to do its job accurately. If the code generator does not correctly map the ECL to the workunit it can lead to corrupt data and invalid results. Problems like that can often be very hard and frustrating for the ECL users to track down.

There is also a strong emphasis on generating output that is as good as possible. Eclcc contains many different optimization stages, and is extensible to allow others to be easily added.

Eclcc needs to be able to cope with reasonably large jobs. Queries that contain several megabytes of ECL, and generate tens of thousands of activities, and 10s of Mb of C++ are routine. These queries need to be processed relatively quickly.

Key ideas

Nearly all the processing of ECL is done using an expression graph. The representation of the expression graph has some particular characteristics:

  • Once the nodes in the expression graph have been created they are NEVER modified.
  • Nodes in the expression graph are ALWAYS commoned up if they are identical.
  • Each node in the expression graph is link counted (see below) to track its lifetime.
  • If a modified graph is required a new graph is created (sharing nodes from the old one)

The ECL language is a declarative language, and in general is assumed to be pure - i.e. there are no side-effects, expressions can be evaluated lazily and re-evaluating an expression causes no problems. This allows eclcc to transform the graph in lots of interesting ways. (Life is never that simple so there are mechanisms for handling the exceptions.)

From declarative to imperative

One of the main challenges with eclcc is converting the declarative ECL code into imperative C++ code. One key problem is it needs to try to ensure that code is only evaluated when it is required, but that it is also only evaluated once. It isn't always possible to satisfy both constraints - for example a global dataset expression used within a child query. Should it be evaluated once before the activity containing the child query is called, or each time the child query is called? If it is called on demand then it may not be evaluated as efficiently...

This issue complicates many of the optimizations and transformations that are done to the queries. Long term the plan is to allow the engines to support more delayed lazy-evaluation, so that whether something is evaluated is more dynamic rather than static.

Flow of processing

The idealised view of the processing within eclcc follows the following stages:

  • Parse the ECL into an expression graph.
  • Expand out function calls.
  • Normalize the expression graph so it is in a consistent format.
  • Normalize the references to fields within datasets to tie them up with their scopes.
  • Apply various global optimizations.
  • Translate logical operations into the activities that will implement them.
  • Resource and generate the global graph
  • For each activity, resource, optimize and generate its child graphs.

In practice the progression is not so clear cut. There tends to be some overlap between the different stages, and some of them may occur in slightly different orders. However the order broadly holds.

Working on the code generator

The regression suite

Before any change is accepted for the code generator it is always run against several regression suites to ensure that it doesn't introduce any problems, and that the change has the desired effect. There are several different regression suites:

  • testing/regress/ecl - The run time regression suite.
  • ecl/regress - a compiler regression suite. This contains tests that cannot run and error tests.
  • LN private suite - This contains a large selection (>10Gb) of archived queries. The contain proprietary code so unfortunately cannot be released as open source.

The ecl/regress directory contains a script 'regress.sh' that is used for running the regression tests. It should be executed in the directory containing the ecl files. The script generates the c++ code (and workunits) for each of the source files to a target directory, and then executes a comparison program to compare the new results with a previous "golden" reference set.

Before making any changes to the compiler, a reference set should be created by running the regression script and copying the generated files to the reference directory.

Here is a sample command line

~/dev/hpcc/ecl/regress/regress.sh -t /regress/hpcc -e /home/<user>/buildr/Release/bin/eclcc -I /home/<user>/dev/hpcc/ecl/regress/modules -I /home/<user>/dev/hpcc/plugins/javaembed -I /home/<user>/dev/hpcc/plugins/v8embed -c /regress/hpcc.master -d bcompare

(A version of this command resides in a shell script in each of my regression suite directories, with the -t and -c options adapted for each suite.)

For a full list of options execute the script with no parameters, or take a look at the script itself. A couple of useful options are:

  • The script can be run on a single file by using the -q option.
  • The (-e) option selects the path of the eclcc. This is particularly useful when running from the build directory (see below), or using multiple build directories to compare behaviour between different versions.

We strongly recommend using a comparison program which allows rules to be defined to ignore certain differences (e.g., beyond compare).

Running directly from the build directory

It is much quicker to run eclcc directly from the build directory, rather than deploying a system and running eclcc from there. To do this you need to configure some options that eclcc requires, e.g. where the include files are found. The options can be set by either setting environment variables or by specifiying options in an eclcc.ini file. The following are the names of the different options:


Environment flag Ini file option


CL_PATH compilerPath

ECLCC_LIBRARY_PATH libraryPath

ECLCC_INCLUDE_PATH includePath

ECLCC_PLUGIN_PATH plugins

HPCC_FILEHOOKS_PATH filehooks

ECLCC_TPL_PATH templatePath

ECLCC_ECLLIBRARY_PATH eclLibrariesPath

ECLCC_ECLBUNDLE_PATH eclBundlesPath

The eclcc.ini can either be a file in the local directory, or specified on the eclcc command line with -specs. Including the settings in a local eclcc.ini file also it easy to debug eclcc directly from the build directory within the eclipse environment.

Hints and tips

  • Logging

    There is an option for eclcc to output a logging file, and another to specify the level of detail in that logging file. If the detail level is above 500 then the expresssion tree for the query is output to the logging file after each of the code transformations. The tracing is very useful for tracking down at which stage inconsistencies are introduced in the expression graph, and also for learning how each transformation affects the query.

    The output format defaults to ECL - which is regenerated from the expression tree. (This ECL cannot generally be compiled without editing - partly because it contains extra annoations.) Use either of the following:

    eclcc myfile.ecl --logfile myfile.log --logdetail 999

    regress.sh -q myfile.ecl -l myfile.log

  • -ftraceIR

    There is a debug option (-ftraceIR) that generates an intermediate representation of the expression graph rather than regenerating ECL. The output tends to be less compact and harder to read quickly, but has the advantage of being better structured, and contains more details of the internal representation. ecl/hql/hqlir.cpp contains more details of the format.

  • Adding extra logging into the source code

    If you want to add tracing of expressions at any point in the code generation then adding either of the following calls will include the expression details in the log file:

    dbglogExpr(expr); // regenerate the ecl for an expression. See other functions in ecl/hql/hqlthql.hpp

    EclIR::dbglogIR(expr); // regenerate the IR for an expression. See other functions in ecl/hql/hqlir.hpp

  • Logging while debugging

    If you are debugging inside gdb it is often useful to be able to dump out details of an expression. Calling EclIR:dump_ir(expr); will generate the IR to stdout.

    p EclIR::dump_ir(expr)

    The function can also be used with multiple parameters. Each expression will be dumped out, but common child nodes will only be generated once. This can be very useful when trying to determine the difference between two expressions. The quickest way is to call EclIR::dump_ir(expr1, expr2). The first difference between the expressions will be the expression that follows the first "return".

  • Expression sequence ids.

    Sometimes it can be hard to determine where a particular IHqlExpression node was created. If that is the case, then defining DEBUG_TRACK_INSTANCEID (in ecl/hql/hqlexpr.ipp) will add a unique sequence number to each IHqlExpression that is created. There is also a function checkSeqId() at the start of ecl/hql/hqlexpr.cpp which is called whenever an expression is created, linked, released etc.. Setting a breakpoint in that function can allow you to trace back exactly when and why a particular node was created.

Expressions

Expression Graph representation

The key data structure within eclcc is the graph representation. The design has some key elements.

  • Once a node is created it is never modified.

    Some derived information (e.g., sort order, number of records, unique hash, ...) might be calculated and stored in the class after it has been created, but that doesn't change what the node represents in any way. Some nodes are created in stages - e.g., records, modules. These nodes are marked as fully completed when closeExpr() is called, after which they cannot be modified.

  • Nodes are always commoned up.

    If the same operator has the same arguments and type then there will be a unique IHqlExpression to represent it. This helps ensure that graphs stay as graphs and don't get converted to trees. It also helps with optimizations, and allows code duplicated in two different contexts to be brought together.

  • The nodes are link counted.

    Link counts are used to control the lifetime of the expression objects. Whenever a reference to an expression node is held, its link count is increased, and decreased when no longer required. The node is freed when there are no more references. (This generally works well, but does give us problems with forward references.)

  • The access to the graph is through interfaces.

    The main interfaces are IHqlExpression, IHqlDataset and IHqlScope. They are all defined in hqlexpr.hpp. The aim of the interfaces is to hide the implementation of the expression nodes so they can be restructured and changed without affecting any other code.

  • The expression classes use interfaces and a type field rather than polymorphism. This could be argued to be bad object design...but.

    There are more than 500 different possible operators. If a class was created for each of them the system would quickly become unwieldy. Instead there are several different classes which model the different types of expression (dataset/expression/scope).

    The interfaces contain everything needed to create and interrogate an expression tree, but they do not contain functionality for directly processing the graph.

    To avoid some of the shortcomings of type fields there are various mechanisms for accessing derived attributes which avoid interrogating the type field.

  • Memory consumption is critical.

It is not unusual to have 10M or even 100M nodes in memory as a query is being processed. At that scale the memory consumption of each node matters - so great care should be taken when considering increasing the size of the objects. The node classes contain a class hierarchy which is there purely to reduce the memory consumption - not to reflect the functionality. With no memory constraints they wouldn't be there, but removing a single pointer per node can save 1Gb of memory usage for very complex queries.

IHqlExpression

This is the interface that is used to walk and interrogate the expression graph once it has been created. Some of the main functions are: getOperator() What does this node represent? It returns a member of the node_operator enumerated type. numChildren() How many arguments does node have? queryChild(unsigned n) What is the nth child? If the argument is out of range it returns NULL. queryType() The type of this node. queryBody() Used to skip annotations (see below) queryProperty() Does this node have a child which is an attribute that matches a given name. (see below for more about attributes). queryValue() For a no_constant return the value of the constant. It returns NULL otherwise.

The nodes in the expression graph are created through factory functions. Some of the expression types have specialised functions - e.g., createDataset, createRow, createDictionary, but scalar expressions and actions are normally created with createValue().

Note: Generally ownership of the arguments to the createX() functions are assumed to be taken over by the newly created node.

The values of the enumeration constants in node_operator are used to calculate "crcs" which are used to check if the ECL for a query matches, and if disk and index record formats match. It contains quite a few legacy entries no_unusedXXX which can be used for new operators (otherwise new operators must be added to the end).

IHqlSimpleScope

This interface is implemented by records, and is used to map names to the fields within the records. If a record contains IFBLOCKs then each of the fields in the ifblock is defined in the IHqlSimpleScope for the containing record.

IHqlScope

Normally obtained by calling IHqlExpression::queryScope(). It is primarily used in the parser to resolve fields from within modules.

The ECL is parsed on demand so as the symbol is looked up it may cause a cascade of ECL to be compiled. The lookup context (HqlLookupContext ) is passed to IHqlScope::lookupSymbol() for several reasons:

  • It contains information about the active repository - the source of the ECL which will be dynamically parsed.
  • It contains caches of expanded functions - to avoid repeating expansion transforms.
  • Some members are used for tracking definitions that are read to build dependency graphs, or archives of submitted queries.

The interface IHqlScope currently has some members that are used for creation; this should be refactored and placed in a different interface.

IHqlDataset

This is normally obtained by calling IHqlExpression::queryDataset(). It has shrunk in size over time, and could quite possibly be folded into IHqlExpression with little pain.

There is a distinction in the code generator between "tables" and "datasets". A table (IHqlDataset::queryTable()) is a dataset operation that defines a new output record. Any operation that has a transform or record that defines an output record (e.g., PROJECT,TABLE) is a table, whilst those that don't (e.g., a filter, dedup) are not. There are a few apparent exceptions -e.g., IF (This is controlled by definesColumnList() which returns true the operator is a table.)

Properties and attributes

There are two related by slightly different concepts. An attribute refers to the explicit flags that are added to operators (e.g., , LOCAL, KEEP(n) etc. specified in the ECL or some internal attributes added by the code generator). There are a couple of different functions for creating attributes. createExtraAttribute() should be used by default. createAttribute() is reserved for an attribute that never has any arguments, or in unusual situations where it is important that the arguments are never transformed. They are tested using queryAttribute()/hasAttribute() and represented by nodes of kind no_attr/no_expr_attr.

The term "property" refers to computed information (e.g., record counts) that can be derived from the operator, its arguments and attributes. They are covered in more detail below.

Field references

Fields can be selected from active rows of a dataset in three main ways:

  • Some operators define LEFT/RIGHT to represent an input or processed dataset. Fields from these active rows are referenced with LEFT.<field-name>. Here LEFT or RIGHT is the "selector".

  • Other operators use the input dataset as the selector. E.g., myFile(myFile.id != 0). Here the input dataset is the "selector".

  • Often when the input dataset is used as the selector it can be omitted. E.g., myFile(id != 0). This is implicitly expanded by the PARSER to the second form. A reference to a field is always represented in the expression graph as a node of kind no_select (with createSelectExpr). The first child is the selector, and the second is the field. Needless to say there are some complications...

  • LEFT/RIGHT.

    The problem is that the different uses of LEFT/RIGHT need to be disambiguated since there may be several different uses of LEFT in a query. This is especially true when operations are executed in child queries. LEFT is represented by a node no_left(record, selSeq). Often the record is sufficient to disambiguate the uses, but there are situations where it isn't enough. So in addition no_left has a child which is a selSeq (selector sequence) which is added as a child attribute of the PROJECT or other operator. At parse time it is a function of the input dataset that is later normalized to a unique id to reduce the transformation work.

  • Active datasets. It is slightly more complicated - because the dataset used as the selector can be any upstream dataset up to the nearest table. So the following ECL code is legal:

    x := DATASET(...)
    +y := x(x.id != 0);
    +z := y(x.id != 100);
    +

Here the reference to x.id in the definition of z is referring to a field in the input dataset.

Because of these semantics the selector in a normalized tree is actually inputDataset->queryNormalizedSelector() rather than inputDatset. This function currently returns the table expression node (but it may change in the future see below).

Attribute "new"

In some situations ECL allows child datasets to be treated as a dataset without an explicit NORMALIZE. E.g., EXISTS(myDataset.ChildDataset);

This is primarily to enable efficient aggregates on disk files to be generated, but it adds some complications with an expression of the form dataset.childdataset.grandchild. E.g.,:

EXISTS(dataset(EXISTS(dataset.childdataset.grandchild))
+

Or:

EXISTS(dataset.childdataset(EXISTS(dataset.childdataset.grandchild))
+

In the first example dataset.childdataset within the dataset.childdataset.grandchild is a reference to a dataset that doesn't have an active cursor and needs to be iterated), whilst in the second it refers to an active cursor.

To differentiate between the two, all references to fields within datasets/rows that don't have active selectors have an additional attribute("new") as a child of the select. So a no_select with a "new" attribute requires the dataset to be created, one without is a member of an active dataset cursor.

If you have a nested row, the new attribute is added to the selection from the dataset, rather than the selection from the nested row. The functions queryDatasetCursor() and querySelectorDataset()) are used to help interpret the meaning.

(An alternative would be to use a different node from no_select - possibly this should be considered - it would be more space efficient.)

The expression graph generated by the ECL parser doesn't contain any new attributes. These are added as one of the first stages of normalizing the expression graph. Any code that works on normalized expressions needs to take care to interpret no_selects correctly.

Transforming selects

When an expression graph is transformed and none of the records are changed, the representation of LEFT/RIGHT remains the same. This means any no_select nodes in the expression tree will also stay the same.

However, if the transform modifies a table (highly likely) it means that the selector for the second form of field selector will also change. Unfortunately this means that transforms often cannot be short-circuited.

It could significantly reduce the extent of the graph that needs traversing, and the number of nodes replaced in a transformed graph if this could be avoided. One possibility is to use a different value for dataset->queryNormalizedSelector() using a unique id associated with the table. I think it would be a good long term change, but it would require unique ids (similar to the selSeq) to be added to all table expressions, and correctly preserved by any optimization.

Annotations

Sometimes it is useful to add information into the expression graph (e.g., symbol names, position information) that doesn't change the meaning, but should be preserved. Annotations allow information to be added in this way.

An annotation's implementation of IHqlExpression generally delegates the majority of the methods through to the annotated expression. This means that most code that interrogates the expression graph can ignore their presence, which simplifies the caller significantly. However transforms need to be careful (see below).

Information about the annotation can be obtained by calling IHqlExpression:: getAnnotationKind() and IHqlExpression:: queryAnnotation().

Associated side-effects

In legacy ECL you will see code like the following::

EXPORT a(x) := FUNCTION
+   Y := F(x);
+   OUTPUT(Y);
+   RETURN G(Y);
+END;
+

The assumption is that whenever a(x) is evaluated the value of Y will be output. However that doesn't particularly fit in with a declarative expression graph. The code generator creates a special node (no_compound) with child(0) as the output action, and child(1) as the value to be evaluated (g(Y)).

If the expression ends up being included in the final query then the action will also be included (via the no_compound). At a later stage the action is migrated to a position in the graph where actions are normally evaluated.

Derived properties

There are many pieces of information that it is useful to know about a node in the expression graph - many of which would be expensive to recomputed each time there were required. Eclcc has several mechanisms for caching derived information so it is available efficiently.

  • Boolean flags - getInfoFlags()/getInfoFlags2().

    There are many Boolean attributes of an expression that are useful to know - e.g., is it constant, does it have side-effects, does it reference any fields from a dataset etc. etc. The bulk of these are calculated and stored in a couple of members of the expression class. They are normally retrieved via accessor functions e.g., containsAssertKeyed(IHqlExpression*).

  • Active datasets - gatherTablesUsed().

    It is very common to want to know which datasets an expression references. This information is calculated and cached on demand and accessed via the IHqlExpression::gatherTablesUsed() functions. There are a couple of other functions IHqlExpression::isIndependentOfScope() and IHqlExpression::usesSelector() which provide efficient functions for common uses.

  • Information stored in the type.

    Currently datasets contain information about sort order, distribution and grouping as part of the expression type. This information should be accessed through the accessor functions applied to the expression (e.g., isGrouped(expr)). At some point in the future it is planned to move this information as a general derived property (see next).

  • Other derived property.

    There is a mechanism (in hqlattr) for calculating and caching an arbitrary derived property of an expression. It is currently used for number of rows, location-independent representation, maximum record size etc. . There are typically accessor functions to access the cached information (rather than calling the underlying IHqlExpression::queryAttribute() function).

  • Helper functions.

    Some information doesn't need to be cached because it isn't expensive to calculate, but rather than duplicating the code, a helper function is provided. E.g., queryOriginalRecord() and hasUnknownTransform(). They are not part of the interface because the number would make the interface unwieldy and they can be completely calculated from the public functions.

    However, it can be very hard to find the function you are looking for, and they would greatly benefit from being grouped e.g., into namespaces.

Transformations

One of the key processes in eclcc is walking and transforming the expression graphs. Both of these are covered by the term transformations. One of the key things to bear in mind is that you need to walk the expression graph as a graph, not as a tree. If you have already examined a node once you shouldn't repeat the work - otherwise the execution time may be exponential with node depth.

Other things to bear in mind

  • If a node isn't modified don't create a new one - return a link to the old one.
  • You generally need to walk the graph and gather some information before creating a modified graph. Sometimes creating a new graph can be short-circuited if no changes will be required.
  • Sometimes you can be tempted to try and short-circuit transforming part of a graph (e.g., the arguments to a dataset activity), but because of the way references to fields within dataset work that often doesn't work.
  • If an expression is moved to another place in the graph, you need to be very careful to check if the original context was conditional and that the new context is not.
  • The meaning of expressions can be context dependent. E.g., References to active datasets can be ambiguous.
  • Never walk the expressions as a tree, always as a graph!
  • Be careful with annotations.

It is essential that an expression that is used in different contexts with different annotations (e.g., two different named symbols) is consistently transformed. Otherwise it is possible for a graph to be converted into a tree. E.g.,:

A := x; B := x; C = A + B;
+

must not be converted to:

A' := x'; B' := X'';  C' := A' + B';
+

For this reason most transformers will check if expr->queryBody() matches expr, and if not will transform the body (the unannotated expression), and then clone any annotations.

Some examples of the work done by transformations are:

  • Constant folding.
  • Expanding function calls.
  • Walking the graph and reporting warnings.
  • Optimizing the order and removing redundant activities.
  • Reducing the fields flowing through the generated graph.
  • Spotting common sub expressions.
  • Calculating the best location to evaluate an expression (e.g., globally instead of in a child query).
  • Many, many others.

Some more details on the individual transforms are given below..

Key Stages

Parsing

The first job of eclcc is to parse the ECL into an expression graph. The source for the ECL can come from various different sources (archive, source files, remote repository). The details are hidden behind the IEclSource/IEclSourceCollection interfaces. The createRepository() function is then used to resolve and parse the various source files on demand.

Several things occur while the ECL is being parsed:

  • Function definitions are expanded inline.

    A slightly unusual behaviour. It means that the expression tree is a fully expanded expression -which is better suited to processing and optimizing.

  • Some limited constant folding occurs.

    When a function is expanded, often it means that some of the test conditions are always true/false. To reduce the transformations the condition may be folded early on.

  • When a symbol is referenced from another module this will recursively cause the ECL for that module (or definition within that module) to be parsed.

  • Currently the semantic checking is done as the ECL is parsed.

    If we are going to fully support template functions and delayed expansion of functions this will probably have to change so that a syntax tree is built first, and then the semantic checking is done later.

Normalizing

There are various problems with the expression graph that comes out of the parser:

  • Records can have values as children (e.g., { myField := infield.value} ), but it causes chaos if record definitions can change while other transformations are going on. So the normalization removes values from fields.

  • Some activities use records to define the values that output records should contain (e.g., TABLE). These are now converted to another form (e.g., no_newusertable).

  • Sometimes expressions have multiple definition names. Symbols and annotations are rationalized and commoned up to aid commoning up other expressions.

  • Some PATTERN definitions are recursive by name. They are resolved to a form that works if all symbols are removed.

  • The CASE/MAP representation for a dataset/action is awkward for the transforms to process. They are converted to nested Ifs.

    (At some point a different representation might be a good idea.)

  • EVALUATE is a weird syntax. Instances are replaced with equivalent code which is much easier to subsequently process.

  • The datasets used in index definitions are primarily there to provide details of the fields. The dataset itself may be very complex and may not actually be used. The dataset input to an index is replaced with a dummy "null" dataset to avoid unnecessary graph transforming, and avoid introducing any additional incorrect dependencies.

Scope checking

Generally if you use LEFT/RIGHT then the input rows are going to be available wherever they are used. However if they are passed into a function, and that function uses them inside a definition marked as global then that is invalid (since by definition global expressions don't have any context).

Similarly if you use syntax <dataset>.<field>, its validity and meaning depends on whether <dataset> is active. The scope transformer ensures that all references to fields are legal, and adds a "new" attribute to any no_selects where it is necessary.

Constant folding: foldHqlExpression

This transform simplifies the expression tree. Its aim is to simplify scalar expressions, and dataset expressions that are valid whether or not the nodes are shared. Some examples are:

  • 1 + 2 => 3 and any other operation on scalar constants.
  • IF(true, x, y) => x
  • COUNT(<empty-dataset>) => 0
  • IF (a = b, 'c', 'd') = 'd' => IF(a=b, false, true) => a != b
  • Simplifying sorts, projects filters on empty datasets

Most of the optimizations are fairly standard, but a few have been added to cover more esoteric examples which have occurred in queries over the years.

This transform also supports the option to percolate constants through the graph. E.g., if a project assigns the value 3 to a field, it can substitute the value 3 wherever that field is used in subsequent activities. This can often lead to further opportunities for constant folding (and removing fields in the implicit project).

Expression optimizer: optimizeHqlExpression

This transformer is used to simplify, combine and reorder dataset expressions. The transformer takes care to count the number of times each expression is used to ensure that none of the transformations cause duplication. E.g., swapping a filter with a sort is a good idea, but if there are two filters of the same sort and they are both swapped you will now be duplicating the sort.

Some examples of the optimizations include:

  • COUNT(SORT(x)) => COUNT(x)
  • Moving filters over projects, joins, sorts.
  • Combining adjacent projects, projects and joins.
  • Removing redundant sorts or distributes
  • Moving filters from JOINs to their inputs.
  • Combining activities e.g., CHOOSEN(SORT(x)) => TOPN(x)
  • Sometimes moving filters into IFs
  • Expanding out a field selected from a single row dataset.
  • Combine filters and projects into compound disk read operations.

Implicit project: insertImplicitProjects

ECL tends to be written as general purpose definitions which can then be combined. This can lead to potential inefficiencies - e.g., one definition may summarise some data in 20 different ways, this is then used by another definition which only uses a subset of those results. The implicit project transformer tracks the data flow at each point through the expression graph, and removes any fields that are not required.

This often works in combination with the other optimizations. For instance the constant percolation can remove the need for fields, and removing fields can sometimes allow a left outer join to be converted to a project.

Workunits

is this the correct term? Should it be a query? This should really be independent of this document...)

The code generator ultimately creates workunits. A workunit completely describes a generated query. It consists of two parts. There is an xml component - this contains the workflow information, the various execution graphs, and information about options. It also describes which inputs can be supplied to the query and what results are generated. The other part is the generated shared object compiled from the generated C++. This contains functions and classes that are used by the engines to execute the queries. Often the xml is compressed and stored as a resource within the shared object -so the shared object contains a complete workunit.

Workflow

The actions in a workunit are divided up into individual workflow items. Details of when each workflow item is executed, what its dependencies are stored in the <Workflow> section of the xml. The generated code also contains a class definition, with a method perform() which is used to execute the actions associated with a particular workflow item. (The class instances are created by calling the exported createProcess() factory function).

The generated code for an individual workflow item will typically call back into the engine at some point to execute a graph.

Graph

The activity graphs are stored in the xml. The graph contains details of which activities are required, how those activities link together, what dependencies there are between the activities. For each activity it might contain the following information:

  • A unique id.
  • The "kind" of the activity (from enum ThorActivityKind in eclhelper.hpp)
  • The ECL that created the activity.
  • Name of the original definition
  • Location (e.g., file, line number) of the original ECL.
  • Information about the record size, number of rows, sort order etc.
  • Hints which control options for a particular activity (e.g,, the number of threads to use while sorting).
  • Record counts and stats once the job has executed.

Each activity in a graph also has a corresponding helper class instance in the generated code. (The name of the class is cAc followed by the activity number, and the exported factory method is fAc followed by the activity number.) These classes implement the interfaces defined in eclhelper.hpp.

The engine uses the information from the xml to produce a graph of activities that need to be executed. It has a general purpose implementation of each activity kind, and it uses the class instance to tailor that general activity to the specific use e.g., what is the filter condition, what fields are set up, what is the sort order?

Inputs and Results

The workunit xml contains details of what inputs can be supplied when that workunit is run. These correspond to STORED definitions in the ECL. The result xml also contains the schema for the results that the workunit will generate.

Once an instance of the workunit has been run, the values of the results may be written back into dali's copy of the workunit so they can be retrieved and displayed.

Generated code

Aims for the generated C++ code:

  • Minimal include dependencies.

    Compile time is an issue - especially for small on-demand queries. To help reduce compile times (and dependencies with the rest of the system) the number of header files included by the generated code is kept to a minimum. In particular references to jlib, boost and icu are kept within the implementation of the runtime functions, and are not included in the public dependencies.

  • Thread-safe.

    It should be possible to use the members of an activity helper from multiple threads without issue. The helpers may contain some context dependent state, so different instances of the helpers are needed for concurrent use from different contexts (e.g., expansions of a graph.)

  • Concise.

    The code should be broadly readable, but the variable names etc. are chosen to generate compact code.

  • Functional.

    Generally the generated code assigns to a variable once, and doesn't modify it afterwards. Some assignments may be conditional, but once the variable is evaluated it isn't updated. (There are of course a few exceptions - e.g., dataset iterators)

Implementation details

First a few pointers to help understand the code within eclcc:

  • It makes extensive use of link counting. You need understand that concept to get very far.

  • If something is done more than once then that is generally split into a helper function.

    The helper functions aren't generally added to the corresponding interface (e.g., IHqlExpression) because the interface would become bloated. Instead they are added as global functions. The big disadvantage of this approach is they can be hard to find. Even better would be for them to be rationalised and organised into namespaces.

  • The code is generally thread-safe unless there would be a significant performance implication. In generally all the code used by the parser for creating expressions is thread safe. Expression graph transforms are thread-safe, and can execute in parallel if a constant (NUM_PARALLEL_TRANSFORMS) is increased. The data structures used to represent the generated code are NOT thread-safe.

  • Much of the code generation is structured fairly procedurally, with classes used to process the stages within it.

  • There is a giant "God" class HqlCppTranslator - which could really do with refactoring.

Parser

The eclcc parser uses the standard tools bison and flex to process the ECL and convert it to a

: expression graph. There are a couple of idiosyncrasies with the way it is implemented.

  • Macros with fully qualified scope.

    Slightly unusually macros are defined in the same way that other definitions are - in particular to can have references to macros in other modules. This means that there are references to macros within the grammar file (instead of being purely handled by a pre-processor). It also means the lexer keeps an active stack of macros being processed.

  • Attributes on operators.

    Many of the operators have optional attributes (e.g., KEEP, INNER, LOCAL, ...). If these were all reserved words it would remove a significant number of keywords from use as symbols, and could also mean that when a new attribute was added it broke existing code. To avoid this the lexer looks ahead in the parser tables (by following the potential reductions) to see if the token really could come next. If it can't then it isn't reserved as a symbol.

Generated code

As the workunit is created the code generator builds up the generated code and the xml for the workunit. Most of the xml generation is encapsulated within the IWorkUnit interface. The xml for the graphs is created in an IPropertyTree, and added to the workunit as a block.

C++ Output structures

The C++ generation is ultimately controlled by some template files (thortpl.cpp). The templates are plain text and contain references to allow named sections of code to be expanded at particular points.

The code generator builds up some structures in memory for each of those named sections. Once the generation is complete some peephole optimization is applied to the code. This structure is walked to expand each named section of code as required.

The BuildCtx class provides a cursor into that generated C++. It will either be created for a given named section, or more typically from another BuildCtx. It has methods for adding the different types of statements. Some are simple (e.g., addExpr()), whilst some create a compound statement (e.g., addFilter). The compound statements change the active selector so any new statements are added within that compound statement.

As well as building up a tree of expressions, this data structure also maintains a tree of associations. For instance when a value is evaluated and assigned to a temporary variable, the logical value is associated with that temporary. If the same expression is required later, the association is matched, and the temporary value is used instead of recalculating it. The associations are also used to track the active datasets, classes generated for row-meta information, activity classes etc. etc.

Activity Helper

Each activity in an expression graph will have an associated class generated in the C++. Each different activity kind expects a helper that implements a particular IHThorArg interface. E.g., a sort activity of kind TAKsort requires a helper that implements IHThorSortArg. The associated factory function is used to create instances of the helper class.

The generated class might take one of two forms:

  • A parameterised version of a library class. These are generated for simple helpers that don't have many variations (e.g., CLibrarySplitArg for TAKsplit), or for special cases that occur very frequently (CLibraryWorkUnitReadArg for internal results).
  • A class derived from a skeleton implementation of that helper (typically CThorXYZ implementing interface IHThorXYZ). The base class has default implementations of some of the functions, and any exceptions are implemented in the derived class.

Meta helper

This is a class that is used by the engines to encapsulate all the information about a single row -e.g., the format that each activity generates. It is an implementation of the IOutputMeta interface. It includes functions to

  • Return the size of the row.
  • Serialize and deserialize from disk.
  • Destroy and clean up row instances.
  • Convert to xml.
  • Provide information about the contained fields.

Building expressions

The same expression nodes are used for representing expressions in the generated C++ as the original ECL expression graph. It is important to keep track of whether an expression represents untranslated ECL, or the "translated" C++. For instance ECL has 1 based indexes, while C++ is zero based. If you processed the expression x[1] it might get translated to x[0] in C++. Translating it again would incorrectly refer to x[-1].

There are two key classes used while building the C++ for an ECL expression:

CHqlBoundExpr.

This represents a value that has been converted to C++. Depending on the type, one or more of the fields will be filled in.

CHqlBoundTarget.

This represents the target of an assignment -C++ variable(s) that are going to be assigned the result of evaluating an expression. It is almost always passed as a const parameter to a function because the target is well-defined and the function needs to update that target.

A C++ expression is sometimes converted back to an ECL pseudo-expression by calling getTranslatedExpr(). This creates an expression node of kind no_translated to indicate the child expression has already been converted.

Scalar expressions

The generation code for expressions has a hierarchy of calls. Each function is there to allow optimal code to be generated - e.g., not creating a temporary variable if none are required. A typical flow might be:

  • buildExpr(ctx, expr, bound).

    Evaluate the ecl expression "expr" and save the C++ representation in the class bound. This might then call through to...

  • buildTempExpr(ctx, expr, bound);

    Create a temporary variable, and evaluate expr and assign it to that temporary variable.... Which then calls.

  • buildExprAssign(ctx, target, expr);

    evaluate the expression, and ensure it is assigned to the C++ target "target".

    The default implementation might be to call buildExpr....

An operator must either be implemented in buildExpr() (calling a function called doBuildExprXXX) or in buildExprAssign() (calling a function called doBuildAssignXXX). Some operators are implemented in both places if there are different implementations that would be more efficient in each context.

Similarly there are several different assignment functions:

  • buildAssign(ctx, <ecl-target>, <ecl-value>);
  • buildExprAssign(ctx, <c++-target>, <ecl-value>);
  • assign(ctx, <C++target>, <c++source>)

The different varieties are there depending on whether the source value or targets have already been translated. (The names could be rationalised!)

Datasets

Most dataset operations are only implemented as activities (e.g., PARSE, DEDUP). If these are used within a transform/filter then eclcc will generate a call to a child query. An activity helper for the appropriate operation will then be generated.

However a subset of the dataset operations can also be evaluated inline without calling a child query. Some examples are filters, projects, and simple aggregation. It removes the overhead of the child query call in the simple cases, and often generates more concise code.

When datasets are evaluated inline there is a similar hierarchy of function calls:

  • buildDatasetAssign(ctx, target, expr);

    Evaluate the dataset expression, and assign it to the target (a builder interface). This may then call....

  • buildIterate(ctx, expr)

    Iterate through each of the rows in the dataset expression in turn. Which may then call...

  • buildDataset(ctx, expr, target, format)

    Build the entire dataset, and return it as a single value.

Some of the operations (e.g., aggregating a filtered dataset) can be done more efficiently by summing and filtering an iterator, than forcing the filtered dataset to be evaluated first.

Dataset cursors

The interface IHqlCppDatasetCursor allows the code generator to iterate through a dataset, or select a particular element from a dataset. It is used to hide the different representation of datasets, e.g.,

  • Blocked - the rows are in a contiguous block of memory appended one after another.
  • Array - the dataset is represented by an array of pointers to the individual rows.
  • Link counted - similar to array, but each element is also link counted.
  • Nested. Sometimes the cursor may iterate through multiple levels of child datasets.

Generally rows that are serialized (e.g., on disk) are in blocked format, and they are stored as link counted rows in memory.

Field access classes

The IReferenceSelector interface and the classes in hqltcppc[2] provide an interface for getting and setting values within a row of a dataset. They hide the details of the layout - e.g., csv/xml/raw data, and the details of exactly how each type is represented in the row.

Key filepos weirdness

The current implementation of keys in HPCC uses a format which uses a separate 8 byte integer field which was historically used to store the file position in the original file. Other complications are that the integer fields are stored big-endian, and signed integer values are biased.

This introduces some complication in the way indexes are handled. You will often find that the logical index definition is replaced with a physical index definition, followed by a project to convert it to the logical view. A similar process occurs for disk files to support VIRTUAL(FILEPOSITION) etc.

Source code

The following are the main directories used by the ecl compiler.


Directory Contents


rtl/eclrtpl Template text files used to generate the C++ code

rtl/include Headers that declare interfaces implemented by the generated code

common/deftype Interfaces and classes for scalar types and values.

common/workunit Code for managing the representation of a work unit.

ecl/hql Classes and interfaces for parsing and representing an ecl expression graph

ecl/hqlcpp Classes for converting an expression graph to a work unit (and C++)

ecl/eclcc The executable which ties everything together.

Challenges

From declarative to imperative

As mentioned at the start of this document, one of the main challenges with eclcc is converting the declarative ECL code into imperative C++ code. The direction we are heading in is to allow the engines to support more lazy-evaluation so possibly in this instance to evaluate it the first time it is used (although that may potentially be much less efficient). This will allow the code generator to relax some of its current assumptions.

There are several example queries which are already producing pathological behaviour from eclcc, causing it to generate C++ functions which are many thousands of lines long.

The parser

Currently the grammar for the parser is too specialised. In particular the separate productions for expression, datasets, actions cause problems - e.g., it is impossible to properly allow sets of datasets to be treated in the same way as other sets.

The semantic checking (and probably semantic interpretation) is done too early. Really the parser should build up a syntax tree, and then disambiguate it and perform the semantic checks on the syntax tree.

The function calls should probably be expanded later than they are. I have tried in the past and hit problems, but I can't remember all the details. Some are related to the semantic checking.

`,223)]))}const f=t(r,[["render",s]]);export{u as __pageData,f as default}; diff --git a/assets/devdoc_CodeGenerator.md.DjbPmndU.lean.js b/assets/devdoc_CodeGenerator.md.DjbPmndU.lean.js new file mode 100644 index 00000000000..abcb9a68e71 --- /dev/null +++ b/assets/devdoc_CodeGenerator.md.DjbPmndU.lean.js @@ -0,0 +1,13 @@ +import{_ as t,c as a,a3 as i,o}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Eclcc/Code generator","description":"","frontmatter":{"title":"Eclcc/Code generator"},"headers":[],"relativePath":"devdoc/CodeGenerator.md","filePath":"devdoc/CodeGenerator.md","lastUpdated":1731340314000}'),r={name:"devdoc/CodeGenerator.md"};function s(n,e,l,h,d,c){return o(),a("div",null,e[0]||(e[0]=[i(`

Introduction

Purpose

The primary purpose of the code generator is to take an ECL query and convert it into a work unit that is suitable for running by one of the engines.

Aims

The code generator has to do its job accurately. If the code generator does not correctly map the ECL to the workunit it can lead to corrupt data and invalid results. Problems like that can often be very hard and frustrating for the ECL users to track down.

There is also a strong emphasis on generating output that is as good as possible. Eclcc contains many different optimization stages, and is extensible to allow others to be easily added.

Eclcc needs to be able to cope with reasonably large jobs. Queries that contain several megabytes of ECL, and generate tens of thousands of activities, and 10s of Mb of C++ are routine. These queries need to be processed relatively quickly.

Key ideas

Nearly all the processing of ECL is done using an expression graph. The representation of the expression graph has some particular characteristics:

  • Once the nodes in the expression graph have been created they are NEVER modified.
  • Nodes in the expression graph are ALWAYS commoned up if they are identical.
  • Each node in the expression graph is link counted (see below) to track its lifetime.
  • If a modified graph is required a new graph is created (sharing nodes from the old one)

The ECL language is a declarative language, and in general is assumed to be pure - i.e. there are no side-effects, expressions can be evaluated lazily and re-evaluating an expression causes no problems. This allows eclcc to transform the graph in lots of interesting ways. (Life is never that simple so there are mechanisms for handling the exceptions.)

From declarative to imperative

One of the main challenges with eclcc is converting the declarative ECL code into imperative C++ code. One key problem is it needs to try to ensure that code is only evaluated when it is required, but that it is also only evaluated once. It isn't always possible to satisfy both constraints - for example a global dataset expression used within a child query. Should it be evaluated once before the activity containing the child query is called, or each time the child query is called? If it is called on demand then it may not be evaluated as efficiently...

This issue complicates many of the optimizations and transformations that are done to the queries. Long term the plan is to allow the engines to support more delayed lazy-evaluation, so that whether something is evaluated is more dynamic rather than static.

Flow of processing

The idealised view of the processing within eclcc follows the following stages:

  • Parse the ECL into an expression graph.
  • Expand out function calls.
  • Normalize the expression graph so it is in a consistent format.
  • Normalize the references to fields within datasets to tie them up with their scopes.
  • Apply various global optimizations.
  • Translate logical operations into the activities that will implement them.
  • Resource and generate the global graph
  • For each activity, resource, optimize and generate its child graphs.

In practice the progression is not so clear cut. There tends to be some overlap between the different stages, and some of them may occur in slightly different orders. However the order broadly holds.

Working on the code generator

The regression suite

Before any change is accepted for the code generator it is always run against several regression suites to ensure that it doesn't introduce any problems, and that the change has the desired effect. There are several different regression suites:

  • testing/regress/ecl - The run time regression suite.
  • ecl/regress - a compiler regression suite. This contains tests that cannot run and error tests.
  • LN private suite - This contains a large selection (>10Gb) of archived queries. The contain proprietary code so unfortunately cannot be released as open source.

The ecl/regress directory contains a script 'regress.sh' that is used for running the regression tests. It should be executed in the directory containing the ecl files. The script generates the c++ code (and workunits) for each of the source files to a target directory, and then executes a comparison program to compare the new results with a previous "golden" reference set.

Before making any changes to the compiler, a reference set should be created by running the regression script and copying the generated files to the reference directory.

Here is a sample command line

~/dev/hpcc/ecl/regress/regress.sh -t /regress/hpcc -e /home/<user>/buildr/Release/bin/eclcc -I /home/<user>/dev/hpcc/ecl/regress/modules -I /home/<user>/dev/hpcc/plugins/javaembed -I /home/<user>/dev/hpcc/plugins/v8embed -c /regress/hpcc.master -d bcompare

(A version of this command resides in a shell script in each of my regression suite directories, with the -t and -c options adapted for each suite.)

For a full list of options execute the script with no parameters, or take a look at the script itself. A couple of useful options are:

  • The script can be run on a single file by using the -q option.
  • The (-e) option selects the path of the eclcc. This is particularly useful when running from the build directory (see below), or using multiple build directories to compare behaviour between different versions.

We strongly recommend using a comparison program which allows rules to be defined to ignore certain differences (e.g., beyond compare).

Running directly from the build directory

It is much quicker to run eclcc directly from the build directory, rather than deploying a system and running eclcc from there. To do this you need to configure some options that eclcc requires, e.g. where the include files are found. The options can be set by either setting environment variables or by specifiying options in an eclcc.ini file. The following are the names of the different options:


Environment flag Ini file option


CL_PATH compilerPath

ECLCC_LIBRARY_PATH libraryPath

ECLCC_INCLUDE_PATH includePath

ECLCC_PLUGIN_PATH plugins

HPCC_FILEHOOKS_PATH filehooks

ECLCC_TPL_PATH templatePath

ECLCC_ECLLIBRARY_PATH eclLibrariesPath

ECLCC_ECLBUNDLE_PATH eclBundlesPath

The eclcc.ini can either be a file in the local directory, or specified on the eclcc command line with -specs. Including the settings in a local eclcc.ini file also it easy to debug eclcc directly from the build directory within the eclipse environment.

Hints and tips

  • Logging

    There is an option for eclcc to output a logging file, and another to specify the level of detail in that logging file. If the detail level is above 500 then the expresssion tree for the query is output to the logging file after each of the code transformations. The tracing is very useful for tracking down at which stage inconsistencies are introduced in the expression graph, and also for learning how each transformation affects the query.

    The output format defaults to ECL - which is regenerated from the expression tree. (This ECL cannot generally be compiled without editing - partly because it contains extra annoations.) Use either of the following:

    eclcc myfile.ecl --logfile myfile.log --logdetail 999

    regress.sh -q myfile.ecl -l myfile.log

  • -ftraceIR

    There is a debug option (-ftraceIR) that generates an intermediate representation of the expression graph rather than regenerating ECL. The output tends to be less compact and harder to read quickly, but has the advantage of being better structured, and contains more details of the internal representation. ecl/hql/hqlir.cpp contains more details of the format.

  • Adding extra logging into the source code

    If you want to add tracing of expressions at any point in the code generation then adding either of the following calls will include the expression details in the log file:

    dbglogExpr(expr); // regenerate the ecl for an expression. See other functions in ecl/hql/hqlthql.hpp

    EclIR::dbglogIR(expr); // regenerate the IR for an expression. See other functions in ecl/hql/hqlir.hpp

  • Logging while debugging

    If you are debugging inside gdb it is often useful to be able to dump out details of an expression. Calling EclIR:dump_ir(expr); will generate the IR to stdout.

    p EclIR::dump_ir(expr)

    The function can also be used with multiple parameters. Each expression will be dumped out, but common child nodes will only be generated once. This can be very useful when trying to determine the difference between two expressions. The quickest way is to call EclIR::dump_ir(expr1, expr2). The first difference between the expressions will be the expression that follows the first "return".

  • Expression sequence ids.

    Sometimes it can be hard to determine where a particular IHqlExpression node was created. If that is the case, then defining DEBUG_TRACK_INSTANCEID (in ecl/hql/hqlexpr.ipp) will add a unique sequence number to each IHqlExpression that is created. There is also a function checkSeqId() at the start of ecl/hql/hqlexpr.cpp which is called whenever an expression is created, linked, released etc.. Setting a breakpoint in that function can allow you to trace back exactly when and why a particular node was created.

Expressions

Expression Graph representation

The key data structure within eclcc is the graph representation. The design has some key elements.

  • Once a node is created it is never modified.

    Some derived information (e.g., sort order, number of records, unique hash, ...) might be calculated and stored in the class after it has been created, but that doesn't change what the node represents in any way. Some nodes are created in stages - e.g., records, modules. These nodes are marked as fully completed when closeExpr() is called, after which they cannot be modified.

  • Nodes are always commoned up.

    If the same operator has the same arguments and type then there will be a unique IHqlExpression to represent it. This helps ensure that graphs stay as graphs and don't get converted to trees. It also helps with optimizations, and allows code duplicated in two different contexts to be brought together.

  • The nodes are link counted.

    Link counts are used to control the lifetime of the expression objects. Whenever a reference to an expression node is held, its link count is increased, and decreased when no longer required. The node is freed when there are no more references. (This generally works well, but does give us problems with forward references.)

  • The access to the graph is through interfaces.

    The main interfaces are IHqlExpression, IHqlDataset and IHqlScope. They are all defined in hqlexpr.hpp. The aim of the interfaces is to hide the implementation of the expression nodes so they can be restructured and changed without affecting any other code.

  • The expression classes use interfaces and a type field rather than polymorphism. This could be argued to be bad object design...but.

    There are more than 500 different possible operators. If a class was created for each of them the system would quickly become unwieldy. Instead there are several different classes which model the different types of expression (dataset/expression/scope).

    The interfaces contain everything needed to create and interrogate an expression tree, but they do not contain functionality for directly processing the graph.

    To avoid some of the shortcomings of type fields there are various mechanisms for accessing derived attributes which avoid interrogating the type field.

  • Memory consumption is critical.

It is not unusual to have 10M or even 100M nodes in memory as a query is being processed. At that scale the memory consumption of each node matters - so great care should be taken when considering increasing the size of the objects. The node classes contain a class hierarchy which is there purely to reduce the memory consumption - not to reflect the functionality. With no memory constraints they wouldn't be there, but removing a single pointer per node can save 1Gb of memory usage for very complex queries.

IHqlExpression

This is the interface that is used to walk and interrogate the expression graph once it has been created. Some of the main functions are: getOperator() What does this node represent? It returns a member of the node_operator enumerated type. numChildren() How many arguments does node have? queryChild(unsigned n) What is the nth child? If the argument is out of range it returns NULL. queryType() The type of this node. queryBody() Used to skip annotations (see below) queryProperty() Does this node have a child which is an attribute that matches a given name. (see below for more about attributes). queryValue() For a no_constant return the value of the constant. It returns NULL otherwise.

The nodes in the expression graph are created through factory functions. Some of the expression types have specialised functions - e.g., createDataset, createRow, createDictionary, but scalar expressions and actions are normally created with createValue().

Note: Generally ownership of the arguments to the createX() functions are assumed to be taken over by the newly created node.

The values of the enumeration constants in node_operator are used to calculate "crcs" which are used to check if the ECL for a query matches, and if disk and index record formats match. It contains quite a few legacy entries no_unusedXXX which can be used for new operators (otherwise new operators must be added to the end).

IHqlSimpleScope

This interface is implemented by records, and is used to map names to the fields within the records. If a record contains IFBLOCKs then each of the fields in the ifblock is defined in the IHqlSimpleScope for the containing record.

IHqlScope

Normally obtained by calling IHqlExpression::queryScope(). It is primarily used in the parser to resolve fields from within modules.

The ECL is parsed on demand so as the symbol is looked up it may cause a cascade of ECL to be compiled. The lookup context (HqlLookupContext ) is passed to IHqlScope::lookupSymbol() for several reasons:

  • It contains information about the active repository - the source of the ECL which will be dynamically parsed.
  • It contains caches of expanded functions - to avoid repeating expansion transforms.
  • Some members are used for tracking definitions that are read to build dependency graphs, or archives of submitted queries.

The interface IHqlScope currently has some members that are used for creation; this should be refactored and placed in a different interface.

IHqlDataset

This is normally obtained by calling IHqlExpression::queryDataset(). It has shrunk in size over time, and could quite possibly be folded into IHqlExpression with little pain.

There is a distinction in the code generator between "tables" and "datasets". A table (IHqlDataset::queryTable()) is a dataset operation that defines a new output record. Any operation that has a transform or record that defines an output record (e.g., PROJECT,TABLE) is a table, whilst those that don't (e.g., a filter, dedup) are not. There are a few apparent exceptions -e.g., IF (This is controlled by definesColumnList() which returns true the operator is a table.)

Properties and attributes

There are two related by slightly different concepts. An attribute refers to the explicit flags that are added to operators (e.g., , LOCAL, KEEP(n) etc. specified in the ECL or some internal attributes added by the code generator). There are a couple of different functions for creating attributes. createExtraAttribute() should be used by default. createAttribute() is reserved for an attribute that never has any arguments, or in unusual situations where it is important that the arguments are never transformed. They are tested using queryAttribute()/hasAttribute() and represented by nodes of kind no_attr/no_expr_attr.

The term "property" refers to computed information (e.g., record counts) that can be derived from the operator, its arguments and attributes. They are covered in more detail below.

Field references

Fields can be selected from active rows of a dataset in three main ways:

  • Some operators define LEFT/RIGHT to represent an input or processed dataset. Fields from these active rows are referenced with LEFT.<field-name>. Here LEFT or RIGHT is the "selector".

  • Other operators use the input dataset as the selector. E.g., myFile(myFile.id != 0). Here the input dataset is the "selector".

  • Often when the input dataset is used as the selector it can be omitted. E.g., myFile(id != 0). This is implicitly expanded by the PARSER to the second form. A reference to a field is always represented in the expression graph as a node of kind no_select (with createSelectExpr). The first child is the selector, and the second is the field. Needless to say there are some complications...

  • LEFT/RIGHT.

    The problem is that the different uses of LEFT/RIGHT need to be disambiguated since there may be several different uses of LEFT in a query. This is especially true when operations are executed in child queries. LEFT is represented by a node no_left(record, selSeq). Often the record is sufficient to disambiguate the uses, but there are situations where it isn't enough. So in addition no_left has a child which is a selSeq (selector sequence) which is added as a child attribute of the PROJECT or other operator. At parse time it is a function of the input dataset that is later normalized to a unique id to reduce the transformation work.

  • Active datasets. It is slightly more complicated - because the dataset used as the selector can be any upstream dataset up to the nearest table. So the following ECL code is legal:

    x := DATASET(...)
    +y := x(x.id != 0);
    +z := y(x.id != 100);
    +

Here the reference to x.id in the definition of z is referring to a field in the input dataset.

Because of these semantics the selector in a normalized tree is actually inputDataset->queryNormalizedSelector() rather than inputDatset. This function currently returns the table expression node (but it may change in the future see below).

Attribute "new"

In some situations ECL allows child datasets to be treated as a dataset without an explicit NORMALIZE. E.g., EXISTS(myDataset.ChildDataset);

This is primarily to enable efficient aggregates on disk files to be generated, but it adds some complications with an expression of the form dataset.childdataset.grandchild. E.g.,:

EXISTS(dataset(EXISTS(dataset.childdataset.grandchild))
+

Or:

EXISTS(dataset.childdataset(EXISTS(dataset.childdataset.grandchild))
+

In the first example dataset.childdataset within the dataset.childdataset.grandchild is a reference to a dataset that doesn't have an active cursor and needs to be iterated), whilst in the second it refers to an active cursor.

To differentiate between the two, all references to fields within datasets/rows that don't have active selectors have an additional attribute("new") as a child of the select. So a no_select with a "new" attribute requires the dataset to be created, one without is a member of an active dataset cursor.

If you have a nested row, the new attribute is added to the selection from the dataset, rather than the selection from the nested row. The functions queryDatasetCursor() and querySelectorDataset()) are used to help interpret the meaning.

(An alternative would be to use a different node from no_select - possibly this should be considered - it would be more space efficient.)

The expression graph generated by the ECL parser doesn't contain any new attributes. These are added as one of the first stages of normalizing the expression graph. Any code that works on normalized expressions needs to take care to interpret no_selects correctly.

Transforming selects

When an expression graph is transformed and none of the records are changed, the representation of LEFT/RIGHT remains the same. This means any no_select nodes in the expression tree will also stay the same.

However, if the transform modifies a table (highly likely) it means that the selector for the second form of field selector will also change. Unfortunately this means that transforms often cannot be short-circuited.

It could significantly reduce the extent of the graph that needs traversing, and the number of nodes replaced in a transformed graph if this could be avoided. One possibility is to use a different value for dataset->queryNormalizedSelector() using a unique id associated with the table. I think it would be a good long term change, but it would require unique ids (similar to the selSeq) to be added to all table expressions, and correctly preserved by any optimization.

Annotations

Sometimes it is useful to add information into the expression graph (e.g., symbol names, position information) that doesn't change the meaning, but should be preserved. Annotations allow information to be added in this way.

An annotation's implementation of IHqlExpression generally delegates the majority of the methods through to the annotated expression. This means that most code that interrogates the expression graph can ignore their presence, which simplifies the caller significantly. However transforms need to be careful (see below).

Information about the annotation can be obtained by calling IHqlExpression:: getAnnotationKind() and IHqlExpression:: queryAnnotation().

Associated side-effects

In legacy ECL you will see code like the following::

EXPORT a(x) := FUNCTION
+   Y := F(x);
+   OUTPUT(Y);
+   RETURN G(Y);
+END;
+

The assumption is that whenever a(x) is evaluated the value of Y will be output. However that doesn't particularly fit in with a declarative expression graph. The code generator creates a special node (no_compound) with child(0) as the output action, and child(1) as the value to be evaluated (g(Y)).

If the expression ends up being included in the final query then the action will also be included (via the no_compound). At a later stage the action is migrated to a position in the graph where actions are normally evaluated.

Derived properties

There are many pieces of information that it is useful to know about a node in the expression graph - many of which would be expensive to recomputed each time there were required. Eclcc has several mechanisms for caching derived information so it is available efficiently.

  • Boolean flags - getInfoFlags()/getInfoFlags2().

    There are many Boolean attributes of an expression that are useful to know - e.g., is it constant, does it have side-effects, does it reference any fields from a dataset etc. etc. The bulk of these are calculated and stored in a couple of members of the expression class. They are normally retrieved via accessor functions e.g., containsAssertKeyed(IHqlExpression*).

  • Active datasets - gatherTablesUsed().

    It is very common to want to know which datasets an expression references. This information is calculated and cached on demand and accessed via the IHqlExpression::gatherTablesUsed() functions. There are a couple of other functions IHqlExpression::isIndependentOfScope() and IHqlExpression::usesSelector() which provide efficient functions for common uses.

  • Information stored in the type.

    Currently datasets contain information about sort order, distribution and grouping as part of the expression type. This information should be accessed through the accessor functions applied to the expression (e.g., isGrouped(expr)). At some point in the future it is planned to move this information as a general derived property (see next).

  • Other derived property.

    There is a mechanism (in hqlattr) for calculating and caching an arbitrary derived property of an expression. It is currently used for number of rows, location-independent representation, maximum record size etc. . There are typically accessor functions to access the cached information (rather than calling the underlying IHqlExpression::queryAttribute() function).

  • Helper functions.

    Some information doesn't need to be cached because it isn't expensive to calculate, but rather than duplicating the code, a helper function is provided. E.g., queryOriginalRecord() and hasUnknownTransform(). They are not part of the interface because the number would make the interface unwieldy and they can be completely calculated from the public functions.

    However, it can be very hard to find the function you are looking for, and they would greatly benefit from being grouped e.g., into namespaces.

Transformations

One of the key processes in eclcc is walking and transforming the expression graphs. Both of these are covered by the term transformations. One of the key things to bear in mind is that you need to walk the expression graph as a graph, not as a tree. If you have already examined a node once you shouldn't repeat the work - otherwise the execution time may be exponential with node depth.

Other things to bear in mind

  • If a node isn't modified don't create a new one - return a link to the old one.
  • You generally need to walk the graph and gather some information before creating a modified graph. Sometimes creating a new graph can be short-circuited if no changes will be required.
  • Sometimes you can be tempted to try and short-circuit transforming part of a graph (e.g., the arguments to a dataset activity), but because of the way references to fields within dataset work that often doesn't work.
  • If an expression is moved to another place in the graph, you need to be very careful to check if the original context was conditional and that the new context is not.
  • The meaning of expressions can be context dependent. E.g., References to active datasets can be ambiguous.
  • Never walk the expressions as a tree, always as a graph!
  • Be careful with annotations.

It is essential that an expression that is used in different contexts with different annotations (e.g., two different named symbols) is consistently transformed. Otherwise it is possible for a graph to be converted into a tree. E.g.,:

A := x; B := x; C = A + B;
+

must not be converted to:

A' := x'; B' := X'';  C' := A' + B';
+

For this reason most transformers will check if expr->queryBody() matches expr, and if not will transform the body (the unannotated expression), and then clone any annotations.

Some examples of the work done by transformations are:

  • Constant folding.
  • Expanding function calls.
  • Walking the graph and reporting warnings.
  • Optimizing the order and removing redundant activities.
  • Reducing the fields flowing through the generated graph.
  • Spotting common sub expressions.
  • Calculating the best location to evaluate an expression (e.g., globally instead of in a child query).
  • Many, many others.

Some more details on the individual transforms are given below..

Key Stages

Parsing

The first job of eclcc is to parse the ECL into an expression graph. The source for the ECL can come from various different sources (archive, source files, remote repository). The details are hidden behind the IEclSource/IEclSourceCollection interfaces. The createRepository() function is then used to resolve and parse the various source files on demand.

Several things occur while the ECL is being parsed:

  • Function definitions are expanded inline.

    A slightly unusual behaviour. It means that the expression tree is a fully expanded expression -which is better suited to processing and optimizing.

  • Some limited constant folding occurs.

    When a function is expanded, often it means that some of the test conditions are always true/false. To reduce the transformations the condition may be folded early on.

  • When a symbol is referenced from another module this will recursively cause the ECL for that module (or definition within that module) to be parsed.

  • Currently the semantic checking is done as the ECL is parsed.

    If we are going to fully support template functions and delayed expansion of functions this will probably have to change so that a syntax tree is built first, and then the semantic checking is done later.

Normalizing

There are various problems with the expression graph that comes out of the parser:

  • Records can have values as children (e.g., { myField := infield.value} ), but it causes chaos if record definitions can change while other transformations are going on. So the normalization removes values from fields.

  • Some activities use records to define the values that output records should contain (e.g., TABLE). These are now converted to another form (e.g., no_newusertable).

  • Sometimes expressions have multiple definition names. Symbols and annotations are rationalized and commoned up to aid commoning up other expressions.

  • Some PATTERN definitions are recursive by name. They are resolved to a form that works if all symbols are removed.

  • The CASE/MAP representation for a dataset/action is awkward for the transforms to process. They are converted to nested Ifs.

    (At some point a different representation might be a good idea.)

  • EVALUATE is a weird syntax. Instances are replaced with equivalent code which is much easier to subsequently process.

  • The datasets used in index definitions are primarily there to provide details of the fields. The dataset itself may be very complex and may not actually be used. The dataset input to an index is replaced with a dummy "null" dataset to avoid unnecessary graph transforming, and avoid introducing any additional incorrect dependencies.

Scope checking

Generally if you use LEFT/RIGHT then the input rows are going to be available wherever they are used. However if they are passed into a function, and that function uses them inside a definition marked as global then that is invalid (since by definition global expressions don't have any context).

Similarly if you use syntax <dataset>.<field>, its validity and meaning depends on whether <dataset> is active. The scope transformer ensures that all references to fields are legal, and adds a "new" attribute to any no_selects where it is necessary.

Constant folding: foldHqlExpression

This transform simplifies the expression tree. Its aim is to simplify scalar expressions, and dataset expressions that are valid whether or not the nodes are shared. Some examples are:

  • 1 + 2 => 3 and any other operation on scalar constants.
  • IF(true, x, y) => x
  • COUNT(<empty-dataset>) => 0
  • IF (a = b, 'c', 'd') = 'd' => IF(a=b, false, true) => a != b
  • Simplifying sorts, projects filters on empty datasets

Most of the optimizations are fairly standard, but a few have been added to cover more esoteric examples which have occurred in queries over the years.

This transform also supports the option to percolate constants through the graph. E.g., if a project assigns the value 3 to a field, it can substitute the value 3 wherever that field is used in subsequent activities. This can often lead to further opportunities for constant folding (and removing fields in the implicit project).

Expression optimizer: optimizeHqlExpression

This transformer is used to simplify, combine and reorder dataset expressions. The transformer takes care to count the number of times each expression is used to ensure that none of the transformations cause duplication. E.g., swapping a filter with a sort is a good idea, but if there are two filters of the same sort and they are both swapped you will now be duplicating the sort.

Some examples of the optimizations include:

  • COUNT(SORT(x)) => COUNT(x)
  • Moving filters over projects, joins, sorts.
  • Combining adjacent projects, projects and joins.
  • Removing redundant sorts or distributes
  • Moving filters from JOINs to their inputs.
  • Combining activities e.g., CHOOSEN(SORT(x)) => TOPN(x)
  • Sometimes moving filters into IFs
  • Expanding out a field selected from a single row dataset.
  • Combine filters and projects into compound disk read operations.

Implicit project: insertImplicitProjects

ECL tends to be written as general purpose definitions which can then be combined. This can lead to potential inefficiencies - e.g., one definition may summarise some data in 20 different ways, this is then used by another definition which only uses a subset of those results. The implicit project transformer tracks the data flow at each point through the expression graph, and removes any fields that are not required.

This often works in combination with the other optimizations. For instance the constant percolation can remove the need for fields, and removing fields can sometimes allow a left outer join to be converted to a project.

Workunits

is this the correct term? Should it be a query? This should really be independent of this document...)

The code generator ultimately creates workunits. A workunit completely describes a generated query. It consists of two parts. There is an xml component - this contains the workflow information, the various execution graphs, and information about options. It also describes which inputs can be supplied to the query and what results are generated. The other part is the generated shared object compiled from the generated C++. This contains functions and classes that are used by the engines to execute the queries. Often the xml is compressed and stored as a resource within the shared object -so the shared object contains a complete workunit.

Workflow

The actions in a workunit are divided up into individual workflow items. Details of when each workflow item is executed, what its dependencies are stored in the <Workflow> section of the xml. The generated code also contains a class definition, with a method perform() which is used to execute the actions associated with a particular workflow item. (The class instances are created by calling the exported createProcess() factory function).

The generated code for an individual workflow item will typically call back into the engine at some point to execute a graph.

Graph

The activity graphs are stored in the xml. The graph contains details of which activities are required, how those activities link together, what dependencies there are between the activities. For each activity it might contain the following information:

  • A unique id.
  • The "kind" of the activity (from enum ThorActivityKind in eclhelper.hpp)
  • The ECL that created the activity.
  • Name of the original definition
  • Location (e.g., file, line number) of the original ECL.
  • Information about the record size, number of rows, sort order etc.
  • Hints which control options for a particular activity (e.g,, the number of threads to use while sorting).
  • Record counts and stats once the job has executed.

Each activity in a graph also has a corresponding helper class instance in the generated code. (The name of the class is cAc followed by the activity number, and the exported factory method is fAc followed by the activity number.) These classes implement the interfaces defined in eclhelper.hpp.

The engine uses the information from the xml to produce a graph of activities that need to be executed. It has a general purpose implementation of each activity kind, and it uses the class instance to tailor that general activity to the specific use e.g., what is the filter condition, what fields are set up, what is the sort order?

Inputs and Results

The workunit xml contains details of what inputs can be supplied when that workunit is run. These correspond to STORED definitions in the ECL. The result xml also contains the schema for the results that the workunit will generate.

Once an instance of the workunit has been run, the values of the results may be written back into dali's copy of the workunit so they can be retrieved and displayed.

Generated code

Aims for the generated C++ code:

  • Minimal include dependencies.

    Compile time is an issue - especially for small on-demand queries. To help reduce compile times (and dependencies with the rest of the system) the number of header files included by the generated code is kept to a minimum. In particular references to jlib, boost and icu are kept within the implementation of the runtime functions, and are not included in the public dependencies.

  • Thread-safe.

    It should be possible to use the members of an activity helper from multiple threads without issue. The helpers may contain some context dependent state, so different instances of the helpers are needed for concurrent use from different contexts (e.g., expansions of a graph.)

  • Concise.

    The code should be broadly readable, but the variable names etc. are chosen to generate compact code.

  • Functional.

    Generally the generated code assigns to a variable once, and doesn't modify it afterwards. Some assignments may be conditional, but once the variable is evaluated it isn't updated. (There are of course a few exceptions - e.g., dataset iterators)

Implementation details

First a few pointers to help understand the code within eclcc:

  • It makes extensive use of link counting. You need understand that concept to get very far.

  • If something is done more than once then that is generally split into a helper function.

    The helper functions aren't generally added to the corresponding interface (e.g., IHqlExpression) because the interface would become bloated. Instead they are added as global functions. The big disadvantage of this approach is they can be hard to find. Even better would be for them to be rationalised and organised into namespaces.

  • The code is generally thread-safe unless there would be a significant performance implication. In generally all the code used by the parser for creating expressions is thread safe. Expression graph transforms are thread-safe, and can execute in parallel if a constant (NUM_PARALLEL_TRANSFORMS) is increased. The data structures used to represent the generated code are NOT thread-safe.

  • Much of the code generation is structured fairly procedurally, with classes used to process the stages within it.

  • There is a giant "God" class HqlCppTranslator - which could really do with refactoring.

Parser

The eclcc parser uses the standard tools bison and flex to process the ECL and convert it to a

: expression graph. There are a couple of idiosyncrasies with the way it is implemented.

  • Macros with fully qualified scope.

    Slightly unusually macros are defined in the same way that other definitions are - in particular to can have references to macros in other modules. This means that there are references to macros within the grammar file (instead of being purely handled by a pre-processor). It also means the lexer keeps an active stack of macros being processed.

  • Attributes on operators.

    Many of the operators have optional attributes (e.g., KEEP, INNER, LOCAL, ...). If these were all reserved words it would remove a significant number of keywords from use as symbols, and could also mean that when a new attribute was added it broke existing code. To avoid this the lexer looks ahead in the parser tables (by following the potential reductions) to see if the token really could come next. If it can't then it isn't reserved as a symbol.

Generated code

As the workunit is created the code generator builds up the generated code and the xml for the workunit. Most of the xml generation is encapsulated within the IWorkUnit interface. The xml for the graphs is created in an IPropertyTree, and added to the workunit as a block.

C++ Output structures

The C++ generation is ultimately controlled by some template files (thortpl.cpp). The templates are plain text and contain references to allow named sections of code to be expanded at particular points.

The code generator builds up some structures in memory for each of those named sections. Once the generation is complete some peephole optimization is applied to the code. This structure is walked to expand each named section of code as required.

The BuildCtx class provides a cursor into that generated C++. It will either be created for a given named section, or more typically from another BuildCtx. It has methods for adding the different types of statements. Some are simple (e.g., addExpr()), whilst some create a compound statement (e.g., addFilter). The compound statements change the active selector so any new statements are added within that compound statement.

As well as building up a tree of expressions, this data structure also maintains a tree of associations. For instance when a value is evaluated and assigned to a temporary variable, the logical value is associated with that temporary. If the same expression is required later, the association is matched, and the temporary value is used instead of recalculating it. The associations are also used to track the active datasets, classes generated for row-meta information, activity classes etc. etc.

Activity Helper

Each activity in an expression graph will have an associated class generated in the C++. Each different activity kind expects a helper that implements a particular IHThorArg interface. E.g., a sort activity of kind TAKsort requires a helper that implements IHThorSortArg. The associated factory function is used to create instances of the helper class.

The generated class might take one of two forms:

  • A parameterised version of a library class. These are generated for simple helpers that don't have many variations (e.g., CLibrarySplitArg for TAKsplit), or for special cases that occur very frequently (CLibraryWorkUnitReadArg for internal results).
  • A class derived from a skeleton implementation of that helper (typically CThorXYZ implementing interface IHThorXYZ). The base class has default implementations of some of the functions, and any exceptions are implemented in the derived class.

Meta helper

This is a class that is used by the engines to encapsulate all the information about a single row -e.g., the format that each activity generates. It is an implementation of the IOutputMeta interface. It includes functions to

  • Return the size of the row.
  • Serialize and deserialize from disk.
  • Destroy and clean up row instances.
  • Convert to xml.
  • Provide information about the contained fields.

Building expressions

The same expression nodes are used for representing expressions in the generated C++ as the original ECL expression graph. It is important to keep track of whether an expression represents untranslated ECL, or the "translated" C++. For instance ECL has 1 based indexes, while C++ is zero based. If you processed the expression x[1] it might get translated to x[0] in C++. Translating it again would incorrectly refer to x[-1].

There are two key classes used while building the C++ for an ECL expression:

CHqlBoundExpr.

This represents a value that has been converted to C++. Depending on the type, one or more of the fields will be filled in.

CHqlBoundTarget.

This represents the target of an assignment -C++ variable(s) that are going to be assigned the result of evaluating an expression. It is almost always passed as a const parameter to a function because the target is well-defined and the function needs to update that target.

A C++ expression is sometimes converted back to an ECL pseudo-expression by calling getTranslatedExpr(). This creates an expression node of kind no_translated to indicate the child expression has already been converted.

Scalar expressions

The generation code for expressions has a hierarchy of calls. Each function is there to allow optimal code to be generated - e.g., not creating a temporary variable if none are required. A typical flow might be:

  • buildExpr(ctx, expr, bound).

    Evaluate the ecl expression "expr" and save the C++ representation in the class bound. This might then call through to...

  • buildTempExpr(ctx, expr, bound);

    Create a temporary variable, and evaluate expr and assign it to that temporary variable.... Which then calls.

  • buildExprAssign(ctx, target, expr);

    evaluate the expression, and ensure it is assigned to the C++ target "target".

    The default implementation might be to call buildExpr....

An operator must either be implemented in buildExpr() (calling a function called doBuildExprXXX) or in buildExprAssign() (calling a function called doBuildAssignXXX). Some operators are implemented in both places if there are different implementations that would be more efficient in each context.

Similarly there are several different assignment functions:

  • buildAssign(ctx, <ecl-target>, <ecl-value>);
  • buildExprAssign(ctx, <c++-target>, <ecl-value>);
  • assign(ctx, <C++target>, <c++source>)

The different varieties are there depending on whether the source value or targets have already been translated. (The names could be rationalised!)

Datasets

Most dataset operations are only implemented as activities (e.g., PARSE, DEDUP). If these are used within a transform/filter then eclcc will generate a call to a child query. An activity helper for the appropriate operation will then be generated.

However a subset of the dataset operations can also be evaluated inline without calling a child query. Some examples are filters, projects, and simple aggregation. It removes the overhead of the child query call in the simple cases, and often generates more concise code.

When datasets are evaluated inline there is a similar hierarchy of function calls:

  • buildDatasetAssign(ctx, target, expr);

    Evaluate the dataset expression, and assign it to the target (a builder interface). This may then call....

  • buildIterate(ctx, expr)

    Iterate through each of the rows in the dataset expression in turn. Which may then call...

  • buildDataset(ctx, expr, target, format)

    Build the entire dataset, and return it as a single value.

Some of the operations (e.g., aggregating a filtered dataset) can be done more efficiently by summing and filtering an iterator, than forcing the filtered dataset to be evaluated first.

Dataset cursors

The interface IHqlCppDatasetCursor allows the code generator to iterate through a dataset, or select a particular element from a dataset. It is used to hide the different representation of datasets, e.g.,

  • Blocked - the rows are in a contiguous block of memory appended one after another.
  • Array - the dataset is represented by an array of pointers to the individual rows.
  • Link counted - similar to array, but each element is also link counted.
  • Nested. Sometimes the cursor may iterate through multiple levels of child datasets.

Generally rows that are serialized (e.g., on disk) are in blocked format, and they are stored as link counted rows in memory.

Field access classes

The IReferenceSelector interface and the classes in hqltcppc[2] provide an interface for getting and setting values within a row of a dataset. They hide the details of the layout - e.g., csv/xml/raw data, and the details of exactly how each type is represented in the row.

Key filepos weirdness

The current implementation of keys in HPCC uses a format which uses a separate 8 byte integer field which was historically used to store the file position in the original file. Other complications are that the integer fields are stored big-endian, and signed integer values are biased.

This introduces some complication in the way indexes are handled. You will often find that the logical index definition is replaced with a physical index definition, followed by a project to convert it to the logical view. A similar process occurs for disk files to support VIRTUAL(FILEPOSITION) etc.

Source code

The following are the main directories used by the ecl compiler.


Directory Contents


rtl/eclrtpl Template text files used to generate the C++ code

rtl/include Headers that declare interfaces implemented by the generated code

common/deftype Interfaces and classes for scalar types and values.

common/workunit Code for managing the representation of a work unit.

ecl/hql Classes and interfaces for parsing and representing an ecl expression graph

ecl/hqlcpp Classes for converting an expression graph to a work unit (and C++)

ecl/eclcc The executable which ties everything together.

Challenges

From declarative to imperative

As mentioned at the start of this document, one of the main challenges with eclcc is converting the declarative ECL code into imperative C++ code. The direction we are heading in is to allow the engines to support more lazy-evaluation so possibly in this instance to evaluate it the first time it is used (although that may potentially be much less efficient). This will allow the code generator to relax some of its current assumptions.

There are several example queries which are already producing pathological behaviour from eclcc, causing it to generate C++ functions which are many thousands of lines long.

The parser

Currently the grammar for the parser is too specialised. In particular the separate productions for expression, datasets, actions cause problems - e.g., it is impossible to properly allow sets of datasets to be treated in the same way as other sets.

The semantic checking (and probably semantic interpretation) is done too early. Really the parser should build up a syntax tree, and then disambiguate it and perform the semantic checks on the syntax tree.

The function calls should probably be expanded later than they are. I have tried in the past and hit problems, but I can't remember all the details. Some are related to the semantic checking.

`,223)]))}const f=t(r,[["render",s]]);export{u as __pageData,f as default}; diff --git a/assets/devdoc_CodeReviews.md.BB4WmPQp.js b/assets/devdoc_CodeReviews.md.BB4WmPQp.js new file mode 100644 index 00000000000..7f3ac71a5e8 --- /dev/null +++ b/assets/devdoc_CodeReviews.md.BB4WmPQp.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as a}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Code Review Guidelines","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/CodeReviews.md","filePath":"devdoc/CodeReviews.md","lastUpdated":1731340314000}'),s={name:"devdoc/CodeReviews.md"};function r(n,e,d,l,h,c){return a(),i("div",null,e[0]||(e[0]=[o('

Code Review Guidelines

The Code Submissions document is aimed at developers that are submitting PRs. This document describes some of the goals and expectations for code reviewers.

Review Goals

Code reviews have a few different goals:

  • Catch architectural or design problems.
    These should have been caught earlier, but better later than never...
  • Catch bugs early (incorrect behaviour, inefficiencies, security issues)
  • Ensure the code is readable and maintainable.
    This includes following the project coding standards (see Style Guide).
  • A opportunity for training/passing on information.
    For example providing information about how the current system works, functionality that is already available or suggestions of other approaches the developer may not have thought of.

It is NOT a goal to change the submission until it matches how the reviewer would have coded it.

General comments

Some general comments on code reviews:

  • Code reviewers should be explicit and clearly describe the problem.
    This should include what change is expected if not obvious. Don’t assume the contributor has same understanding/view as reviewer.
  • If a comment is not clear the contributor should ask for clarification.
    ...rather than wasting time trying to second-guess the reviewer.
  • Contributors should feel free to push back if they consider comments are too picky.
    The reviewer can either agree, or provide reasons why they consider it to be an issue.
  • The reviewer should not extend the scope of the original change.
    If the change could be extended, or only partially solves the issue, a new JIRA should be created for the extra work. If the change will introduce regressions, or fundamentally fails to solve the problem then this does not apply!
  • Clearly indicate if a review is incomplete.
    Sometimes a significant design problem means the rest of the code has not been reviewed in detail. Other times an initial review has picked up a set of issues, but the reviewer needs to go back and check other aspects in detail. If this is the case it should be explicitly noted.
  • Repeated issues.
    The reviewer is free to comment on every instance of a repeated issue, but a simple annotation should alert contributor to address appropriately eg: [Please address all instances of this issue]
  • Contributers should provide feedback to the reviewer.
    The contributor should respond to a comment if it isn't obvious where/how they have been addressed (but no need to acknowledge typo/indentation/etc)
  • Only the reviewer should mark issues as resolved using the Github resolve conversation button.
  • Code reviews should be a priority.
    Both reviewers and contributors should respond in a timely manner - don't leave it for days. It destroys the flow of thought and conversation.
  • Check all review comments have been addressed.
    If they have not been addressed you are guaranteed another review/submit cycle. In particular watch out for collapsed conversations. If there are large numbers of comments GitHub will collapse them, which can make comments easy to miss.
  • Sometimes PRs need to be restarted.
    If there are large number of comments > 100, it can be hard to track all the comments and GitHub can become unresponsive. It may be better to close the PR and open a new one.
  • Submit any changes as extra commits. This makes it clear to the reviewer what has changed, and avoids them having to re-review everything. Please do not squash them until the reviewers approve the PR. The few exceptions to this are if the PR is only a couple of lines, or the PR is completely rewritten in response to the review.
  • Reviewers use GitHub's features
    Making use of the "viewed" button can make it easier to track what has changed - or quickly remove trivial changes from view. Ignoring whitespace can often simplify comparisons - especially when code has been refactored or extra conditions or try/catch bocks have been introduced.

Strictness

All code reviews don't need to be equally strict. The "strictness" of the review should reflect the importance and location of the change. Some examples:

  • If it is closely associated with an existing file, then the indentation, style should match the surrounding code - a mixture of styles makes it much harder to read. If it is in a new, independent source file or project this is less of an issue.
  • If the code is in a core library then efficiency and edge cases will be more important.
  • If it is a core part of the system then security is key. If it is a developer only tool then edge cases are less significant.
  • Reviews of draft pull requests are likely to concentrate on the overall approach, rather than the details. They are likely to be more informal (e.g. not always using comments tags).

Checklist

What are some examples of checks to bear in mind when reviewing code?

General:

  • Is the commit title in the correct format, and understandable in a change log?
  • Is the target correct?
  • Is the size appropriate. Could it have been split up?
  • Does the jira contain details of the change, especially the reason?
  • Does it duplicate other functionality?
  • Does the style match the context and the style guide?
  • Is the design encapsulated at the right level? Too abstract or too concrete?

Content:

  • Silly mistakes - indent, typos, commented outcode, spurious changes.
  • Does it introduce any memory leaks? E.g. Correct use of linking? Are exceptions released?
  • Thread safety
    • critical sections or atomic variables if accessed by more than one thread
    • race conditions
    • deadlock
  • authorization. Should it be checked, does it fail by default?
  • Any potential for overflow or DOS? Are all user inputs validated and all lengths protected?
  • Are all secrets stored and passed securely?
  • Comments explaining why for any code that is complex or counter-intuitive.
  • Backward compatibility.
    Could this possibly cause problems if data produced with this change is used in earlier/later versions? Could there be problems if it was used in a mixed-version environment?

Comment tags

When reading comments in a review it can sometimes be hard to know why the reviewer made a comment, or what response is expected. If there is any doubt the contributor should ask. However to make it clearer we are aiming to always add a tag to the front of each review comment. The tag will give an indication of why the comment is being made, its severity and what kind of response is expected. Here is a provisional table of tags:

TagWhatWhyExpected response
design:An architectural or design issueThe reviewer considers the PR has a significant problem which will affect its functionality or future extensibilityreviewer/developer redesign expected before any further changes
scope:The scope of the PR does not match the JiraIf the scope of the fix is too large it can be hard to review, and take much longer to resolve all the issues before the PR is accepted.Discussion. Split the PR into multiple simpler PRs.
function:Incorrect/unexpected functionality implementedThe function doesn't match the description in the jira, or doesn't solve the original problemdeveloper expected to address issue (or discuss)
security:Something in the code introduces a security problemThe reviewer has spotted potential security issues e.g. injection attacksdeveloper expected to discuss the issue (and then address)
bug:A coding issue that will cause incorrect behaviourLikely to cause confusion, invalid results or crashes.developer expected to address issue
efficiency:The code works, but may have scaling or other efficiency issues.Inefficiency can cause problem in some key functions and areasdeveloper addressing the problem (or discuss)
discuss:Reviewer has thought of a potential problem, but not sure if it appliesReviewer has a concern it may be an issue, and wants to check the developer has thought about and addressed the issueDiscussion - either in the PR or offline.
style:Reviewer points out non-conforming code styleMakes the code hard to readDeveloper to fix
indent:A fairly obvious indentation issueMakes the code hard to readDeveloper to fix.
format:Any other unusual formattingMakes the code hard to readDeveloper to fix.
typo:Minor typing errorMakes something (code/message/comment) harder to readDeveloper to fix.
minor:A minor issue that could be improved.Education (the suggestion is better for a particular reason), or something simple to clean up at the same time as other changesDeveloper recommended to fix, but unlikely to stop a merge
picky:A very minor issue that could be improved, but is barely worth commenting onEducation, or something to clean up at the same time as other changesDeveloper discretion to fix, wouldn't stop a merge
future:An additional feature or functionality that fits in but should be done as a separate PR.Ensure that missing functionality is tracked, but PRs are not held up by additional requirements.Contributor to create Jira (unless trivial) and number noted in response.
question:Review has a question that they are not sure of the answer toReviewer would like clarification to help understand the code or design. The answer may lead to further comments.An answer to the question.
note:Reviewer wants to pass on some information to the contributor which they may not knowPassing on knowledge/backgroundcontributor should consider the note, but no change expected/required
personal:Reviewer has an observation based on personal experienceReviewer has comments that would improve the code, but not part of the style guide or required. E.g. patterns for guard conditionsReflect on the suggestion, but no change expected.
documentation:This change may have an impact on documentationMake sure changes can be usedContributor to create Jira describing the impact created, and number noted in response.

The comments should always be constructive. The reviewer should have a reason for each of them, and be able to articulate the reason in the comment or when asked. "I wouldn't have done it like that" is not a good enough on its own!

Similarly there is a difference in opinion within the team on some style issues - e.g. standard libraries or jlib, inline or out of line functions, nested or non-nested classes. Reviews should try and avoid commenting on these unless there is a clear reason why they are significant (functionality, efficiency, compile time) and if so spell it out. Code reviewers should discuss any style issues that they consider should be universally adopted that are not in the style guide.

',23)]))}const p=t(s,[["render",r]]);export{m as __pageData,p as default}; diff --git a/assets/devdoc_CodeReviews.md.BB4WmPQp.lean.js b/assets/devdoc_CodeReviews.md.BB4WmPQp.lean.js new file mode 100644 index 00000000000..7f3ac71a5e8 --- /dev/null +++ b/assets/devdoc_CodeReviews.md.BB4WmPQp.lean.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as a}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Code Review Guidelines","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/CodeReviews.md","filePath":"devdoc/CodeReviews.md","lastUpdated":1731340314000}'),s={name:"devdoc/CodeReviews.md"};function r(n,e,d,l,h,c){return a(),i("div",null,e[0]||(e[0]=[o('

Code Review Guidelines

The Code Submissions document is aimed at developers that are submitting PRs. This document describes some of the goals and expectations for code reviewers.

Review Goals

Code reviews have a few different goals:

  • Catch architectural or design problems.
    These should have been caught earlier, but better later than never...
  • Catch bugs early (incorrect behaviour, inefficiencies, security issues)
  • Ensure the code is readable and maintainable.
    This includes following the project coding standards (see Style Guide).
  • A opportunity for training/passing on information.
    For example providing information about how the current system works, functionality that is already available or suggestions of other approaches the developer may not have thought of.

It is NOT a goal to change the submission until it matches how the reviewer would have coded it.

General comments

Some general comments on code reviews:

  • Code reviewers should be explicit and clearly describe the problem.
    This should include what change is expected if not obvious. Don’t assume the contributor has same understanding/view as reviewer.
  • If a comment is not clear the contributor should ask for clarification.
    ...rather than wasting time trying to second-guess the reviewer.
  • Contributors should feel free to push back if they consider comments are too picky.
    The reviewer can either agree, or provide reasons why they consider it to be an issue.
  • The reviewer should not extend the scope of the original change.
    If the change could be extended, or only partially solves the issue, a new JIRA should be created for the extra work. If the change will introduce regressions, or fundamentally fails to solve the problem then this does not apply!
  • Clearly indicate if a review is incomplete.
    Sometimes a significant design problem means the rest of the code has not been reviewed in detail. Other times an initial review has picked up a set of issues, but the reviewer needs to go back and check other aspects in detail. If this is the case it should be explicitly noted.
  • Repeated issues.
    The reviewer is free to comment on every instance of a repeated issue, but a simple annotation should alert contributor to address appropriately eg: [Please address all instances of this issue]
  • Contributers should provide feedback to the reviewer.
    The contributor should respond to a comment if it isn't obvious where/how they have been addressed (but no need to acknowledge typo/indentation/etc)
  • Only the reviewer should mark issues as resolved using the Github resolve conversation button.
  • Code reviews should be a priority.
    Both reviewers and contributors should respond in a timely manner - don't leave it for days. It destroys the flow of thought and conversation.
  • Check all review comments have been addressed.
    If they have not been addressed you are guaranteed another review/submit cycle. In particular watch out for collapsed conversations. If there are large numbers of comments GitHub will collapse them, which can make comments easy to miss.
  • Sometimes PRs need to be restarted.
    If there are large number of comments > 100, it can be hard to track all the comments and GitHub can become unresponsive. It may be better to close the PR and open a new one.
  • Submit any changes as extra commits. This makes it clear to the reviewer what has changed, and avoids them having to re-review everything. Please do not squash them until the reviewers approve the PR. The few exceptions to this are if the PR is only a couple of lines, or the PR is completely rewritten in response to the review.
  • Reviewers use GitHub's features
    Making use of the "viewed" button can make it easier to track what has changed - or quickly remove trivial changes from view. Ignoring whitespace can often simplify comparisons - especially when code has been refactored or extra conditions or try/catch bocks have been introduced.

Strictness

All code reviews don't need to be equally strict. The "strictness" of the review should reflect the importance and location of the change. Some examples:

  • If it is closely associated with an existing file, then the indentation, style should match the surrounding code - a mixture of styles makes it much harder to read. If it is in a new, independent source file or project this is less of an issue.
  • If the code is in a core library then efficiency and edge cases will be more important.
  • If it is a core part of the system then security is key. If it is a developer only tool then edge cases are less significant.
  • Reviews of draft pull requests are likely to concentrate on the overall approach, rather than the details. They are likely to be more informal (e.g. not always using comments tags).

Checklist

What are some examples of checks to bear in mind when reviewing code?

General:

  • Is the commit title in the correct format, and understandable in a change log?
  • Is the target correct?
  • Is the size appropriate. Could it have been split up?
  • Does the jira contain details of the change, especially the reason?
  • Does it duplicate other functionality?
  • Does the style match the context and the style guide?
  • Is the design encapsulated at the right level? Too abstract or too concrete?

Content:

  • Silly mistakes - indent, typos, commented outcode, spurious changes.
  • Does it introduce any memory leaks? E.g. Correct use of linking? Are exceptions released?
  • Thread safety
    • critical sections or atomic variables if accessed by more than one thread
    • race conditions
    • deadlock
  • authorization. Should it be checked, does it fail by default?
  • Any potential for overflow or DOS? Are all user inputs validated and all lengths protected?
  • Are all secrets stored and passed securely?
  • Comments explaining why for any code that is complex or counter-intuitive.
  • Backward compatibility.
    Could this possibly cause problems if data produced with this change is used in earlier/later versions? Could there be problems if it was used in a mixed-version environment?

Comment tags

When reading comments in a review it can sometimes be hard to know why the reviewer made a comment, or what response is expected. If there is any doubt the contributor should ask. However to make it clearer we are aiming to always add a tag to the front of each review comment. The tag will give an indication of why the comment is being made, its severity and what kind of response is expected. Here is a provisional table of tags:

TagWhatWhyExpected response
design:An architectural or design issueThe reviewer considers the PR has a significant problem which will affect its functionality or future extensibilityreviewer/developer redesign expected before any further changes
scope:The scope of the PR does not match the JiraIf the scope of the fix is too large it can be hard to review, and take much longer to resolve all the issues before the PR is accepted.Discussion. Split the PR into multiple simpler PRs.
function:Incorrect/unexpected functionality implementedThe function doesn't match the description in the jira, or doesn't solve the original problemdeveloper expected to address issue (or discuss)
security:Something in the code introduces a security problemThe reviewer has spotted potential security issues e.g. injection attacksdeveloper expected to discuss the issue (and then address)
bug:A coding issue that will cause incorrect behaviourLikely to cause confusion, invalid results or crashes.developer expected to address issue
efficiency:The code works, but may have scaling or other efficiency issues.Inefficiency can cause problem in some key functions and areasdeveloper addressing the problem (or discuss)
discuss:Reviewer has thought of a potential problem, but not sure if it appliesReviewer has a concern it may be an issue, and wants to check the developer has thought about and addressed the issueDiscussion - either in the PR or offline.
style:Reviewer points out non-conforming code styleMakes the code hard to readDeveloper to fix
indent:A fairly obvious indentation issueMakes the code hard to readDeveloper to fix.
format:Any other unusual formattingMakes the code hard to readDeveloper to fix.
typo:Minor typing errorMakes something (code/message/comment) harder to readDeveloper to fix.
minor:A minor issue that could be improved.Education (the suggestion is better for a particular reason), or something simple to clean up at the same time as other changesDeveloper recommended to fix, but unlikely to stop a merge
picky:A very minor issue that could be improved, but is barely worth commenting onEducation, or something to clean up at the same time as other changesDeveloper discretion to fix, wouldn't stop a merge
future:An additional feature or functionality that fits in but should be done as a separate PR.Ensure that missing functionality is tracked, but PRs are not held up by additional requirements.Contributor to create Jira (unless trivial) and number noted in response.
question:Review has a question that they are not sure of the answer toReviewer would like clarification to help understand the code or design. The answer may lead to further comments.An answer to the question.
note:Reviewer wants to pass on some information to the contributor which they may not knowPassing on knowledge/backgroundcontributor should consider the note, but no change expected/required
personal:Reviewer has an observation based on personal experienceReviewer has comments that would improve the code, but not part of the style guide or required. E.g. patterns for guard conditionsReflect on the suggestion, but no change expected.
documentation:This change may have an impact on documentationMake sure changes can be usedContributor to create Jira describing the impact created, and number noted in response.

The comments should always be constructive. The reviewer should have a reason for each of them, and be able to articulate the reason in the comment or when asked. "I wouldn't have done it like that" is not a good enough on its own!

Similarly there is a difference in opinion within the team on some style issues - e.g. standard libraries or jlib, inline or out of line functions, nested or non-nested classes. Reviews should try and avoid commenting on these unless there is a clear reason why they are significant (functionality, efficiency, compile time) and if so spell it out. Code reviewers should discuss any style issues that they consider should be universally adopted that are not in the style guide.

',23)]))}const p=t(s,[["render",r]]);export{m as __pageData,p as default}; diff --git a/assets/devdoc_CodeSubmissions.md.DoLTBfxy.js b/assets/devdoc_CodeSubmissions.md.DoLTBfxy.js new file mode 100644 index 00000000000..d6cbfdc68f3 --- /dev/null +++ b/assets/devdoc_CodeSubmissions.md.DoLTBfxy.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as s}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Code Submission Guidelines","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/CodeSubmissions.md","filePath":"devdoc/CodeSubmissions.md","lastUpdated":1731340314000}'),r={name:"devdoc/CodeSubmissions.md"};function a(n,e,h,l,d,c){return s(),i("div",null,e[0]||(e[0]=[o('

Code Submission Guidelines

We welcome submissions to the platform especially in the form of pull requests into the HPCC-Systems github repository. The following describes some of processes for merging PRs.

Pull requests

There are a few things that should be considered when creating a PR to increase the likelihood that they can be accepted quickly.

  • Write a good commit message
    The format should be HPCC-XXXXX (where XXXXX is the bug number) followed by a description of the issue. The text should make sense in a change log by itself - without reference to the jira or the contents of the PR. We should aim to increase the information that is included as part of the commit message - not rely on on the jira.
  • Ensure the reviewer has enough information to review the change.
    The code reviewer only has the JIRA and the PR to go on. The JIRA (or associated documentation) should contain enough details to review the PR - e.g. the purpose, main aim, why the change was made etc.. If the scope of the jira has changed then the jira should be updated to reflect that.
    If the submission requires changes to the documentation then the JIRA should contain all the details needed to document it, and the PR should either contain the documentation changes, or a documentation JIRA should be created.
  • Fill in the checklist
    The check boxes are there to remind you to consider different aspects of the PR. Not all of them apply to every submission, but if you tick a box and have not really thought about the item then prepare to be embarrassed!
  • Prefer small submissions
    It isn't always possible, but several smaller PRs are much easier to review than one large change. If your submission includes semi-automatic/mechanical changes (e.g. renaming large numbers of function calls, or adding an extra parameter) please keep it as a separate commit. This makes it much easier to review the PR - since the reviewer will be looking for different errors in the different types of changes.
  • Check for silly mistakes
    Review your own code in github, after creating the PR to check for silly mistakes. It doesn't take long, and often catches trivial issues. It may avoid the need for a cycle of code-review/fixes. It may be helpful to add some notes to specific changes e.g. "this change is mainly or solely refactoring method A into method B and C. ". Some common examples of trivial issues to look for include:
    • Inconsistent indentation, or using tabs rather than spaces to indent.
    • Lines of tracing left in.
    • Lines of code commented out that should be deleted.
    • TBD reminders of work that need implementing or removing.
    • Unrelated files that have been accidentally modified.
    • Accidental changes to submodule versions.
    • Typos in error messages, tracing or comments, or in the commit message.
    • Incomplete edits when copy and pasting code.
    • Check subtractions are the right way around, and potential for overflow.
    • New files with the wrong copyright date
  • Check the target branch (see below)
  • Request one or more reviews. For relatively simple changes a single reviewer is normally enough.

Reviewers

All pull requests should be reviewed by someone who is not the author before merging. Complex changes, changes that require input from multiple experts, or that have implications throughout the system should be reviewed by multiple reviewers. This should include someone who is responsible for merging changes for that part of the system. (Unless it is a simple change written by someone with review rights.)

Contributors should use the github reviewers section on the PR to request reviews. After a contributor has pushed a set of changes in response to a review, they should refresh the github review status, so the users are notified it is ready for re-review. When the review is complete, a person with responsibility for merging changes to that part of the system should be added as a reviewer (or refreshed), with a comment that it is ready to merge.

Reviewers should check for PRs that are ready for their review via github's webpage (filter "review-requested:<reviewer-id>") or via the github CLI (e.g. gh pr status). Contributors should similarly ensure they stay up to date with any comments on requests for change on their submissions.

Target branch

The Version support document contains details of the different versions that are supported, and which version should be targetted for different kinds of changes. Occasionally earlier branches will be chosen, (e.g. security fixes to even older versions) but they should always be carefully discussed (and documented).

Changes will always be upmerged into the next point release for all the more recent major and minor versions (and master).

',12)]))}const f=t(r,[["render",a]]);export{m as __pageData,f as default}; diff --git a/assets/devdoc_CodeSubmissions.md.DoLTBfxy.lean.js b/assets/devdoc_CodeSubmissions.md.DoLTBfxy.lean.js new file mode 100644 index 00000000000..d6cbfdc68f3 --- /dev/null +++ b/assets/devdoc_CodeSubmissions.md.DoLTBfxy.lean.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as s}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Code Submission Guidelines","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/CodeSubmissions.md","filePath":"devdoc/CodeSubmissions.md","lastUpdated":1731340314000}'),r={name:"devdoc/CodeSubmissions.md"};function a(n,e,h,l,d,c){return s(),i("div",null,e[0]||(e[0]=[o('

Code Submission Guidelines

We welcome submissions to the platform especially in the form of pull requests into the HPCC-Systems github repository. The following describes some of processes for merging PRs.

Pull requests

There are a few things that should be considered when creating a PR to increase the likelihood that they can be accepted quickly.

  • Write a good commit message
    The format should be HPCC-XXXXX (where XXXXX is the bug number) followed by a description of the issue. The text should make sense in a change log by itself - without reference to the jira or the contents of the PR. We should aim to increase the information that is included as part of the commit message - not rely on on the jira.
  • Ensure the reviewer has enough information to review the change.
    The code reviewer only has the JIRA and the PR to go on. The JIRA (or associated documentation) should contain enough details to review the PR - e.g. the purpose, main aim, why the change was made etc.. If the scope of the jira has changed then the jira should be updated to reflect that.
    If the submission requires changes to the documentation then the JIRA should contain all the details needed to document it, and the PR should either contain the documentation changes, or a documentation JIRA should be created.
  • Fill in the checklist
    The check boxes are there to remind you to consider different aspects of the PR. Not all of them apply to every submission, but if you tick a box and have not really thought about the item then prepare to be embarrassed!
  • Prefer small submissions
    It isn't always possible, but several smaller PRs are much easier to review than one large change. If your submission includes semi-automatic/mechanical changes (e.g. renaming large numbers of function calls, or adding an extra parameter) please keep it as a separate commit. This makes it much easier to review the PR - since the reviewer will be looking for different errors in the different types of changes.
  • Check for silly mistakes
    Review your own code in github, after creating the PR to check for silly mistakes. It doesn't take long, and often catches trivial issues. It may avoid the need for a cycle of code-review/fixes. It may be helpful to add some notes to specific changes e.g. "this change is mainly or solely refactoring method A into method B and C. ". Some common examples of trivial issues to look for include:
    • Inconsistent indentation, or using tabs rather than spaces to indent.
    • Lines of tracing left in.
    • Lines of code commented out that should be deleted.
    • TBD reminders of work that need implementing or removing.
    • Unrelated files that have been accidentally modified.
    • Accidental changes to submodule versions.
    • Typos in error messages, tracing or comments, or in the commit message.
    • Incomplete edits when copy and pasting code.
    • Check subtractions are the right way around, and potential for overflow.
    • New files with the wrong copyright date
  • Check the target branch (see below)
  • Request one or more reviews. For relatively simple changes a single reviewer is normally enough.

Reviewers

All pull requests should be reviewed by someone who is not the author before merging. Complex changes, changes that require input from multiple experts, or that have implications throughout the system should be reviewed by multiple reviewers. This should include someone who is responsible for merging changes for that part of the system. (Unless it is a simple change written by someone with review rights.)

Contributors should use the github reviewers section on the PR to request reviews. After a contributor has pushed a set of changes in response to a review, they should refresh the github review status, so the users are notified it is ready for re-review. When the review is complete, a person with responsibility for merging changes to that part of the system should be added as a reviewer (or refreshed), with a comment that it is ready to merge.

Reviewers should check for PRs that are ready for their review via github's webpage (filter "review-requested:<reviewer-id>") or via the github CLI (e.g. gh pr status). Contributors should similarly ensure they stay up to date with any comments on requests for change on their submissions.

Target branch

The Version support document contains details of the different versions that are supported, and which version should be targetted for different kinds of changes. Occasionally earlier branches will be chosen, (e.g. security fixes to even older versions) but they should always be carefully discussed (and documented).

Changes will always be upmerged into the next point release for all the more recent major and minor versions (and master).

',12)]))}const f=t(r,[["render",a]]);export{m as __pageData,f as default}; diff --git a/assets/devdoc_DevDocs.md.DbnKa3rJ.js b/assets/devdoc_DevDocs.md.DbnKa3rJ.js new file mode 100644 index 00000000000..18a1bd08827 --- /dev/null +++ b/assets/devdoc_DevDocs.md.DbnKa3rJ.js @@ -0,0 +1,8 @@ +import{_ as t,c as a,a3 as o,o as i}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Working with developer documentation","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/DevDocs.md","filePath":"devdoc/DevDocs.md","lastUpdated":1731340314000}'),n={name:"devdoc/DevDocs.md"};function s(d,e,l,r,c,h){return i(),a("div",null,e[0]||(e[0]=[o(`

Working with developer documentation

Some basic guidlines to ensure your documentation works well with VitePress

Documentation location

Documents can be located anywhere in the repository folder structure. If it makes sense to have documentation "close" to specific components, then it can be located in the same folder as the component. For example, any developer documentation for specific plugins can be located in those folders. If this isn't appropriate then the documentation can be located in the devdoc or subfolders of devdoc.

WARNING

There is an exclusion list in the devdoc/.vitepress/config.js file that prevents certain folders from being included in the documentation. If you add a new document to a folder that is excluded, then it will not be included in the documentation. If you need to add a new document to an excluded folder, then you will need to update the exclusion list in the devdoc/.vitepress/config.js file.

Documentation format

Documentation is written in Markdown. This is a simple format that is easy to read and write. It is also easy to convert to other formats, such as HTML, PDF, and Word. Markdown is supported by many editors, including Visual Studio Code, and is supported by VitePress.

TIP

VitePress extends Markdown with some additional features, such as custom containers, it is recommended that you refer to the VitePress documentation for more details.

Rendering documentation locally with VitePress

To assist with the writing of documentation, VitePress can be used to render the documentation locally. This allows you to see how the documentation will look when it is published. To start the local development server you need to type the following commands in the root HPCC-Platform folder:

sh
npm install
+npm run docs-dev

This will start a local development server and display the URL that you can use to view the documentation. The default URL is http://localhost:5173/HPCC-Platform, but it may be different on your machine. The server will automatically reload when you make changes to the documentation.

WARNING

The first time you start the VitePress server it will take a while to complete. This is because it is locating all the markdown files in the repository and creating the html pages. Once it has completed this step once, it will be much faster to start the server again.

Adding a new document

To add a new document, you need to add a new markdown file to the repository. The file should be named appropriately and have the .md file extension. Once the file exists, you can view it by navigating to the appropriate URL. For example, if you add a new file called MyNewDocument.md to the devdoc folder, then you can view it by navigating to http://localhost:5173/HPCC-Platform/devdoc/MyNewDocument.html.

Adding a new document to the sidebar

To add a new document to the sidebar, you need to add an entry to the devdoc/.vitepress/config.js file. The entry should be added to the sidebar section. For example, to add a new document called MyNewDocument.md to the devdoc folder, you would add the following entry to the sidebar section:

js
sidebar: [
+    ...
+    {
+        text: 'My New Document',
+        link: '/devdoc/MyNewDocument'
+    }
+    ...

TIP

You can find more information on the config.js file in the VitePress documentation.

Editing the main landing page

The conent of the main landing page is located in index.md in the root folder. Its structure uses the VitePress "home" layout.

`,21)]))}const m=t(n,[["render",s]]);export{u as __pageData,m as default}; diff --git a/assets/devdoc_DevDocs.md.DbnKa3rJ.lean.js b/assets/devdoc_DevDocs.md.DbnKa3rJ.lean.js new file mode 100644 index 00000000000..18a1bd08827 --- /dev/null +++ b/assets/devdoc_DevDocs.md.DbnKa3rJ.lean.js @@ -0,0 +1,8 @@ +import{_ as t,c as a,a3 as o,o as i}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Working with developer documentation","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/DevDocs.md","filePath":"devdoc/DevDocs.md","lastUpdated":1731340314000}'),n={name:"devdoc/DevDocs.md"};function s(d,e,l,r,c,h){return i(),a("div",null,e[0]||(e[0]=[o(`

Working with developer documentation

Some basic guidlines to ensure your documentation works well with VitePress

Documentation location

Documents can be located anywhere in the repository folder structure. If it makes sense to have documentation "close" to specific components, then it can be located in the same folder as the component. For example, any developer documentation for specific plugins can be located in those folders. If this isn't appropriate then the documentation can be located in the devdoc or subfolders of devdoc.

WARNING

There is an exclusion list in the devdoc/.vitepress/config.js file that prevents certain folders from being included in the documentation. If you add a new document to a folder that is excluded, then it will not be included in the documentation. If you need to add a new document to an excluded folder, then you will need to update the exclusion list in the devdoc/.vitepress/config.js file.

Documentation format

Documentation is written in Markdown. This is a simple format that is easy to read and write. It is also easy to convert to other formats, such as HTML, PDF, and Word. Markdown is supported by many editors, including Visual Studio Code, and is supported by VitePress.

TIP

VitePress extends Markdown with some additional features, such as custom containers, it is recommended that you refer to the VitePress documentation for more details.

Rendering documentation locally with VitePress

To assist with the writing of documentation, VitePress can be used to render the documentation locally. This allows you to see how the documentation will look when it is published. To start the local development server you need to type the following commands in the root HPCC-Platform folder:

sh
npm install
+npm run docs-dev

This will start a local development server and display the URL that you can use to view the documentation. The default URL is http://localhost:5173/HPCC-Platform, but it may be different on your machine. The server will automatically reload when you make changes to the documentation.

WARNING

The first time you start the VitePress server it will take a while to complete. This is because it is locating all the markdown files in the repository and creating the html pages. Once it has completed this step once, it will be much faster to start the server again.

Adding a new document

To add a new document, you need to add a new markdown file to the repository. The file should be named appropriately and have the .md file extension. Once the file exists, you can view it by navigating to the appropriate URL. For example, if you add a new file called MyNewDocument.md to the devdoc folder, then you can view it by navigating to http://localhost:5173/HPCC-Platform/devdoc/MyNewDocument.html.

Adding a new document to the sidebar

To add a new document to the sidebar, you need to add an entry to the devdoc/.vitepress/config.js file. The entry should be added to the sidebar section. For example, to add a new document called MyNewDocument.md to the devdoc folder, you would add the following entry to the sidebar section:

js
sidebar: [
+    ...
+    {
+        text: 'My New Document',
+        link: '/devdoc/MyNewDocument'
+    }
+    ...

TIP

You can find more information on the config.js file in the VitePress documentation.

Editing the main landing page

The conent of the main landing page is located in index.md in the root folder. Its structure uses the VitePress "home" layout.

`,21)]))}const m=t(n,[["render",s]]);export{u as __pageData,m as default}; diff --git a/assets/devdoc_Development.md.Cz70vUo4.js b/assets/devdoc_Development.md.Cz70vUo4.js new file mode 100644 index 00000000000..db4c016916b --- /dev/null +++ b/assets/devdoc_Development.md.Cz70vUo4.js @@ -0,0 +1,3 @@ +import{_ as s,c as t,a3 as i,o as a}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Development Guide","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/Development.md","filePath":"devdoc/Development.md","lastUpdated":1731340314000}'),n={name:"devdoc/Development.md"};function o(h,e,l,r,p,d){return a(),t("div",null,e[0]||(e[0]=[i(`

Development Guide

HPCC Source

The most upto date details of building the system are found on the HPCC Wiki.

Getting the sources

The HPCC Platform sources are hosted on GitHub. You can download a snapshot of any branch using the download button there, or you can set up a git clone of the repository. If you are planning to contribute changes to the system, see the CONTRIBUTORS document for information about how to set up a GitHub fork of the project through which pull-requests can be made.

Building the system from sources

Requirements

The HPCC platform requires a number of third party tools and libraries in order to build. The HPCC Wiki contains the details of the dependencies that are required for different distributions.

For building any documentation, the following are also required:

bash
sudo apt-get install docbook
+sudo apt-get install xsltproc
+sudo apt-get install fop

NOTE: Installing the above via alternative methods (i.e. from source) may place installations outside of searched paths.

Building the system

The HPCC system is built using the cross-platform build tool cmake, which is available for Windows, virtually all flavors of Linux, FreeBSD, and other platforms. You should install cmake version 2.8.3 or later before building the sources.

On some distros you will need to build cmake from sources if the version of cmake in the standard repositories for that distro is not modern enough. It is good practice in cmake to separate the build directory where objects and executables are made from the source directory, and the HPCC cmake scripts will enforce this.

To build the sources, create a directory where the built files should be located, and from that directory, run:

bash
cmake <source directory>

Depending on your operating system and the compilers installed on it, this will create a makefile, Visual Studio .sln file, or other build script for building the system. If cmake was configured to create a makefile, then you can build simply by typing:

bash
make

If a Visual Studio solution file was created, you can load it simply by typing the name: hpccsystems-platform.sln

This will load the solution in Visual Studio where you can build in the usual way.

Packaging

To make an installation package on a supported linux system, use the command:

bash
make package

This will first do a make to ensure everything is up to date, then will create the appropriate package for your operating system, Currently supported package formats are rpm (for RedHat/Centos) and .deb (for Debian and Ubuntu). If the operating system is not one of the above, or is not recognized, make package will create a tarball.

The package installation does not start the service on the machine, so if you want to give it a go or test it (see below), make sure to start the service manually and wait until all services are up (mainly wait for EclWatch to come up on port 8010).

Testing the system

After compiling, installing the package and starting the services, you can test the HPCC platform on a single-node setup.

Unit Tests

Some components have their own unit-tests. Once you have compiled (no need to start the services), you can already run them. Supposing you build a Debug version, from the build directory you can run:

bash
./Debug/bin/roxie -selftest

and:

bash
./Debug/bin/eclagent -selftest

You can also run the Dali regression self-tests:

bash
./Debug/bin/daregress localhost

Regression Tests

MORE Completely out of date - needs rewriting.

Compiler Tests

The ECLCC compiler tests rely on two distinct runs: a known good one and your test build. For normal development, you can safely assume that the OSS/master branch in github is good. For overnight testing, golden directories need to be maintained according to the test infrastructure. There are Bash (Linux) and Batch (Windows) scripts to run the regressions:

The basic idea behind this tests is to compare the output files (logs and XML files) between runs. The log files should change slightly (the comparison should be good enough to filter most irrelevant differences), but the XML files should be identical if nothing has changed. You should only see differences in the XML where you have changed in the code, or new tests were added as part of your development.

On Linux, there are two steps:

Step 1: Check-out OSS/master, compile and run the regressions to populate the 'golden' directory:

bash
./regress.sh -t golden -e buildDir/Debug/bin/eclcc

This will run the regressions in parallel, using as many CPUs as you have, and using your just-compiled ECLCC, assuming you compiled for Debug version.

Step 2: Make your changes (or check-out your branch), compile and run again, this time output to a new directory and compare to the 'golden' repo.:

bash
./regress.sh -t my_branch -c golden -e buildDir/Debug/bin/eclcc

This will run the regressions in the same way, output to 'my_branch' dir and compare it to the golden version, highlighting the differences.

NOTE: If you changed the headers that the compiled binaries will use, you must re-install the package (or provide -i option to the script to the new headers).

Step 3: Step 2 only listed the differences, now you need to see what they are. For that, re-run the regressing script omitting the compiler, since the only thing we'll do is to compare verbosely.:

bash
./regress.sh -t my_branch -c golden

This will show you all differences, using the same ignore filters as before, between your two branches. Once you're happy with the differences, commit and issue a pull-request.

TODO: Describe compiler tests on Windows.

Debugging the system

On linux systems, the makefile generated by cmake will build a specific version (debug or release) of the system depending on the options selected when cmake is first run in that directory. The default is to build a release system. In order to build a debug system instead, use command:

bash
cmake -DCMAKE_BUILD_TYPE=Debug <source directory>

You can then run make or make package in the usual way to build the system.

On a Windows system, cmake always generates s solution file with both debug and release target platforms in it, so you can select which one to build within Visual Studio.

`,56)]))}const g=s(n,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/devdoc_Development.md.Cz70vUo4.lean.js b/assets/devdoc_Development.md.Cz70vUo4.lean.js new file mode 100644 index 00000000000..db4c016916b --- /dev/null +++ b/assets/devdoc_Development.md.Cz70vUo4.lean.js @@ -0,0 +1,3 @@ +import{_ as s,c as t,a3 as i,o as a}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Development Guide","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/Development.md","filePath":"devdoc/Development.md","lastUpdated":1731340314000}'),n={name:"devdoc/Development.md"};function o(h,e,l,r,p,d){return a(),t("div",null,e[0]||(e[0]=[i(`

Development Guide

HPCC Source

The most upto date details of building the system are found on the HPCC Wiki.

Getting the sources

The HPCC Platform sources are hosted on GitHub. You can download a snapshot of any branch using the download button there, or you can set up a git clone of the repository. If you are planning to contribute changes to the system, see the CONTRIBUTORS document for information about how to set up a GitHub fork of the project through which pull-requests can be made.

Building the system from sources

Requirements

The HPCC platform requires a number of third party tools and libraries in order to build. The HPCC Wiki contains the details of the dependencies that are required for different distributions.

For building any documentation, the following are also required:

bash
sudo apt-get install docbook
+sudo apt-get install xsltproc
+sudo apt-get install fop

NOTE: Installing the above via alternative methods (i.e. from source) may place installations outside of searched paths.

Building the system

The HPCC system is built using the cross-platform build tool cmake, which is available for Windows, virtually all flavors of Linux, FreeBSD, and other platforms. You should install cmake version 2.8.3 or later before building the sources.

On some distros you will need to build cmake from sources if the version of cmake in the standard repositories for that distro is not modern enough. It is good practice in cmake to separate the build directory where objects and executables are made from the source directory, and the HPCC cmake scripts will enforce this.

To build the sources, create a directory where the built files should be located, and from that directory, run:

bash
cmake <source directory>

Depending on your operating system and the compilers installed on it, this will create a makefile, Visual Studio .sln file, or other build script for building the system. If cmake was configured to create a makefile, then you can build simply by typing:

bash
make

If a Visual Studio solution file was created, you can load it simply by typing the name: hpccsystems-platform.sln

This will load the solution in Visual Studio where you can build in the usual way.

Packaging

To make an installation package on a supported linux system, use the command:

bash
make package

This will first do a make to ensure everything is up to date, then will create the appropriate package for your operating system, Currently supported package formats are rpm (for RedHat/Centos) and .deb (for Debian and Ubuntu). If the operating system is not one of the above, or is not recognized, make package will create a tarball.

The package installation does not start the service on the machine, so if you want to give it a go or test it (see below), make sure to start the service manually and wait until all services are up (mainly wait for EclWatch to come up on port 8010).

Testing the system

After compiling, installing the package and starting the services, you can test the HPCC platform on a single-node setup.

Unit Tests

Some components have their own unit-tests. Once you have compiled (no need to start the services), you can already run them. Supposing you build a Debug version, from the build directory you can run:

bash
./Debug/bin/roxie -selftest

and:

bash
./Debug/bin/eclagent -selftest

You can also run the Dali regression self-tests:

bash
./Debug/bin/daregress localhost

Regression Tests

MORE Completely out of date - needs rewriting.

Compiler Tests

The ECLCC compiler tests rely on two distinct runs: a known good one and your test build. For normal development, you can safely assume that the OSS/master branch in github is good. For overnight testing, golden directories need to be maintained according to the test infrastructure. There are Bash (Linux) and Batch (Windows) scripts to run the regressions:

The basic idea behind this tests is to compare the output files (logs and XML files) between runs. The log files should change slightly (the comparison should be good enough to filter most irrelevant differences), but the XML files should be identical if nothing has changed. You should only see differences in the XML where you have changed in the code, or new tests were added as part of your development.

On Linux, there are two steps:

Step 1: Check-out OSS/master, compile and run the regressions to populate the 'golden' directory:

bash
./regress.sh -t golden -e buildDir/Debug/bin/eclcc

This will run the regressions in parallel, using as many CPUs as you have, and using your just-compiled ECLCC, assuming you compiled for Debug version.

Step 2: Make your changes (or check-out your branch), compile and run again, this time output to a new directory and compare to the 'golden' repo.:

bash
./regress.sh -t my_branch -c golden -e buildDir/Debug/bin/eclcc

This will run the regressions in the same way, output to 'my_branch' dir and compare it to the golden version, highlighting the differences.

NOTE: If you changed the headers that the compiled binaries will use, you must re-install the package (or provide -i option to the script to the new headers).

Step 3: Step 2 only listed the differences, now you need to see what they are. For that, re-run the regressing script omitting the compiler, since the only thing we'll do is to compare verbosely.:

bash
./regress.sh -t my_branch -c golden

This will show you all differences, using the same ignore filters as before, between your two branches. Once you're happy with the differences, commit and issue a pull-request.

TODO: Describe compiler tests on Windows.

Debugging the system

On linux systems, the makefile generated by cmake will build a specific version (debug or release) of the system depending on the options selected when cmake is first run in that directory. The default is to build a release system. In order to build a debug system instead, use command:

bash
cmake -DCMAKE_BUILD_TYPE=Debug <source directory>

You can then run make or make package in the usual way to build the system.

On a Windows system, cmake always generates s solution file with both debug and release target platforms in it, so you can select which one to build within Visual Studio.

`,56)]))}const g=s(n,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/devdoc_GitAuthenticate.md.DWDVKYp3.js b/assets/devdoc_GitAuthenticate.md.DWDVKYp3.js new file mode 100644 index 00000000000..cfaef864e5f --- /dev/null +++ b/assets/devdoc_GitAuthenticate.md.DWDVKYp3.js @@ -0,0 +1,12 @@ +import{_ as a,c as t,a3 as s,o as n}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"HPCC git support","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/GitAuthenticate.md","filePath":"devdoc/GitAuthenticate.md","lastUpdated":1731340314000}'),i={name:"devdoc/GitAuthenticate.md"};function o(p,e,r,c,l,h){return n(),t("div",null,e[0]||(e[0]=[s(`

HPCC git support

Version 8.4 of the HPCC platform allows package files to define dependencies between git repositories and also allows you to compile directly from a git repository.

E.g.

ecl run hthor --main demo.main@ghalliday/gch-ecldemo-d#version1 --server=...

There are no futher requirements if the repositories are public, but private repositories have the additional complication of supplying authentication information. Git provides various methods for providing the credentials...

Credentials for local development

The following are the recommended approaches configuring the credentials on a local development system interacting with github:

  1. ssh key.

In this scenario, the ssh key associated with the local developer machine is registered with the github account. For more details see https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh

This is used when the github reference is of the form ssh://github.com. The sshkey can be protected with a passcode, and there are various options to avoid having to enter the passcode each time.

It is preferrable to use the https:// protocol instead of ssh:// for links in package-lock.json files. If ssh:// is used it requires any machine that processes the dependency to have access to a registered ssh key.

  1. github authentication

Download the GitHub command line tool (https://github.com/cli/cli). You can then use it to authenticate all git access with

gh auth login

Probably the simplest option if you are using github. More details are found at https://cli.github.com/manual/gh_auth_login

  1. Use a personal access token

These are similar to a password, but with additional restrictions on their lifetime and the resources that can be accessed.

Details on how to to create them are found : https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token

These can then be used with the various git credential caching options. E.g. see https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage

Configuring eclccserver

All of the options above are likely to involve some user interaction - passphrases for ssh keys, web interaction with github authentication, and initial entry for cached access tokens. This is problematic for eclccserver - which cannot support user interaction, and it is preferrable not to pass credentials around.

The solution is to use a personal access token securely stored as a secret. (This would generally be associated with a special service account.) This avoids the need to pass credentials and allows the keys to be rotated.

The following describes the support in the different versions:

Kubernetes

In Kubernetes you need to take the following steps:

a) add the gitUsername property to the eclccserver component in the value.yaml file:

eclccserver:
+- name: myeclccserver
+  gitUsername: ghalliday

b) add a secret to the values.yaml file, with a key that matches the username:

secrets:
+  git:
+    ghalliday: my-git-secret

note: this cannot currently use a vault - probably need to rethink that. (Possibly extract from secret and supply as an optional environment variable to be picked up by the bash script.)

c) add a secret to Kubernetes containing the personal access token:

apiVersion: v1
+kind: Secret
+metadata:
+  name: my-git-secret
+type: Opaque
+stringData:
+  password: ghp_eZLHeuoHxxxxxxxxxxxxxxxxxxxxol3986sS=
kubectl apply -f ~/dev/hpcc/helm/secrets/my-git-secret

When a query is submitted to eclccserver, any git repositories are accessed using the user name and password.

Bare-metal

Bare-metal require some similar configuration steps:

a) Define the environment variable HPCC_GIT_USERNAME

export HPCC_GIT_USERNAME=ghalliday

b) Store the access token in /opt/HPCCSystems/secrets/git/$HPCC_GIT_USERNAME/password

E.g.

$cat /opt/HPCCSystems/secrets/git/ghalliday/password
+ghp_eZLHeuoHxxxxxxxxxxxxxxxxxxxxol3986sS=
`,41)]))}const g=a(i,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/devdoc_GitAuthenticate.md.DWDVKYp3.lean.js b/assets/devdoc_GitAuthenticate.md.DWDVKYp3.lean.js new file mode 100644 index 00000000000..cfaef864e5f --- /dev/null +++ b/assets/devdoc_GitAuthenticate.md.DWDVKYp3.lean.js @@ -0,0 +1,12 @@ +import{_ as a,c as t,a3 as s,o as n}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"HPCC git support","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/GitAuthenticate.md","filePath":"devdoc/GitAuthenticate.md","lastUpdated":1731340314000}'),i={name:"devdoc/GitAuthenticate.md"};function o(p,e,r,c,l,h){return n(),t("div",null,e[0]||(e[0]=[s(`

HPCC git support

Version 8.4 of the HPCC platform allows package files to define dependencies between git repositories and also allows you to compile directly from a git repository.

E.g.

ecl run hthor --main demo.main@ghalliday/gch-ecldemo-d#version1 --server=...

There are no futher requirements if the repositories are public, but private repositories have the additional complication of supplying authentication information. Git provides various methods for providing the credentials...

Credentials for local development

The following are the recommended approaches configuring the credentials on a local development system interacting with github:

  1. ssh key.

In this scenario, the ssh key associated with the local developer machine is registered with the github account. For more details see https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh

This is used when the github reference is of the form ssh://github.com. The sshkey can be protected with a passcode, and there are various options to avoid having to enter the passcode each time.

It is preferrable to use the https:// protocol instead of ssh:// for links in package-lock.json files. If ssh:// is used it requires any machine that processes the dependency to have access to a registered ssh key.

  1. github authentication

Download the GitHub command line tool (https://github.com/cli/cli). You can then use it to authenticate all git access with

gh auth login

Probably the simplest option if you are using github. More details are found at https://cli.github.com/manual/gh_auth_login

  1. Use a personal access token

These are similar to a password, but with additional restrictions on their lifetime and the resources that can be accessed.

Details on how to to create them are found : https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token

These can then be used with the various git credential caching options. E.g. see https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage

Configuring eclccserver

All of the options above are likely to involve some user interaction - passphrases for ssh keys, web interaction with github authentication, and initial entry for cached access tokens. This is problematic for eclccserver - which cannot support user interaction, and it is preferrable not to pass credentials around.

The solution is to use a personal access token securely stored as a secret. (This would generally be associated with a special service account.) This avoids the need to pass credentials and allows the keys to be rotated.

The following describes the support in the different versions:

Kubernetes

In Kubernetes you need to take the following steps:

a) add the gitUsername property to the eclccserver component in the value.yaml file:

eclccserver:
+- name: myeclccserver
+  gitUsername: ghalliday

b) add a secret to the values.yaml file, with a key that matches the username:

secrets:
+  git:
+    ghalliday: my-git-secret

note: this cannot currently use a vault - probably need to rethink that. (Possibly extract from secret and supply as an optional environment variable to be picked up by the bash script.)

c) add a secret to Kubernetes containing the personal access token:

apiVersion: v1
+kind: Secret
+metadata:
+  name: my-git-secret
+type: Opaque
+stringData:
+  password: ghp_eZLHeuoHxxxxxxxxxxxxxxxxxxxxol3986sS=
kubectl apply -f ~/dev/hpcc/helm/secrets/my-git-secret

When a query is submitted to eclccserver, any git repositories are accessed using the user name and password.

Bare-metal

Bare-metal require some similar configuration steps:

a) Define the environment variable HPCC_GIT_USERNAME

export HPCC_GIT_USERNAME=ghalliday

b) Store the access token in /opt/HPCCSystems/secrets/git/$HPCC_GIT_USERNAME/password

E.g.

$cat /opt/HPCCSystems/secrets/git/ghalliday/password
+ghp_eZLHeuoHxxxxxxxxxxxxxxxxxxxxol3986sS=
`,41)]))}const g=a(i,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/devdoc_LDAPSecurityManager.md.Cty5AMLN.js b/assets/devdoc_LDAPSecurityManager.md.Cty5AMLN.js new file mode 100644 index 00000000000..77a97406441 --- /dev/null +++ b/assets/devdoc_LDAPSecurityManager.md.Cty5AMLN.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as n,o}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"LDAP Security Manager Init","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/LDAPSecurityManager.md","filePath":"devdoc/LDAPSecurityManager.md","lastUpdated":1731340314000}'),i={name:"devdoc/LDAPSecurityManager.md"};function r(s,e,c,d,h,l){return o(),a("div",null,e[0]||(e[0]=[n('

LDAP Security Manager Init

This document covers the main steps taken by the LDAP Security Manager during initialization. It is important to note that the LDAP Security Manager uses the LDAP protocol to access an Active Directory, AD. The AD is the store for users, groups, permissions, resources, and more. The term LDAP is generally overused to refer to both.

LDAP Instances

Each service and/or component using the LDAP security manager gets its own instance of the security manager. This includes a unique connection pool (see below). All operations described apply to each LDAP instance.

Initialization Steps

The following sections cover the main steps taken during initialization

Load Configuration

The following items are loaded from the configuration:

AD Hosts

The LDAP Security Manager supports using multiple ADs. The FQDN or IP address of each AD host is read from configuration data and stored internally. The source is a comma separated list stored in the ldapAddress config value. Each entry is added to a pool of ADs.

Note that all ADs are expected to use the same credentials and have the same configuration

AD Credentials

AD credentials consist of a username and password. The LDAP security manager users these to perform all operations on behalf of users and components in the cluster. There are three potential sources for credentials.

  1. A Kubernetes secret. If the ldapAdminSecretKey config value is set, but ldapAdminVaultId is not (see 2) then the AD credentials are retrieved as Kubernetes secrets.
  2. If both ldapAdminSecretKey and ldapAdminVaultId config values are present, the AD credentials are retrieved from the vault.
  3. Hardcoded values from the systemCommonName and systemPassword config values stored in the environment.xml file.

As stated above, when multiple ADs are in use, the configuration of each must be the same. This includes credentials.

Retrieve Server Information from the AD

During initialization, the security manager begins incrementing through the set of defined ADs until it is able to connect and retrieve information from an AD. Once retrieved, the information is used for all ADs (see statement above about all ADs being the same). The accessed AD is marked as the current AD and no other ADs are accessed during initialization.

The retrieved information is used to verify the AD type so the security manager adjusts for variations between types. Additionally, defined DNs may be adjusted to match AD type requirements.

Connections

The manager handles connections to an AD in order to perform required operations. It is possible that values such as permissions and resources may be cached to improve performance.

Connection Pool

The LDAP security manager maintains a pool of LDAP connections. The pool is limited in size to maxConnections from the configuration. The connection pool starts empty. As connections are created, each is added to the pool until the max allowed is reached.

The following process is used when an LDAP connection is needed.

First, the connection pool is searched for a free connection. If found and valid, the connection is returned. A connection is considered free if no one is using it and valid if the AD can be accessed. If no valid free connections are found, a new uninitialized connection is created.

For a new connection, an attempt is made to connect to each AD starting with the current. See Handling AD Hosts below for how ADs are cycled when a connection fails. For each AD, as it cycles through, connection attempts are retried with a short delay between each. If unable to connect, the AD host is marked rejected and the next is attempted. Once a new connection has been established, if the max number of connections has not been reached yet, the connection is added to the pool.

It is important to note that if the pool has reached its max size, new connections will continue to be made, but are not saved in the pool. This allows the pool to maintain a steady working state, but allow for higher demand. Connections not saved to the pool are deleted once no longer in use.

Handling AD Hosts

The manager keeps a list of AD hosts and the index of the current host. The current host is used for all AD operations until there is a failure. At that time the manager marks the host as "rejected" and moves to the next host using a round-robin scheme.

',28)]))}const p=t(i,[["render",r]]);export{m as __pageData,p as default}; diff --git a/assets/devdoc_LDAPSecurityManager.md.Cty5AMLN.lean.js b/assets/devdoc_LDAPSecurityManager.md.Cty5AMLN.lean.js new file mode 100644 index 00000000000..77a97406441 --- /dev/null +++ b/assets/devdoc_LDAPSecurityManager.md.Cty5AMLN.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as n,o}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"LDAP Security Manager Init","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/LDAPSecurityManager.md","filePath":"devdoc/LDAPSecurityManager.md","lastUpdated":1731340314000}'),i={name:"devdoc/LDAPSecurityManager.md"};function r(s,e,c,d,h,l){return o(),a("div",null,e[0]||(e[0]=[n('

LDAP Security Manager Init

This document covers the main steps taken by the LDAP Security Manager during initialization. It is important to note that the LDAP Security Manager uses the LDAP protocol to access an Active Directory, AD. The AD is the store for users, groups, permissions, resources, and more. The term LDAP is generally overused to refer to both.

LDAP Instances

Each service and/or component using the LDAP security manager gets its own instance of the security manager. This includes a unique connection pool (see below). All operations described apply to each LDAP instance.

Initialization Steps

The following sections cover the main steps taken during initialization

Load Configuration

The following items are loaded from the configuration:

AD Hosts

The LDAP Security Manager supports using multiple ADs. The FQDN or IP address of each AD host is read from configuration data and stored internally. The source is a comma separated list stored in the ldapAddress config value. Each entry is added to a pool of ADs.

Note that all ADs are expected to use the same credentials and have the same configuration

AD Credentials

AD credentials consist of a username and password. The LDAP security manager users these to perform all operations on behalf of users and components in the cluster. There are three potential sources for credentials.

  1. A Kubernetes secret. If the ldapAdminSecretKey config value is set, but ldapAdminVaultId is not (see 2) then the AD credentials are retrieved as Kubernetes secrets.
  2. If both ldapAdminSecretKey and ldapAdminVaultId config values are present, the AD credentials are retrieved from the vault.
  3. Hardcoded values from the systemCommonName and systemPassword config values stored in the environment.xml file.

As stated above, when multiple ADs are in use, the configuration of each must be the same. This includes credentials.

Retrieve Server Information from the AD

During initialization, the security manager begins incrementing through the set of defined ADs until it is able to connect and retrieve information from an AD. Once retrieved, the information is used for all ADs (see statement above about all ADs being the same). The accessed AD is marked as the current AD and no other ADs are accessed during initialization.

The retrieved information is used to verify the AD type so the security manager adjusts for variations between types. Additionally, defined DNs may be adjusted to match AD type requirements.

Connections

The manager handles connections to an AD in order to perform required operations. It is possible that values such as permissions and resources may be cached to improve performance.

Connection Pool

The LDAP security manager maintains a pool of LDAP connections. The pool is limited in size to maxConnections from the configuration. The connection pool starts empty. As connections are created, each is added to the pool until the max allowed is reached.

The following process is used when an LDAP connection is needed.

First, the connection pool is searched for a free connection. If found and valid, the connection is returned. A connection is considered free if no one is using it and valid if the AD can be accessed. If no valid free connections are found, a new uninitialized connection is created.

For a new connection, an attempt is made to connect to each AD starting with the current. See Handling AD Hosts below for how ADs are cycled when a connection fails. For each AD, as it cycles through, connection attempts are retried with a short delay between each. If unable to connect, the AD host is marked rejected and the next is attempted. Once a new connection has been established, if the max number of connections has not been reached yet, the connection is added to the pool.

It is important to note that if the pool has reached its max size, new connections will continue to be made, but are not saved in the pool. This allows the pool to maintain a steady working state, but allow for higher demand. Connections not saved to the pool are deleted once no longer in use.

Handling AD Hosts

The manager keeps a list of AD hosts and the index of the current host. The current host is used for all AD operations until there is a failure. At that time the manager marks the host as "rejected" and moves to the next host using a round-robin scheme.

',28)]))}const p=t(i,[["render",r]]);export{m as __pageData,p as default}; diff --git a/assets/devdoc_MemoryManager.md.lMxAbcSH.js b/assets/devdoc_MemoryManager.md.lMxAbcSH.js new file mode 100644 index 00000000000..5a70e95fd80 --- /dev/null +++ b/assets/devdoc_MemoryManager.md.lMxAbcSH.js @@ -0,0 +1 @@ +import{_ as a,c as t,a3 as o,o as i}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"The Roxie Memory Manager","description":"","frontmatter":{"title":"The Roxie Memory Manager"},"headers":[],"relativePath":"devdoc/MemoryManager.md","filePath":"devdoc/MemoryManager.md","lastUpdated":1731340314000}'),r={name:"devdoc/MemoryManager.md"};function s(n,e,l,h,c,d){return i(),t("div",null,e[0]||(e[0]=[o('

Introduction

This memory manager started life as the memory manager which was only used for the Roxie engine. It had several original design goals:

  • Support link counted rows. (When the last reference is released the row is freed.)
  • Be as fast as possible on allocate and deallocate of small rows.
  • Allow rows serialized from slaves to be used directly without being cloned first.
  • Allow the memory used by a single query, or by all queries combined, to be limited, with graceful recovery.
  • Isolate roxie queries from one another, so that one query can't bring down all the rest by allocating too much memory.
  • Guarantee all the memory used by a query is freed when the query finishes, reducing the possibility of memory leaks.
  • Predictable behaviour with no pathogenic cases.

(Note that efficient usage of memory does not appear on that list - the expectation when the memory manager was first designed was that Roxie queries would use minimal amounts of memory and speed was more important. Some subsequent changes e.g., Packed heaps, and configurable bucket sizes help mitigate that.)

Main Structure

The basic design is to reserve (but not commit) a single large block of memory in the virtual address space. This memory is subdivided into "pages". (These are not the same as the os virtual memory pages. The memory manager pages are currently defined as 1Mb in size.)

The page bitmap

The system uses a bitmap to indicate whether each page from the global memory has been allocated. All active IRowManager instances allocate pages from the same global memory space. To help reduce fragmentation allocations for single pages are fulfilled from one end of the address space, while allocations for multiple pages are fulfilled from the other.

IRowManager

This provides the primary interface for allocating memory. The size of a requested allocation is rounded up to the next "bucket" size, and the allocation is then satisfied by the heap associated with that bucket size. Different engines can specify different bucket sizes - an optional list is provided to setTotalMemoryLimit. Roxie tends to use fewer buckets to help reduce the number of active heaps. Thor uses larger numbers since it is more important to minimize the memory wasted.

Roxie uses a separate instance of IRowManager for each query. This provides the mechanism for limiting how much memory a query uses. Thor uses a single instance of an IRowManager for each slave/master.

Heaps

Memory is allocated from a set of "heaps - where each heap allocates blocks of memory of a single size. The heap exclusively owns a set of heaplet (each 1 page in size), which are held in a doubly linked list, and sub allocates memory from those heaplets.

Information about each heaplet is stored in the base of the page (using a class with virtual functions) and the address of an allocated row is masked to determine which heap object it belongs to, and how it should be linked/released etc. Any pointer not in the allocated virtual address (e.g., constant data) can be linked/released with no effect.

Each heaplet contains a high water mark of the address within the page that has already been allocated (freeBase), and a lockless singly-linked list of rows which have been released (r_block). Releasing a row is non-blocking and does not involve any spin locks or critical sections. However, this means that empty pages need to be returned to the global memory pool at another time. (This is done in releaseEmptyPages()).

When the last row in a page is released a flag (possibleEmptyPages) is set in its associated heap. * This is checked before trying to free pages from a particular heap, avoiding waiting on a lock and traversing a candidate list.

Any page which might contain some spare memory is added to a lockless spare memory linked list. * Items are popped from this list when a heap fails to allocate memory from the current heaplet. Each item is checked in turn if it has space before allocating a new heaplet. * The list is also walked when checking to see which pages can be returned to the global memory. The doubly linked heaplet list allows efficient freeing.

Each allocation has a link count and an allocator id associated with it. The allocator id represents the type of the row, and is used to determine what destructor needs to be called when the row is destroyed. (The count for a row also contains a flag in the top bit to indicate if it is fully constructed, and therefore valid for the destructor to be called.)

Huge Heap

A specialized heap is used to manage all allocations that require more than one page of memory. These allocations are not held on a free list when they are released, but each is returned directly to the global memory pool. Allocations in the huge heap can be expanded and shrunk using the resizeRow() functions - see below.

Specialised Heaps:

Packed

By default a fixed size heaps rounds the requested allocation size up to the next bucket size. A packed heap changes this behaviour and it is rounded up to the next 4 byte boundary instead. This reduces the amount of memory wasted for each row, but potentially increases the number of distinct heaps.

Unique

By default all fixed size heaps of the same size are shared. This reduces the memory consumption, but if the heap is used by multiple threads it can cause significant contention. If a unique heap is specified then it will not be shared with any other requests. Unique heaps store information about the type of each row in the heaplet header, rather than per row - which reduces the allocation overhead for each row. (Note to self: Is there ever any advantage having a heap that is unique but not packed??)

Blocked

Blocked is an option on createFixedRowHeap() to allocate multiple rows from the heaplet, and then return the additional rows on subsequent calls. It is likely to reduce the average number of atomic operations required for each row being allocated, but the heap that is returned can only be used from a single thread because it is not thread safe.

Scanning

By default the heaplets use a lock free singly linked list to keep track of rows that have been freed. This requires an atomic operation for each allocation and for each free. The scanning allocator uses an alternative approach. When a row is freed the row header is marked, and a row is allocated by scanning through the heaplet for rows that have been marked as free. Scanning uses atomic store and get, rather than more expensive synchronized atomic operations, so is generally faster than the linked list - provided a free row is found fairly quickly.

The scanning heaps have better best-case performance, but worse worse-case performance (if large numbers of rows need to be scanned before a free row is found). The best-case tends to be true if only one thread/activity is accessing a particular heap, and the worse-case if multiple activities are accessing a heap, particularly if the rows are being buffered. It is the default for thor which tends to have few active allocators, but not for roxie, which tends to have large numbers of allocators.

Delay Release

This is another varation on the scanning allocator, which further reduces the number of atomic operations. Usually when a row is allocated the link count on the heaplet is increased, and when it is freed the link count is decremented. This option delays decrementing the link count when the row is released, by marking the row with a different free flag. If it is subsequently reallocated there is no need to increment the link count. The downside is that it is more expensive to check whether a heaplet is completely empty (since you can no longer rely on the heaplet linkcount alone).

Dynamic Spilling

Thor has additional requirements to roxie. In roxie, if a query exceeds its memory requirements then it is terminated. Thor needs to be able to spill rows and other memory to disk and continue. This is achieved by allowing any process that stores buffered rows to register a callback with the memory manager. When more memory is required these callbacks are called to free up memory, and allow the job to continue.

Each callback can specify a priority - lower priority callbacks are called first since they are assumed to have a lower cost associated with spilling. When more memory is required the callbacks are called in priority order until one of them succeeds. The can also be passed a flag to indicate it is critical to force them to free up as much memory as possible.

Complications

There are several different complications involved with the memory spilling:

  • There will be many different threads allocating rows.
  • Callbacks could be triggered at any time.
  • There is a large scope for deadlock between the callbacks and allocations.
  • It may be better to not resize a large array if rows had to be evicted to resize it.
  • Filtered record streams can cause significant wasted space in the memory blocks.
  • Resizing a multi-page allocation is non trivial.

Callback Rules

Some rules to follow when implementing callbacks:

  • A callback cannot allocate any memory from the memory manager. If it does it is likely to deadlock.

  • You cannot allocate memory while holding a lock if that lock is also required by a callback.

    Again this will cause deadlock. If it proves impossible you can use a try-lock primitive in the callback, but it means you won't be able to spill those rows.

  • If the heaps are fragmented it may be more efficient to repack the heaps than spill to disk.

  • If you're resizing a potentially big block of memory use the resize function with the callback.

Resizing Large memory blocks

Some of the memory allocations cover more than one "page" - e.g., arrays used to store blocks of rows. (These are called huge pages internally, not to be confused with operating system support for huge pages...) When one of these memory blocks needs to be expanded you need to be careful:

  • Allocating a new page, copying, updating the pointer (within a cs) and then freeing is safe. Unfortunately it may involve copying a large chunk of memory. It may also fail if there isn't memory for the new and old block, even if the existing block could have been expanded into an adjacent block.
  • You can't lock, call a resize routine and update the pointer because the resize routine may need to allocate a new memory block- that may trigger a callback, which could in turn deadlock trying to gain the lock. (The callback may be from another thread...)
  • Therefore the memory manager contains a call which allows you to resize a block, but with a callback which is used to atomically update the pointer so it always remains thread safe.

Compacting heaps

Occasionally you have processes which read a large number of rows and then filter them so only a few are still held in memory. Rows tend to be allocated in sequence through the heap pages, which can mean those few remaining rows are scattered over many pages. If they could all be moved to a single page it would free up a significant amount of memory.

The memory manager contains a function to pack a set of rows into a smaller number of pages: IRowManager->compactRows().

This works by iterating through each of the rows in a list. If the row belongs to a heap that could be compacted, and isn't part of a full heaplet, then the row is moved. Since subsequent rows tend to be allocated from the same heaplet this has the effect of compacting the rows.

Shared Memory

Much of the time Thor doesn't uses full memory available to it. If you are running multiple Thor processes on the same machine you may want to configure the system so that each Thor has a private block of memory, but there is also a shared block of memory which can be used by whichever process needs it.

The ILargeMemCallback provides a mechanism to dynamically allocate more memory to a process as it requires it. This could potentially be done in stages rather than all or nothing.

(Currently unused as far as I know... the main problem is that borrowing memory needs to be coordinated.)

Huge pages

When OS processes use a large amount of memory, mapping virtual addresses to physical addresses can begin to take a significant proportion of the execution time. This is especially true once the TLB is not large enough to store all the mappings. Huge pages can significantly help with this problem by reducing the number of TLB entries needed to cover the virtual address space. The memory manager supports huge pages in two different ways:

Huge pages can be preallocated (e.g., with hugeadm) for exclusive use as huge pages. If huge pages are enabled for a particular engine, and sufficient huge pages are available to supply the memory for the memory manager, then they will be used.

Linux kernels from 2.6.38 onward have support for transparent huge pages. These do not need to be preallocated, instead the operating system tries to use them behind the scenes. HPCC version 5.2 and following takes advantage of this feature to significantly speed memory access up when large amounts of memory are used by each process.

Preallocated huge pages tend to be more efficient, but they have the disadvantage that the operating system currently does not reuse unused huge pages for other purposes e.g., disk cache.

There is also a memory manager option to not return the memory to the operating system when it is no longer required. This has the advantage of not clearing the memory whenever it is required again, but the same disadvantage as preallocated huge pages that the unused memory cannot be used for disk cache. We recommend this option is selected when preallocated huge pages are in use - until the kernel allows them to be reused.

Global memory and channels

Changes in 6.x allow Thor to run multiple channels within the same process. This allows data that is constant for all channels to be shared between all slave channels - a prime example is the rhs of a lookup join. For the queries to run efficiently the memory manager needs to ensure that each slave channel has the same amount of memory - especially when memory is being used that is shared between them.

createGlobalRowManager() allows a single global row manager to be created which also provides slave row managers for the different channels via the querySlaveRowManager(unsigned slave) method.

',61)]))}const u=a(r,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/devdoc_MemoryManager.md.lMxAbcSH.lean.js b/assets/devdoc_MemoryManager.md.lMxAbcSH.lean.js new file mode 100644 index 00000000000..5a70e95fd80 --- /dev/null +++ b/assets/devdoc_MemoryManager.md.lMxAbcSH.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,a3 as o,o as i}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"The Roxie Memory Manager","description":"","frontmatter":{"title":"The Roxie Memory Manager"},"headers":[],"relativePath":"devdoc/MemoryManager.md","filePath":"devdoc/MemoryManager.md","lastUpdated":1731340314000}'),r={name:"devdoc/MemoryManager.md"};function s(n,e,l,h,c,d){return i(),t("div",null,e[0]||(e[0]=[o('

Introduction

This memory manager started life as the memory manager which was only used for the Roxie engine. It had several original design goals:

  • Support link counted rows. (When the last reference is released the row is freed.)
  • Be as fast as possible on allocate and deallocate of small rows.
  • Allow rows serialized from slaves to be used directly without being cloned first.
  • Allow the memory used by a single query, or by all queries combined, to be limited, with graceful recovery.
  • Isolate roxie queries from one another, so that one query can't bring down all the rest by allocating too much memory.
  • Guarantee all the memory used by a query is freed when the query finishes, reducing the possibility of memory leaks.
  • Predictable behaviour with no pathogenic cases.

(Note that efficient usage of memory does not appear on that list - the expectation when the memory manager was first designed was that Roxie queries would use minimal amounts of memory and speed was more important. Some subsequent changes e.g., Packed heaps, and configurable bucket sizes help mitigate that.)

Main Structure

The basic design is to reserve (but not commit) a single large block of memory in the virtual address space. This memory is subdivided into "pages". (These are not the same as the os virtual memory pages. The memory manager pages are currently defined as 1Mb in size.)

The page bitmap

The system uses a bitmap to indicate whether each page from the global memory has been allocated. All active IRowManager instances allocate pages from the same global memory space. To help reduce fragmentation allocations for single pages are fulfilled from one end of the address space, while allocations for multiple pages are fulfilled from the other.

IRowManager

This provides the primary interface for allocating memory. The size of a requested allocation is rounded up to the next "bucket" size, and the allocation is then satisfied by the heap associated with that bucket size. Different engines can specify different bucket sizes - an optional list is provided to setTotalMemoryLimit. Roxie tends to use fewer buckets to help reduce the number of active heaps. Thor uses larger numbers since it is more important to minimize the memory wasted.

Roxie uses a separate instance of IRowManager for each query. This provides the mechanism for limiting how much memory a query uses. Thor uses a single instance of an IRowManager for each slave/master.

Heaps

Memory is allocated from a set of "heaps - where each heap allocates blocks of memory of a single size. The heap exclusively owns a set of heaplet (each 1 page in size), which are held in a doubly linked list, and sub allocates memory from those heaplets.

Information about each heaplet is stored in the base of the page (using a class with virtual functions) and the address of an allocated row is masked to determine which heap object it belongs to, and how it should be linked/released etc. Any pointer not in the allocated virtual address (e.g., constant data) can be linked/released with no effect.

Each heaplet contains a high water mark of the address within the page that has already been allocated (freeBase), and a lockless singly-linked list of rows which have been released (r_block). Releasing a row is non-blocking and does not involve any spin locks or critical sections. However, this means that empty pages need to be returned to the global memory pool at another time. (This is done in releaseEmptyPages()).

When the last row in a page is released a flag (possibleEmptyPages) is set in its associated heap. * This is checked before trying to free pages from a particular heap, avoiding waiting on a lock and traversing a candidate list.

Any page which might contain some spare memory is added to a lockless spare memory linked list. * Items are popped from this list when a heap fails to allocate memory from the current heaplet. Each item is checked in turn if it has space before allocating a new heaplet. * The list is also walked when checking to see which pages can be returned to the global memory. The doubly linked heaplet list allows efficient freeing.

Each allocation has a link count and an allocator id associated with it. The allocator id represents the type of the row, and is used to determine what destructor needs to be called when the row is destroyed. (The count for a row also contains a flag in the top bit to indicate if it is fully constructed, and therefore valid for the destructor to be called.)

Huge Heap

A specialized heap is used to manage all allocations that require more than one page of memory. These allocations are not held on a free list when they are released, but each is returned directly to the global memory pool. Allocations in the huge heap can be expanded and shrunk using the resizeRow() functions - see below.

Specialised Heaps:

Packed

By default a fixed size heaps rounds the requested allocation size up to the next bucket size. A packed heap changes this behaviour and it is rounded up to the next 4 byte boundary instead. This reduces the amount of memory wasted for each row, but potentially increases the number of distinct heaps.

Unique

By default all fixed size heaps of the same size are shared. This reduces the memory consumption, but if the heap is used by multiple threads it can cause significant contention. If a unique heap is specified then it will not be shared with any other requests. Unique heaps store information about the type of each row in the heaplet header, rather than per row - which reduces the allocation overhead for each row. (Note to self: Is there ever any advantage having a heap that is unique but not packed??)

Blocked

Blocked is an option on createFixedRowHeap() to allocate multiple rows from the heaplet, and then return the additional rows on subsequent calls. It is likely to reduce the average number of atomic operations required for each row being allocated, but the heap that is returned can only be used from a single thread because it is not thread safe.

Scanning

By default the heaplets use a lock free singly linked list to keep track of rows that have been freed. This requires an atomic operation for each allocation and for each free. The scanning allocator uses an alternative approach. When a row is freed the row header is marked, and a row is allocated by scanning through the heaplet for rows that have been marked as free. Scanning uses atomic store and get, rather than more expensive synchronized atomic operations, so is generally faster than the linked list - provided a free row is found fairly quickly.

The scanning heaps have better best-case performance, but worse worse-case performance (if large numbers of rows need to be scanned before a free row is found). The best-case tends to be true if only one thread/activity is accessing a particular heap, and the worse-case if multiple activities are accessing a heap, particularly if the rows are being buffered. It is the default for thor which tends to have few active allocators, but not for roxie, which tends to have large numbers of allocators.

Delay Release

This is another varation on the scanning allocator, which further reduces the number of atomic operations. Usually when a row is allocated the link count on the heaplet is increased, and when it is freed the link count is decremented. This option delays decrementing the link count when the row is released, by marking the row with a different free flag. If it is subsequently reallocated there is no need to increment the link count. The downside is that it is more expensive to check whether a heaplet is completely empty (since you can no longer rely on the heaplet linkcount alone).

Dynamic Spilling

Thor has additional requirements to roxie. In roxie, if a query exceeds its memory requirements then it is terminated. Thor needs to be able to spill rows and other memory to disk and continue. This is achieved by allowing any process that stores buffered rows to register a callback with the memory manager. When more memory is required these callbacks are called to free up memory, and allow the job to continue.

Each callback can specify a priority - lower priority callbacks are called first since they are assumed to have a lower cost associated with spilling. When more memory is required the callbacks are called in priority order until one of them succeeds. The can also be passed a flag to indicate it is critical to force them to free up as much memory as possible.

Complications

There are several different complications involved with the memory spilling:

  • There will be many different threads allocating rows.
  • Callbacks could be triggered at any time.
  • There is a large scope for deadlock between the callbacks and allocations.
  • It may be better to not resize a large array if rows had to be evicted to resize it.
  • Filtered record streams can cause significant wasted space in the memory blocks.
  • Resizing a multi-page allocation is non trivial.

Callback Rules

Some rules to follow when implementing callbacks:

  • A callback cannot allocate any memory from the memory manager. If it does it is likely to deadlock.

  • You cannot allocate memory while holding a lock if that lock is also required by a callback.

    Again this will cause deadlock. If it proves impossible you can use a try-lock primitive in the callback, but it means you won't be able to spill those rows.

  • If the heaps are fragmented it may be more efficient to repack the heaps than spill to disk.

  • If you're resizing a potentially big block of memory use the resize function with the callback.

Resizing Large memory blocks

Some of the memory allocations cover more than one "page" - e.g., arrays used to store blocks of rows. (These are called huge pages internally, not to be confused with operating system support for huge pages...) When one of these memory blocks needs to be expanded you need to be careful:

  • Allocating a new page, copying, updating the pointer (within a cs) and then freeing is safe. Unfortunately it may involve copying a large chunk of memory. It may also fail if there isn't memory for the new and old block, even if the existing block could have been expanded into an adjacent block.
  • You can't lock, call a resize routine and update the pointer because the resize routine may need to allocate a new memory block- that may trigger a callback, which could in turn deadlock trying to gain the lock. (The callback may be from another thread...)
  • Therefore the memory manager contains a call which allows you to resize a block, but with a callback which is used to atomically update the pointer so it always remains thread safe.

Compacting heaps

Occasionally you have processes which read a large number of rows and then filter them so only a few are still held in memory. Rows tend to be allocated in sequence through the heap pages, which can mean those few remaining rows are scattered over many pages. If they could all be moved to a single page it would free up a significant amount of memory.

The memory manager contains a function to pack a set of rows into a smaller number of pages: IRowManager->compactRows().

This works by iterating through each of the rows in a list. If the row belongs to a heap that could be compacted, and isn't part of a full heaplet, then the row is moved. Since subsequent rows tend to be allocated from the same heaplet this has the effect of compacting the rows.

Shared Memory

Much of the time Thor doesn't uses full memory available to it. If you are running multiple Thor processes on the same machine you may want to configure the system so that each Thor has a private block of memory, but there is also a shared block of memory which can be used by whichever process needs it.

The ILargeMemCallback provides a mechanism to dynamically allocate more memory to a process as it requires it. This could potentially be done in stages rather than all or nothing.

(Currently unused as far as I know... the main problem is that borrowing memory needs to be coordinated.)

Huge pages

When OS processes use a large amount of memory, mapping virtual addresses to physical addresses can begin to take a significant proportion of the execution time. This is especially true once the TLB is not large enough to store all the mappings. Huge pages can significantly help with this problem by reducing the number of TLB entries needed to cover the virtual address space. The memory manager supports huge pages in two different ways:

Huge pages can be preallocated (e.g., with hugeadm) for exclusive use as huge pages. If huge pages are enabled for a particular engine, and sufficient huge pages are available to supply the memory for the memory manager, then they will be used.

Linux kernels from 2.6.38 onward have support for transparent huge pages. These do not need to be preallocated, instead the operating system tries to use them behind the scenes. HPCC version 5.2 and following takes advantage of this feature to significantly speed memory access up when large amounts of memory are used by each process.

Preallocated huge pages tend to be more efficient, but they have the disadvantage that the operating system currently does not reuse unused huge pages for other purposes e.g., disk cache.

There is also a memory manager option to not return the memory to the operating system when it is no longer required. This has the advantage of not clearing the memory whenever it is required again, but the same disadvantage as preallocated huge pages that the unused memory cannot be used for disk cache. We recommend this option is selected when preallocated huge pages are in use - until the kernel allows them to be reused.

Global memory and channels

Changes in 6.x allow Thor to run multiple channels within the same process. This allows data that is constant for all channels to be shared between all slave channels - a prime example is the rhs of a lookup join. For the queries to run efficiently the memory manager needs to ensure that each slave channel has the same amount of memory - especially when memory is being used that is shared between them.

createGlobalRowManager() allows a single global row manager to be created which also provides slave row managers for the different channels via the querySlaveRowManager(unsigned slave) method.

',61)]))}const u=a(r,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/devdoc_Metrics.md.BVV7YlV-.js b/assets/devdoc_Metrics.md.BVV7YlV-.js new file mode 100644 index 00000000000..e83404d25fc --- /dev/null +++ b/assets/devdoc_Metrics.md.BVV7YlV-.js @@ -0,0 +1,17 @@ +import{_ as t,c as i,a3 as s,o as a}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Metrics Framework Design","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/Metrics.md","filePath":"devdoc/Metrics.md","lastUpdated":1731340314000}'),n={name:"devdoc/Metrics.md"};function r(o,e,l,h,c,p){return a(),i("div",null,e[0]||(e[0]=[s(`

Metrics Framework Design

Introduction

This document describes the design of a metrics framework that allows HPCC Systems components to implement a metric collection strategy. Metrics provide the following functionality:

  • Alerts and monitoring

    An important DevOps function is to monitor the cluster and providing alerts when problems are detected. Aggregated metric values from multiple sources provide the necessary data to build a complete picture of cluster health that drives monitoring and alerts.

  • Scaling

    As described above, aggregated metric data is also used to dynamically respond to changing cluster demands and load. Metrics provide the monitoring capability to react and take action

  • Fault diagnosis and resource monitoring

    Metrics provide historical data useful in diagnosing problems by profiling how demand and usage patterns may change prior to a fault. Predictive analysis can also be applied.

  • Analysis of jobs/workunits and profiling

    With proper instrumentation, a robust dynamic metric strategy can track workunit processing. Internal problems with queries should be diagnosed from deep drill down logging.

The document consists of several sections in order to provide requirements as well as the design of framework components.

Definitions

Some definitions are useful.

Metric

: A measurement defined by a component that represents an internal state that is useful in a system reliability engineering function. In the context of the framework, a metric is an object representing the above.

Metric Value

: The current value of a metric.

Metric Updating

: The component task of updating metric state.

Collection

: A framework process of selecting relevant metrics based on configuration and then retrieving their values.

Reporting

: A framework process of converting values obtained during a collection into a format suitable for ingestion by a collection system.

Trigger

: What causes the collection of metric values.

Collection System

: The store for metric values generated during the reporting framework process.

Use Scenarios

This section describes how components expect to use the framework. It is not a complete list of all requirements but rather a sample.

Roxie

Roxie desires to keep a count of many different internal values. Some examples are

  • Disk type operations such as seeks and reads

  • Execution totals

    Need to track items such as total numbers of items such as success and failures as well as breaking some counts into individual reasons. For example, failures may need be categorized such as as

    • Busy
    • Timeout
    • Bad input

    Or even by priority (high, low, sla, etc.)

  • Current operational levels such as the length of internal queues

  • The latency of operations such as queue results, agent responses, and gateway responses

Roxie also has the need to track internal memory usage beyond the pod/system level capabilities. Tracking the state of its large fixed memory pool is necessary.

The Roxie buddy system also must track how often and who is completing requests. The "I Beat You To It" set of metrics must be collected and exposed in order to detect pending node failure. While specific action on these counts is not known up front, it appears clear that these values are useful and should be collected.

There does not appear to be a need for creating and destroying metrics dynamically. The set of metrics is most likely to be created at startup and remain active through the life of the Roxie. If, however, stats collection seeps into the metrics framework, dynamic creation and destruction of stats metrics is a likely requirement.

ESP

There are some interesting decisions with respect to ESP and collection of metrics. Different applications within ESP present different use cases for collection. Ownership of a given task drives some of these use cases. Take workunit queues. If ownership of the task, with respect to metrics, is WsWorkunits, then use cases are centric to that component. However, if agents listening on the queue are to report metrics, then a different set of use cases emerge. It is clear that additional work is needed to generate clear ownership of metrics gathered by ESP and/or the tasks it performs.

ESP needs to report the activeTransactions value from the TxSummary class(es). This gives an indication of how busy the ESP is in terms of client requests.

Direct measurement of response time in requests may not be useful since the type of request causes different execution paths within ESP that are expected to take widely varying amounts of time. Creation of metrics for each method is not recommended. However, two possible solutions are to a) create a metric for request types, or b) use a histogram to measure response time ranges. Another option mentioned redefines the meaning of a bucket in a histogram. Instead of a numeric distribution, each bucket represents a unique subtask within an overall "metric" representing a measured operation. This should be explored whether for operational or developmental purposes.

For tracking specific queries and their health, the feeling is that logging can accomplish this better than metrics since the list of queries to monitor will vary between clusters. Additionally, operational metrics solving the cases mentioned above will give a view into the overall health of ESP which will affect the execution of queries. Depending on actions taken by these metrics, scaling may solve overload conditions to keep cluster responsiveness acceptable.

For Roxie a workunit operates as a service. Measuring service performance using a histogram to capture response times as a distribution may be appropriate. Extracting the 95th percentile of response time may be useful as well.

There are currently no use cases requiring consistency between values of different metrics.

At this time the only concrete metric identified is the number of requests received. As the framework design progresses and ESP is instrumented, the list will grow.

Dali Use Cases

From information gathered, Dali plans to keep counts and rates for many of the items it manages.

Framework Design

This section covers the design and architecture of the framework. It discusses the main areas of the design, the interactions between each area, and an overall process model of how the framework operates.

The framework consists of three major areas: metrics, sinks, and the glue logic. These areas work together with the platform and the component to provide a reusable metrics collection function.

Metrics represent the quantifiable component state measurements used to track and assess the status of the component. Metrics are typically scalar values that are easily aggregated by a collection system. Aggregated values provide the necessary input to take component and cluster actions such as scaling up and down. The component is responsible for creating metrics and instrumenting the code. The framework provides the support for collecting and reporting the values. Metrics provide the following:

  • Simple methods for the component to update the metric
  • Simple methods for the framework to retrieve metric value(s)
  • Handling of all synchronization between updating and retrieving metric values

In addition, the framework provides the support for retrieving values so that the component does not participate in metric reporting. The component simply creates the metrics it needs, then instruments the component to update the metric whenever its state changes. For example, the component may create a metric that counts the total number of requests received. Then, wherever the component receives a request, a corresponding update to the count is added. Nowhere in the component is any code added to retrieve the count as that is handled by the framework.

Sinks provide a pluggable interface to hide the specifics of collection systems so that the metrics framework is independent of those dependencies. Sinks:

  • Operate independently of other sinks in the system
  • Convert metric native values into collection system specific measurements and reports
  • Drive the collection and reporting processes

The third area of the framework is the glue logic, referred to as the MetricsManager. It manages the metrics system for the component. It provides the following:

  • Handles framework initialization
  • Loads sinks as required
  • Manages the list of metrics for the component
  • Handles collection and reporting with a set of convenience methods used by sinks

The framework is designed to be instantiated into a component as part of its process and address space. All objects instantiated as part of the framework are owned by the component and are not shareable with any other component whether local or remote. Any coordination or consistency requirements that may arise in the implementation of a sink shall be the sole responsibility of the sink.

Framework Implementation

The framework is implemented within jlib. The following sections describe each area of the framework.

Metrics

Components use metrics to measure their internal state. Metrics can represent everything from the number of requests received to the average length some value remains cached. Components are responsible for creating and updating metrics for each measured state. The framework shall provide a set of metrics designed to cover the majority of component measurement requirements. All metrics share a common interface to allow the framework to manage them in a common way.

To meet the requirement to manage metrics independent of the underlying metric state, all metrics implement a common interface. All metrics then add their specific methods to update and retrieve internal state. Generally the component uses the update method(s) to update state and the framework uses retrieval methods to get current state when reporting. The metric insures synchronized access.

For components that already have an implementation that tracks a metric, the framework provides a way to instantiate a custom metric. The custom metric allows the component to leverage the existing implementation and give the framework access to the metric value for collection and reporting. Note that custom metrics only support simple scalar metrics such as a counter or a gauge.

Sinks

The framework defines a sink interface to support the different requirements of collection systems. Examples of collection systems are Prometheus, Datadog, and Elasticsearch. Each has different requirements for how and when measurements are ingested. The following are examples of different collection system requirements:

  • Polled vs Periodic
  • Single measurement vs multiple reports
  • Report format (JSON, text, etc.)
  • Push vs Pull

Sinks are responsible for two main functions: initiating a collection and reporting measurements to the collection system. The Metrics Reporter provides the support to complete these functions.

The sink encapsulates all of the collection system requirements providing a pluggable architecture that isolates components from these differences. The framework supports multiple sinks concurrently, each operating independently.

Instrumented components are not aware of the sink or sinks in use. Sinks can be changed without requiring changes to a component. Therefore, components are independent of the collection system(s) in use.

Metrics Reporter

The metrics reporter class provides all of the common functions to bind together the component, the metrics it creates, and the sinks to which measurements are reported. It is responsible for the following:

  • Initialization of the framework
  • Managing the metrics created by the component
  • Handling collection and reporting as directed by configured sinks

Metrics Implementations

The sections that follow discuss metric implementations.

Counter Metric

A counter metric is a monotonically increasing value that "counts" the total occurrences of some event. Examples include the number of requests received, or the number of cache misses. Once created, the component instruments the code with updates to the count whenever appropriate.

Gauge Metric

A gauge metric is a continuously updated value representing the current state of an interesting value in the component. For example, the amount of memory used in an internal buffer, or the number of requests waiting on a queue. A gauge metric may increase or decrease in value as needed. Reading the value of a gauge is a stateless operation in that there are no dependencies on the previous reading. The value returned shall always be the current state.

Once created, the component shall update the gauge anytime the state of what is measured is updated. The metric shall provide methods to increase and decrease the value. The sink reads the value during collection and reporting.

Custom Metric

A custom metric is a class that allows a component to leverage existing metrics. The component creates an instance of a custom metric (a templated class) and passes a reference to the underlying metric value. When collection is performed, the custom metric simply reads the value of the metric using the reference provided during construction. The component maintains full responsibility for updating the metric value as the custom metric class provides no update methods. The component is also responsible for ensuring atomic access to the value if necessary.

Histogram Metric

Records counts of measurements according to defined bucket limits. When created, the caller defines as set of bucket limits. During event recording, the component records measurements. The metric separates each recorded measurement into its bucket by testing the measurement value against each bucket limit using a less than or equal test. Each bucket contains a count of measurements meeting that criteria. Additionally, the metric maintains a default bucket for measurements outside of the maximum bucket limit. This is sometimes known as the "inf" bucket.

Some storage systems, such as Prometheus, require each bucket to accumulate its measurements with the previous bucket(s). It is the responsibility of the sink to accumulate values as needed.

Scaled Histogram Metric

A histogram metric that allows setting the bucket limit units in one domain, but take measurements in another domain. For example, the bucket limits may represent millisecond durations, yet it is more effecient to use execution cycles to take the measurements. A scaled histogram converts from the the measurement domain (cycles) to the limit units domain using a scale factor provided at initialization. All conversions are encapsulated in the scaled histogram class such that no external scaling is required by any consumer such as a sink.

Configuration

This section discusses configuration. Since Helm charts are capable of combining configuration data at a global level into a component's specific configuration, The combined configuration takes the form as shown below. Note that as the design progresses it is expected that there will be additions.

yaml
    component:
+      metrics:
+        sinks:
+        - type: <sink_type>
+          name: <sink name>
+          settings:
+            sink_setting1: sink_setting_value1
+            sink_setting2: sink_setting_value2

Where (based on being a child of the current component):

metrics

: Metrics configuration for the component

metrics.sinks

: List of sinks defined for the component (may have been combined with global config)

metrics.sinks[].type

: The type for the sink. The type is substituted into the following pattern to determine the lib to load: libhpccmetrics<type><shared_object_extension>

metrics.sinks[].name

: A name for the sink.

metrics.sinks[].settings

: A set of key/value pairs passed to the sink when initialized. It should contain information necessary for the operation of the sink. Nested YML is supported. Example settings are the prometheus server name, or the collection period for a periodic sink.

Metric Naming

Metric names shall follow a convention as outlined in this section. Because different collection systems have different requirements for how metric value reports are generated, naming is split into two parts.

First, each metric is given a base name that describes what the underlying value is. Second, meta data is assigned to each metric to further qualify the value. For example, a set of metrics may count the number of requests a component has received. Each metric would have the same base name, but meta data would separate types of request (GET vs POST), or disposition such as pass or fail.

Base Name

The following convention defines how metric names are formed:

  • Names consist of parts separated by a period (.)

  • Each part shall use snake case (allows for compound names in each part)

  • Each name shall begin with a prefix representing the scop of the metric

  • Names for metric types shall be named as follows (followed by examples):

    Gauges: <scope>.<plural-noun>.<state> esp.requests.waiting, esp.status_requests.waiting

    Counters: <scope>.<plural-noun>.<past-tense-verb> thor.requests.failed, esp.gateway_requests.queued

    Time: <scope>.<singular-noun>.<state or active-verb>.time dali.request.blocked.time, dali.request.process.time

Meta Data

Meta data further qualifies a metric value. This allows metrics to have the same name, but different scopes or categories. Generally, meta data is only used to furher qualify metrics that would have the same base name, but need further distinction. An example best describes a use case for meta data. Consider a component that accepts HTTP requests, but needs to track GET and POST requests separately. Instead of defining metrics with names post_requests.received and get_requests.received, the component creates two metrics with the base name requests.received and attaches meta data describing the request type of POST to one and GET to the other.

Use of meta data allows aggregating both types of requests into a single combined count of received requests while allowing a breakdown by type.

Meta data is represented as a key/value pair and is attached to the metric by the component during metric creation. The sink is responsible for converting meta data into useful information for the collection system during reporting.

The Component Instrumentation section covers how meta data is added to a metric.

Component Instrumentation

In order to instrument a component for metrics using the framework, a component must include the metrics header from jlib (jmetrics.hpp) and add jlib as a dependent lib (if not already doing so).

The general steps for instrumentation are

  1. Create a metrics reporter object
  2. Create metric objects for each internal state to measure and add each to the reporter
  3. Add updates to each metric throughout the component wherever metric state changes

The metrics reporter is a singleton created using the platform defined singleton pattern template. The component must obtain a reference to the reporter. Use the following example:

cpp
using namespace hpccMetrics;
+MetricsManager &metricsManager = queryMetricsManager();

Metrics are wrapped by a standard C++ shared pointer. The component is responsible for maintaining a reference to each shared pointer during the lifetime of the metric. The framework keeps a weak pointer to each metric and thus does not maintain a reference. The following is an example of creating a counter metric and adding it to the reporter. The using namespace eliminates the need to prefix all metrics types with hpccMetrics. Its use is assumed for all code examples that follow.

cpp
std::shared_ptr<CounterMetric> pCounter = std::make_shared<CounterMetric>("metricName", "description");
+metricsManager.add(pCounter);

Note the metric type for both the shared pointer variable and in the make_shared template that creates the metric and returns a shared pointer. Simply substitute other metric types and handle any differences in the constructor arguments as needed.

Once created, add updates to the metric state throughout the component code where required. Using the above example, the following line of code increments the counter metric by 1.

cpp
pCounter->inc(1);

Note that only a single line of code is required to update the metric.

That's it! There are no component requirements related to collection or reporting of metric values. That is handled by the framework and loaded sinks.

For convenience, there are function templates that handle creating the reporter, creating a metric, and adding the metric to the reporter. For example, the above three lines of code that created the reporter, a metric, and added it, can be replaced by the following:

auto pCount = createMetricAndAddToManager<CounterMetric>("metricName", "description");
+

For convenience a similar function template exists for creating custom metrics. For a custom metric the framework must know the metric type and have a reference to the underlying state variable. The following template function handles creating a custom metric and adding it to the reporter (which is created if needed as well):

auto pCustomMetric = createCustomMetricAndAddToManager("customName", "description", metricType, value);
+

Where:

  • metricType

    A defined metric type as defined by the MetricType enum.

  • value

    A reference to the underlying event state which must be a scalar value convertable to a 64bit unsigned integer (__uint64)

Adding Metric Meta Data

A component, depending on requirements, may attach meta data to further qualify created metrics. Meta data takes the form of key value pairs. The base metric class MetricBase constructor defines a parameter for a vector of meta data. Metric subclasses also define meta data as a constructor parameter, however an empty vector is the default. The IMetric interface defines a method for retrieving the meta data.

Meta data is order dependent.

Below are two examples of constructing a metric with meta data. One creates the vector and passes it as a parameter, the other constructs the vector in place.

cpp
MetricMetaData metaData1{{"key1", "value1"}};
+std::shared_ptr<CounterMetric> pCounter1 =
+    std::make_shared<CounterMetric>("requests.completed", "description", SMeasureCount, metaData1);
+
+std::shared_ptr<CounterMetric> pCounter2 =
+    std::make_shared<CounterMetric>("requests.completed", "description", SMeasureCount, MetricMetaData{{"key1", "value2"}});

Metric Units

Metric units are treated separately from the base name and meta data. The reason is to allow the sink to translate based on collection system requirements. The base framework provides a convenience method for converting units into a string. However, the sink is free to do any conversions, both actual units and the string representation, as needed.

Metric units are defined using a subset of the StaticsMeasure enumeration values defined in jstatscodes.h. The current values are used:

  • SMeasureTimeNs - A time measurement in nanoseconds
  • SMeasureCount - A count of events
  • SMeasureSize - Size in bytes
`,132)]))}const u=t(n,[["render",r]]);export{m as __pageData,u as default}; diff --git a/assets/devdoc_Metrics.md.BVV7YlV-.lean.js b/assets/devdoc_Metrics.md.BVV7YlV-.lean.js new file mode 100644 index 00000000000..e83404d25fc --- /dev/null +++ b/assets/devdoc_Metrics.md.BVV7YlV-.lean.js @@ -0,0 +1,17 @@ +import{_ as t,c as i,a3 as s,o as a}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Metrics Framework Design","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/Metrics.md","filePath":"devdoc/Metrics.md","lastUpdated":1731340314000}'),n={name:"devdoc/Metrics.md"};function r(o,e,l,h,c,p){return a(),i("div",null,e[0]||(e[0]=[s(`

Metrics Framework Design

Introduction

This document describes the design of a metrics framework that allows HPCC Systems components to implement a metric collection strategy. Metrics provide the following functionality:

  • Alerts and monitoring

    An important DevOps function is to monitor the cluster and providing alerts when problems are detected. Aggregated metric values from multiple sources provide the necessary data to build a complete picture of cluster health that drives monitoring and alerts.

  • Scaling

    As described above, aggregated metric data is also used to dynamically respond to changing cluster demands and load. Metrics provide the monitoring capability to react and take action

  • Fault diagnosis and resource monitoring

    Metrics provide historical data useful in diagnosing problems by profiling how demand and usage patterns may change prior to a fault. Predictive analysis can also be applied.

  • Analysis of jobs/workunits and profiling

    With proper instrumentation, a robust dynamic metric strategy can track workunit processing. Internal problems with queries should be diagnosed from deep drill down logging.

The document consists of several sections in order to provide requirements as well as the design of framework components.

Definitions

Some definitions are useful.

Metric

: A measurement defined by a component that represents an internal state that is useful in a system reliability engineering function. In the context of the framework, a metric is an object representing the above.

Metric Value

: The current value of a metric.

Metric Updating

: The component task of updating metric state.

Collection

: A framework process of selecting relevant metrics based on configuration and then retrieving their values.

Reporting

: A framework process of converting values obtained during a collection into a format suitable for ingestion by a collection system.

Trigger

: What causes the collection of metric values.

Collection System

: The store for metric values generated during the reporting framework process.

Use Scenarios

This section describes how components expect to use the framework. It is not a complete list of all requirements but rather a sample.

Roxie

Roxie desires to keep a count of many different internal values. Some examples are

  • Disk type operations such as seeks and reads

  • Execution totals

    Need to track items such as total numbers of items such as success and failures as well as breaking some counts into individual reasons. For example, failures may need be categorized such as as

    • Busy
    • Timeout
    • Bad input

    Or even by priority (high, low, sla, etc.)

  • Current operational levels such as the length of internal queues

  • The latency of operations such as queue results, agent responses, and gateway responses

Roxie also has the need to track internal memory usage beyond the pod/system level capabilities. Tracking the state of its large fixed memory pool is necessary.

The Roxie buddy system also must track how often and who is completing requests. The "I Beat You To It" set of metrics must be collected and exposed in order to detect pending node failure. While specific action on these counts is not known up front, it appears clear that these values are useful and should be collected.

There does not appear to be a need for creating and destroying metrics dynamically. The set of metrics is most likely to be created at startup and remain active through the life of the Roxie. If, however, stats collection seeps into the metrics framework, dynamic creation and destruction of stats metrics is a likely requirement.

ESP

There are some interesting decisions with respect to ESP and collection of metrics. Different applications within ESP present different use cases for collection. Ownership of a given task drives some of these use cases. Take workunit queues. If ownership of the task, with respect to metrics, is WsWorkunits, then use cases are centric to that component. However, if agents listening on the queue are to report metrics, then a different set of use cases emerge. It is clear that additional work is needed to generate clear ownership of metrics gathered by ESP and/or the tasks it performs.

ESP needs to report the activeTransactions value from the TxSummary class(es). This gives an indication of how busy the ESP is in terms of client requests.

Direct measurement of response time in requests may not be useful since the type of request causes different execution paths within ESP that are expected to take widely varying amounts of time. Creation of metrics for each method is not recommended. However, two possible solutions are to a) create a metric for request types, or b) use a histogram to measure response time ranges. Another option mentioned redefines the meaning of a bucket in a histogram. Instead of a numeric distribution, each bucket represents a unique subtask within an overall "metric" representing a measured operation. This should be explored whether for operational or developmental purposes.

For tracking specific queries and their health, the feeling is that logging can accomplish this better than metrics since the list of queries to monitor will vary between clusters. Additionally, operational metrics solving the cases mentioned above will give a view into the overall health of ESP which will affect the execution of queries. Depending on actions taken by these metrics, scaling may solve overload conditions to keep cluster responsiveness acceptable.

For Roxie a workunit operates as a service. Measuring service performance using a histogram to capture response times as a distribution may be appropriate. Extracting the 95th percentile of response time may be useful as well.

There are currently no use cases requiring consistency between values of different metrics.

At this time the only concrete metric identified is the number of requests received. As the framework design progresses and ESP is instrumented, the list will grow.

Dali Use Cases

From information gathered, Dali plans to keep counts and rates for many of the items it manages.

Framework Design

This section covers the design and architecture of the framework. It discusses the main areas of the design, the interactions between each area, and an overall process model of how the framework operates.

The framework consists of three major areas: metrics, sinks, and the glue logic. These areas work together with the platform and the component to provide a reusable metrics collection function.

Metrics represent the quantifiable component state measurements used to track and assess the status of the component. Metrics are typically scalar values that are easily aggregated by a collection system. Aggregated values provide the necessary input to take component and cluster actions such as scaling up and down. The component is responsible for creating metrics and instrumenting the code. The framework provides the support for collecting and reporting the values. Metrics provide the following:

  • Simple methods for the component to update the metric
  • Simple methods for the framework to retrieve metric value(s)
  • Handling of all synchronization between updating and retrieving metric values

In addition, the framework provides the support for retrieving values so that the component does not participate in metric reporting. The component simply creates the metrics it needs, then instruments the component to update the metric whenever its state changes. For example, the component may create a metric that counts the total number of requests received. Then, wherever the component receives a request, a corresponding update to the count is added. Nowhere in the component is any code added to retrieve the count as that is handled by the framework.

Sinks provide a pluggable interface to hide the specifics of collection systems so that the metrics framework is independent of those dependencies. Sinks:

  • Operate independently of other sinks in the system
  • Convert metric native values into collection system specific measurements and reports
  • Drive the collection and reporting processes

The third area of the framework is the glue logic, referred to as the MetricsManager. It manages the metrics system for the component. It provides the following:

  • Handles framework initialization
  • Loads sinks as required
  • Manages the list of metrics for the component
  • Handles collection and reporting with a set of convenience methods used by sinks

The framework is designed to be instantiated into a component as part of its process and address space. All objects instantiated as part of the framework are owned by the component and are not shareable with any other component whether local or remote. Any coordination or consistency requirements that may arise in the implementation of a sink shall be the sole responsibility of the sink.

Framework Implementation

The framework is implemented within jlib. The following sections describe each area of the framework.

Metrics

Components use metrics to measure their internal state. Metrics can represent everything from the number of requests received to the average length some value remains cached. Components are responsible for creating and updating metrics for each measured state. The framework shall provide a set of metrics designed to cover the majority of component measurement requirements. All metrics share a common interface to allow the framework to manage them in a common way.

To meet the requirement to manage metrics independent of the underlying metric state, all metrics implement a common interface. All metrics then add their specific methods to update and retrieve internal state. Generally the component uses the update method(s) to update state and the framework uses retrieval methods to get current state when reporting. The metric insures synchronized access.

For components that already have an implementation that tracks a metric, the framework provides a way to instantiate a custom metric. The custom metric allows the component to leverage the existing implementation and give the framework access to the metric value for collection and reporting. Note that custom metrics only support simple scalar metrics such as a counter or a gauge.

Sinks

The framework defines a sink interface to support the different requirements of collection systems. Examples of collection systems are Prometheus, Datadog, and Elasticsearch. Each has different requirements for how and when measurements are ingested. The following are examples of different collection system requirements:

  • Polled vs Periodic
  • Single measurement vs multiple reports
  • Report format (JSON, text, etc.)
  • Push vs Pull

Sinks are responsible for two main functions: initiating a collection and reporting measurements to the collection system. The Metrics Reporter provides the support to complete these functions.

The sink encapsulates all of the collection system requirements providing a pluggable architecture that isolates components from these differences. The framework supports multiple sinks concurrently, each operating independently.

Instrumented components are not aware of the sink or sinks in use. Sinks can be changed without requiring changes to a component. Therefore, components are independent of the collection system(s) in use.

Metrics Reporter

The metrics reporter class provides all of the common functions to bind together the component, the metrics it creates, and the sinks to which measurements are reported. It is responsible for the following:

  • Initialization of the framework
  • Managing the metrics created by the component
  • Handling collection and reporting as directed by configured sinks

Metrics Implementations

The sections that follow discuss metric implementations.

Counter Metric

A counter metric is a monotonically increasing value that "counts" the total occurrences of some event. Examples include the number of requests received, or the number of cache misses. Once created, the component instruments the code with updates to the count whenever appropriate.

Gauge Metric

A gauge metric is a continuously updated value representing the current state of an interesting value in the component. For example, the amount of memory used in an internal buffer, or the number of requests waiting on a queue. A gauge metric may increase or decrease in value as needed. Reading the value of a gauge is a stateless operation in that there are no dependencies on the previous reading. The value returned shall always be the current state.

Once created, the component shall update the gauge anytime the state of what is measured is updated. The metric shall provide methods to increase and decrease the value. The sink reads the value during collection and reporting.

Custom Metric

A custom metric is a class that allows a component to leverage existing metrics. The component creates an instance of a custom metric (a templated class) and passes a reference to the underlying metric value. When collection is performed, the custom metric simply reads the value of the metric using the reference provided during construction. The component maintains full responsibility for updating the metric value as the custom metric class provides no update methods. The component is also responsible for ensuring atomic access to the value if necessary.

Histogram Metric

Records counts of measurements according to defined bucket limits. When created, the caller defines as set of bucket limits. During event recording, the component records measurements. The metric separates each recorded measurement into its bucket by testing the measurement value against each bucket limit using a less than or equal test. Each bucket contains a count of measurements meeting that criteria. Additionally, the metric maintains a default bucket for measurements outside of the maximum bucket limit. This is sometimes known as the "inf" bucket.

Some storage systems, such as Prometheus, require each bucket to accumulate its measurements with the previous bucket(s). It is the responsibility of the sink to accumulate values as needed.

Scaled Histogram Metric

A histogram metric that allows setting the bucket limit units in one domain, but take measurements in another domain. For example, the bucket limits may represent millisecond durations, yet it is more effecient to use execution cycles to take the measurements. A scaled histogram converts from the the measurement domain (cycles) to the limit units domain using a scale factor provided at initialization. All conversions are encapsulated in the scaled histogram class such that no external scaling is required by any consumer such as a sink.

Configuration

This section discusses configuration. Since Helm charts are capable of combining configuration data at a global level into a component's specific configuration, The combined configuration takes the form as shown below. Note that as the design progresses it is expected that there will be additions.

yaml
    component:
+      metrics:
+        sinks:
+        - type: <sink_type>
+          name: <sink name>
+          settings:
+            sink_setting1: sink_setting_value1
+            sink_setting2: sink_setting_value2

Where (based on being a child of the current component):

metrics

: Metrics configuration for the component

metrics.sinks

: List of sinks defined for the component (may have been combined with global config)

metrics.sinks[].type

: The type for the sink. The type is substituted into the following pattern to determine the lib to load: libhpccmetrics<type><shared_object_extension>

metrics.sinks[].name

: A name for the sink.

metrics.sinks[].settings

: A set of key/value pairs passed to the sink when initialized. It should contain information necessary for the operation of the sink. Nested YML is supported. Example settings are the prometheus server name, or the collection period for a periodic sink.

Metric Naming

Metric names shall follow a convention as outlined in this section. Because different collection systems have different requirements for how metric value reports are generated, naming is split into two parts.

First, each metric is given a base name that describes what the underlying value is. Second, meta data is assigned to each metric to further qualify the value. For example, a set of metrics may count the number of requests a component has received. Each metric would have the same base name, but meta data would separate types of request (GET vs POST), or disposition such as pass or fail.

Base Name

The following convention defines how metric names are formed:

  • Names consist of parts separated by a period (.)

  • Each part shall use snake case (allows for compound names in each part)

  • Each name shall begin with a prefix representing the scop of the metric

  • Names for metric types shall be named as follows (followed by examples):

    Gauges: <scope>.<plural-noun>.<state> esp.requests.waiting, esp.status_requests.waiting

    Counters: <scope>.<plural-noun>.<past-tense-verb> thor.requests.failed, esp.gateway_requests.queued

    Time: <scope>.<singular-noun>.<state or active-verb>.time dali.request.blocked.time, dali.request.process.time

Meta Data

Meta data further qualifies a metric value. This allows metrics to have the same name, but different scopes or categories. Generally, meta data is only used to furher qualify metrics that would have the same base name, but need further distinction. An example best describes a use case for meta data. Consider a component that accepts HTTP requests, but needs to track GET and POST requests separately. Instead of defining metrics with names post_requests.received and get_requests.received, the component creates two metrics with the base name requests.received and attaches meta data describing the request type of POST to one and GET to the other.

Use of meta data allows aggregating both types of requests into a single combined count of received requests while allowing a breakdown by type.

Meta data is represented as a key/value pair and is attached to the metric by the component during metric creation. The sink is responsible for converting meta data into useful information for the collection system during reporting.

The Component Instrumentation section covers how meta data is added to a metric.

Component Instrumentation

In order to instrument a component for metrics using the framework, a component must include the metrics header from jlib (jmetrics.hpp) and add jlib as a dependent lib (if not already doing so).

The general steps for instrumentation are

  1. Create a metrics reporter object
  2. Create metric objects for each internal state to measure and add each to the reporter
  3. Add updates to each metric throughout the component wherever metric state changes

The metrics reporter is a singleton created using the platform defined singleton pattern template. The component must obtain a reference to the reporter. Use the following example:

cpp
using namespace hpccMetrics;
+MetricsManager &metricsManager = queryMetricsManager();

Metrics are wrapped by a standard C++ shared pointer. The component is responsible for maintaining a reference to each shared pointer during the lifetime of the metric. The framework keeps a weak pointer to each metric and thus does not maintain a reference. The following is an example of creating a counter metric and adding it to the reporter. The using namespace eliminates the need to prefix all metrics types with hpccMetrics. Its use is assumed for all code examples that follow.

cpp
std::shared_ptr<CounterMetric> pCounter = std::make_shared<CounterMetric>("metricName", "description");
+metricsManager.add(pCounter);

Note the metric type for both the shared pointer variable and in the make_shared template that creates the metric and returns a shared pointer. Simply substitute other metric types and handle any differences in the constructor arguments as needed.

Once created, add updates to the metric state throughout the component code where required. Using the above example, the following line of code increments the counter metric by 1.

cpp
pCounter->inc(1);

Note that only a single line of code is required to update the metric.

That's it! There are no component requirements related to collection or reporting of metric values. That is handled by the framework and loaded sinks.

For convenience, there are function templates that handle creating the reporter, creating a metric, and adding the metric to the reporter. For example, the above three lines of code that created the reporter, a metric, and added it, can be replaced by the following:

auto pCount = createMetricAndAddToManager<CounterMetric>("metricName", "description");
+

For convenience a similar function template exists for creating custom metrics. For a custom metric the framework must know the metric type and have a reference to the underlying state variable. The following template function handles creating a custom metric and adding it to the reporter (which is created if needed as well):

auto pCustomMetric = createCustomMetricAndAddToManager("customName", "description", metricType, value);
+

Where:

  • metricType

    A defined metric type as defined by the MetricType enum.

  • value

    A reference to the underlying event state which must be a scalar value convertable to a 64bit unsigned integer (__uint64)

Adding Metric Meta Data

A component, depending on requirements, may attach meta data to further qualify created metrics. Meta data takes the form of key value pairs. The base metric class MetricBase constructor defines a parameter for a vector of meta data. Metric subclasses also define meta data as a constructor parameter, however an empty vector is the default. The IMetric interface defines a method for retrieving the meta data.

Meta data is order dependent.

Below are two examples of constructing a metric with meta data. One creates the vector and passes it as a parameter, the other constructs the vector in place.

cpp
MetricMetaData metaData1{{"key1", "value1"}};
+std::shared_ptr<CounterMetric> pCounter1 =
+    std::make_shared<CounterMetric>("requests.completed", "description", SMeasureCount, metaData1);
+
+std::shared_ptr<CounterMetric> pCounter2 =
+    std::make_shared<CounterMetric>("requests.completed", "description", SMeasureCount, MetricMetaData{{"key1", "value2"}});

Metric Units

Metric units are treated separately from the base name and meta data. The reason is to allow the sink to translate based on collection system requirements. The base framework provides a convenience method for converting units into a string. However, the sink is free to do any conversions, both actual units and the string representation, as needed.

Metric units are defined using a subset of the StaticsMeasure enumeration values defined in jstatscodes.h. The current values are used:

  • SMeasureTimeNs - A time measurement in nanoseconds
  • SMeasureCount - A count of events
  • SMeasureSize - Size in bytes
`,132)]))}const u=t(n,[["render",r]]);export{m as __pageData,u as default}; diff --git a/assets/devdoc_NewFileProcessing.md.DGLPSf2v.js b/assets/devdoc_NewFileProcessing.md.DGLPSf2v.js new file mode 100644 index 00000000000..064d17d7d1b --- /dev/null +++ b/assets/devdoc_NewFileProcessing.md.DGLPSf2v.js @@ -0,0 +1,14 @@ +import{_ as t,c as a,a3 as i,o}from"./chunks/framework.DkhCEVKm.js";const f=JSON.parse('{"title":"Storage planes","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/NewFileProcessing.md","filePath":"devdoc/NewFileProcessing.md","lastUpdated":1731340314000}'),r={name:"devdoc/NewFileProcessing.md"};function s(n,e,l,p,h,c){return o(),a("div",null,e[0]||(e[0]=[i(`

Documentation about the new file work.

YAML files. The following are the YAML definitions which are used to serialize file information from dali/external store to the engines and if necessary to the worker nodes.

Storage planes

This is already covered in the deployed helm charts. It has been extended and rationalized slightly.

storage:

: hostGroups: - name: <required> hosts: [ .... ] - name: <required> hostGroup: <name> count: <unsigned:#hosts> # how many hosts within the host group are used ?(default is number of hosts) offset: <unsigned:0> # index of first host included in the derived group delta: <unsigned:0> # first host within the range[offset..offset+count-1] in the derived group

planes:
+
+:   name: \\<required\\> prefix: \\<path\\> \\# Root directory for
+    accessing the plane (if pvc defined), or url to access plane.
+    numDevices: 1 \\# number of devices that are part of the plane
+    hostGroup: \\<name\\> \\# Name of the host group for bare metal
+    hosts: \\[ host-names \\] \\# A list of host names for bare metal
+    secret: \\<secret-id\\> \\# what secret is required to access the
+    files. options: \\# not sure if it is needed
+

Changes: * The replication information has been removed from the storage plane. It will now be specified on the thor instance indicating where (if anywhere) files are replicated. * The hash character (#) in a prefix or a secret name will be substituted with the device number. This replaces the old includeDeviceInPath property. This allows more flexible device substition for both local mounts and storage accounts. The number of hashes provides the default padding for the device number. (Existing Helm charts will need to be updated to follow these new rules.) * Neither thor or roxie replication is special cased. They are represented as multiple locations that the file lives (see examples below). Existing baremetal environments would be mapped to this new representation with implicit replication planes. (It is worth checking the mapping to roxie is fine.)

Files

file: - name: <logical-file-name> format: <type> # e.g. flat, csv, xml, key, parquet meta: <binary> # (opt) format of the file, (serialized hpcc field information). metaCrc: <unsigned> # hash of the meta numParts # How many file parts. singlePartNoSuffix: <boolean> # Does a single part file include .part_1_of_1? numRows: # total number of rows in the file (if known) rawSize: # total uncompressed size diskSize # is this useful? when binary copying? planes: [] # list of storage planes that the file is stored on. tlk: # ???Should the tlk be stored in the meta and returned? splitType: <split-format> # Are there associated split points, and if so what format? (And if so what variant?)

#options relating to the format of the input file:

: grouped: <boolean> # is the file grouped? compressed: <boolean> blockCompressed: <boolean> formatOptions: # Any options that relate to the file format e.g. csvTerminator. These are nested because they can be completely free format recordSize: # if a fixed size record. Not really sure it is useful

part: \\# optional information about each of the file parts (Cannot
+implement virtual file position without this) - numRows: \\<count\\>
+\\# number of rows in the file part rawSize: \\<size\\> \\# uncompressed
+size of the file part diskSize: \\<size\\> \\# size of the part on disk
+

# extra fields that are used to return information from the file lookup service

missing: <boolean> # true if the file could not be found external: <boolean> # filename of the form external:: or plane:

If the information needs to be signed to be passed to dafilesrv for example, the entire structure of (storage, files) is serialized, and compressed, and that then signed.

Functions

Logically executed on the engine, and retrived from dali or in future versions from an esp service (even if for remote reads).

GetFileInfomation(<logical-filename>, <options>)

The logical-filename can be any logical name - including a super file, or an implicit superfile.

options include: * Are compressed sizes needed? * Are signatures required? * Is virtual fileposition (non-local) required? * name of the user

This returns a structure that provides information about a list of files

meta:

: hostGroups: storage: files: secrets: #The secret names are known, how do we know which keys are required for those secrets?

Some key questions: * Should the TLK be in the dali meta information? [Possibly, but not in first phase. ] * Should the split points be in the dali meta information? [Probably not, but the meta should indicate whether they exist, and if so what format they are. ] * Super files (implicit or explicit) can contain the same file information more than once. Should it be duplicated, or have a flag to indicate a repeat. [I suspect this is fairly uncommon, so duplication would be fine for the first version.] * What storage plane information is serialized back? [ all is simplest. Can optimize later. ]

NOTE: This doesn't address the question of writing to a disk file...


Local class for interpreting the results. Logically executed on the manager, and may gather extra information that will be serialized to all workers. The aim is that the same class implementations are used by all the engines (and fileview in esp).

MasterFileCollection : RemoteFileCollection : FileCollection(eclReadOptions, eclFormatOptions, wuid, user, expectedMeta, projectedMeta); MasterFileCollection //Master has access to dali RemoteFileCollection : has access to remote esp // think some more

FileCollection::GatherFileInformation(<logical-filename>, gatherOptions); - potentially called once per query. - class is responsible for optimizing case where it matches the previous call (e.g. in a child query). - possibly responsible for retrieving the split points ()

Following options are used to control whether split points are retrieved when file information is gathered * number of channels reading the data? * number of strands reading each channel? * preserve order?

gatherOptions: * is it a temporary file?

This class serializes all information to every worker, where it is used to recereate a copy of the master filecollection. This will contain information derived from dali, and locally e.g. options specified in the activity helper. Each worker has a complete copy of the file information. (This is similar to dafilesrv with security tokens.)

The files that are actually required by a worker are calculated by calling the following function. (Note the derived information is not serialized.)

FilePartition FileCollection::calculatePartition(numChannels, partitionOptions)

partitionOptions: * number of channels reading the data? * number of strands reading each channel? * which channel? * preserve order? * myIP

A file partition contains a list of file slices:

class FileSlice (not serialized) { IMagicRowStream * createRowStream(filter, ...); // MORE! File * logicalFile; offset_t startOffset; offset_t endOffset; };

Things to bear in mind: - Optimize same file reused in a child query (filter likely to change) - Optimize same format reused in a child query (filename may be dynamic) - Intergrating third party file formats and distributed file systems may require extra information. - optimize reusing the format options. - ideally fail over to a backup copy midstream.. and retry in failed read e.g. if network fault

Examples

Example definition for a thor400, and two thor200s on the same nodes:

hostGroup: - name: thor400Group host: [node400_01,node400_02,node400_03,...node400_400]

storage:

: planes: #Simple 400 way thor - name: thor400 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group #The storage plane used for replicating files on thor. - name: thor400_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group offset: 1 # A 200 way thor using the first 200 nodes as the thor 400 - name: thor200A prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 # A 200 way thor using the second 200 nodes as the thor 400 - name: thor200B prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 start: 200 # The replication plane for a 200 way thor using the second 200 nodes as the thor 400 - name: thor200B_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 start: 200 offset: 1 # A roxie storage where 50way files are stored on a 100 way roxie - name: roxie100 prefix: /var/lib/HPCCSystems/roxie100 hosts: thor400Group size: 50 # The replica of the roxie storage where 50way files are stored on a 100 way roxie - name: roxie100_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group start: 50 size: 50

device = (start + (part + offset) % size;

size <= numDevices offset < numDevices device <= numDevices;

There is no special casing of roxie replication, and each file exists on multiple storage planes. All of these should be considered when determining which is the best copy to read from a particular engine node.

Creating storage planes from an existing systems [implemented]

Milestones:

a) Create baremetal storage planes [done]

b) [a] Start simplifying information in dali meta (e.g. partmask, remove full path name) c) [a] Switch reading code to use storageplane away from using dali path and environment paths - in ALL disk reading and writing code - change numDevices so it matches the container d) [c] Convert dali information from using copies to multiple groups/planese) [a] Reimplement the current code to create an IPropertyTree from dali file information (in a form that can be reused in dali) *f) [e] Refactor existing PR to use data in an IPropertyTree and cleanly separate the interfaces. g) Switch hthor over to using the new classes by default and work through all issues h) Refactor stream reading code. Look at the spark interfaces for inspiration/compatibility i) Refactor disk writing code into common class? j) [e] create esp service for accessing meta information k) [h] Refactor and review azure blob code l) [k] Re-implement S3 reading and writing code.

m) Switch fileview over to using the new classes. (Great test they can be used in another context + fixes a longstanding bug.)

) Implications for index reading? Will they end up being treated as a normal file? Don't implement for 8.0, although interface may support it.

*) My primary focus for initial work.

File reading refactoring

Buffer sizes: - storage plane specifies an optimal reading minimum - compression may have a requirement - the use for the data may impose a requirement e.g. a subset of the data, or only fetching a single record

  • parallel disk reading may want to read a big chunk, but then process in sections. groan.

Look at lambda functions to create split points for a file. Can we use the java classes to implement it on binary files (and csv/xml)?

****************** Reading classes and meta information****************** meta comes from a combination of the information in dfs and the helper

The main meta information uses the same structure that is return by the function that returns file infromation from dali. The format specific options are contained in a nested attribute so they can be completely arbitrary

The helper class also generates a meta structure. Some options fill in root elements - e.g. compressed. Some fill in a new section (hints: @x=y). The format options are generated from the paramaters to the dataset format.

note normally there is only a single (or very few) files, so merging isn't too painful. queryMeta() queryOptions() rename meta to format? ???

DFU server

Where does DFUserver fit in in a container system?

DFU has the following main functionality in a bare metal system: a) Spray a file from a 1 way landing zone to an N-way thor b) Convert file format when spraying. I suspect utf-16->utf8 is the only option actually used. c) Spray multiple files from a landing zone to a single logical file on an N-way thor d) Copy a logical file from a remote environment e) Despray a logical file to an external landing zone. f) Replicate an existing logical file on a given group. g) Copy logical files between groups h) File monitoring i) logical file operations j) superfile operations

ECL has the ability to read a logical file directly from a landingzone using 'FILE::<ip>' file syntax, but I don't think it is used very frequently.

How does this map to a containerized system? I think the same basic operations are likely to be useful. a) In most scenarios Landing zones are likely to be replaced with (blob) storage accounts. But for security reasons these are likely to remain distinct from the main location used by HPCC to store datasets. (The customer will have only access keys to copy files to and from those storage accounts.) The containerized system has a way for ECL to directly read from a blob storage account ('PLANE::<plane'), but I imagine users will still want to copy the files in many situations to control the lifetime of the copies etc. b) We still need a way to convert from utf16 to utf8, or extend the platform to allow utf16 to be read directly. c) This is still equally useful, allowing a set of files to be stored as a single file in a form that is easy for ECL to process. d) Important for copying data from an existing bare metal system to the cloud, and from a cloud system back to a bare metal system. e) Useful for exporting results to customers f+g) Essentially the same thing in the cloud world. It might still be useful to have h) I suspect we will need to map this to cloud-specific apis. i+j) Just as applicable in the container world.

Broadly, landing zones in bare metal map to special storage planes in containerized, and groups also map to more general storage planes.

There are a couple of complications connected with the implementation:

  1. Copying is currently done by starting an ftslave process on either the source or the target nodes. In the container world there is no local node, and I think we would prefer not to start a process in order to copy each file. 2) Copying between storage groups should be done using the cloud provider api, rather than transferring data via a k8s job.

Suggestions:

  • Have a load balanced dafilesrv which supports multiple replicas. It would have a secure external service, and an internal service for trusted components.
  • Move the ftslave logic into dafilesrv. Move the current code for ftslave actions into dafilesrv with new operations.
  • When copying from/to a bare metal system the requests are sent to the dafilesrv for the node that currently runs ftslave. For a container system the requests are sent to the loadbalanced service.
  • It might be possible to migrate to lamda style functions for some of the work...
  • A later optimization would use a cloud service where it was possible.
  • When local split points are supported it may be better to spray a file 1:1 along with partition information. Even without local split points it may still be better to spray a file 1:1 (cheaper).
  • What are the spray targets? It may need to be storage plane + number of parts, rather than a target cluster. The default number of parts is the #devices on the storage plane.

=> Milestones a) Move ftslave code to dafilesrv (partition, pull, push) [Should be included in 7.12.x stream to allow remote read compatibility?] b) Create a dafilesrv component to the helm charts, with internal and external services. c) use storage planes to determine how files are sprayed etc. (bare-metal, #devices) Adapt dfu/fileservices calls to take (storageplane,number) instead of cluster. There should already be a 1:1 mapping from existing cluster to storage planes in a bare-metal system, so this may not involve much work. [May also need a flag to indicate if ._1_of_1 is appended?] d) Select correct dafilesrv for bare-metal storage planes, or load balanced service for other. (May need to think through how remote files are represented.)

=> Can import from a bare metal system or a containerized system using command line??

: NOTE: Bare-metal to containerized will likely need push operations on the bare-metal system. (And therefore serialized security information) This may still cause issues since it is unlikely containerized will be able to pull from bare-metal. Pushing, but not creating a logical file entry on the containerized system should be easier since it can use a local storage plane definition.

e) Switch over to using the esp based meta information, so that it can include details of storage planes and secrets.

: [Note this would also need to be in 7.12.x to allow remote export to containerized, that may well be a step too far]

f) Add option to configure the number of file parts for spray/copy/despray g) Ensure that eclwatch picks up the list of storage planes (and the default number of file parts), and has ability to specify #parts.

Later: h) plan how cloud-services can be used for some of the copies i) investigate using serverless functions to calculate split points. j) Use refactored disk read/write interfaces to clean up read and copy code. k) we may not want to expose access keys to allow remote reads/writes - in which they would need to be pushed from a bare-metal dafilesrv to a containerized dafilesrv.

Other dependencies: * Refactored file meta information. If this is switching to being plane based, then the meta information should also be plane based. Main difference is not including the path in the meta information (can just be ignored) * esp service for getting file information. When reading remotely it needs to go via this now...

`,80)]))}const m=t(r,[["render",s]]);export{f as __pageData,m as default}; diff --git a/assets/devdoc_NewFileProcessing.md.DGLPSf2v.lean.js b/assets/devdoc_NewFileProcessing.md.DGLPSf2v.lean.js new file mode 100644 index 00000000000..064d17d7d1b --- /dev/null +++ b/assets/devdoc_NewFileProcessing.md.DGLPSf2v.lean.js @@ -0,0 +1,14 @@ +import{_ as t,c as a,a3 as i,o}from"./chunks/framework.DkhCEVKm.js";const f=JSON.parse('{"title":"Storage planes","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/NewFileProcessing.md","filePath":"devdoc/NewFileProcessing.md","lastUpdated":1731340314000}'),r={name:"devdoc/NewFileProcessing.md"};function s(n,e,l,p,h,c){return o(),a("div",null,e[0]||(e[0]=[i(`

Documentation about the new file work.

YAML files. The following are the YAML definitions which are used to serialize file information from dali/external store to the engines and if necessary to the worker nodes.

Storage planes

This is already covered in the deployed helm charts. It has been extended and rationalized slightly.

storage:

: hostGroups: - name: <required> hosts: [ .... ] - name: <required> hostGroup: <name> count: <unsigned:#hosts> # how many hosts within the host group are used ?(default is number of hosts) offset: <unsigned:0> # index of first host included in the derived group delta: <unsigned:0> # first host within the range[offset..offset+count-1] in the derived group

planes:
+
+:   name: \\<required\\> prefix: \\<path\\> \\# Root directory for
+    accessing the plane (if pvc defined), or url to access plane.
+    numDevices: 1 \\# number of devices that are part of the plane
+    hostGroup: \\<name\\> \\# Name of the host group for bare metal
+    hosts: \\[ host-names \\] \\# A list of host names for bare metal
+    secret: \\<secret-id\\> \\# what secret is required to access the
+    files. options: \\# not sure if it is needed
+

Changes: * The replication information has been removed from the storage plane. It will now be specified on the thor instance indicating where (if anywhere) files are replicated. * The hash character (#) in a prefix or a secret name will be substituted with the device number. This replaces the old includeDeviceInPath property. This allows more flexible device substition for both local mounts and storage accounts. The number of hashes provides the default padding for the device number. (Existing Helm charts will need to be updated to follow these new rules.) * Neither thor or roxie replication is special cased. They are represented as multiple locations that the file lives (see examples below). Existing baremetal environments would be mapped to this new representation with implicit replication planes. (It is worth checking the mapping to roxie is fine.)

Files

file: - name: <logical-file-name> format: <type> # e.g. flat, csv, xml, key, parquet meta: <binary> # (opt) format of the file, (serialized hpcc field information). metaCrc: <unsigned> # hash of the meta numParts # How many file parts. singlePartNoSuffix: <boolean> # Does a single part file include .part_1_of_1? numRows: # total number of rows in the file (if known) rawSize: # total uncompressed size diskSize # is this useful? when binary copying? planes: [] # list of storage planes that the file is stored on. tlk: # ???Should the tlk be stored in the meta and returned? splitType: <split-format> # Are there associated split points, and if so what format? (And if so what variant?)

#options relating to the format of the input file:

: grouped: <boolean> # is the file grouped? compressed: <boolean> blockCompressed: <boolean> formatOptions: # Any options that relate to the file format e.g. csvTerminator. These are nested because they can be completely free format recordSize: # if a fixed size record. Not really sure it is useful

part: \\# optional information about each of the file parts (Cannot
+implement virtual file position without this) - numRows: \\<count\\>
+\\# number of rows in the file part rawSize: \\<size\\> \\# uncompressed
+size of the file part diskSize: \\<size\\> \\# size of the part on disk
+

# extra fields that are used to return information from the file lookup service

missing: <boolean> # true if the file could not be found external: <boolean> # filename of the form external:: or plane:

If the information needs to be signed to be passed to dafilesrv for example, the entire structure of (storage, files) is serialized, and compressed, and that then signed.

Functions

Logically executed on the engine, and retrived from dali or in future versions from an esp service (even if for remote reads).

GetFileInfomation(<logical-filename>, <options>)

The logical-filename can be any logical name - including a super file, or an implicit superfile.

options include: * Are compressed sizes needed? * Are signatures required? * Is virtual fileposition (non-local) required? * name of the user

This returns a structure that provides information about a list of files

meta:

: hostGroups: storage: files: secrets: #The secret names are known, how do we know which keys are required for those secrets?

Some key questions: * Should the TLK be in the dali meta information? [Possibly, but not in first phase. ] * Should the split points be in the dali meta information? [Probably not, but the meta should indicate whether they exist, and if so what format they are. ] * Super files (implicit or explicit) can contain the same file information more than once. Should it be duplicated, or have a flag to indicate a repeat. [I suspect this is fairly uncommon, so duplication would be fine for the first version.] * What storage plane information is serialized back? [ all is simplest. Can optimize later. ]

NOTE: This doesn't address the question of writing to a disk file...


Local class for interpreting the results. Logically executed on the manager, and may gather extra information that will be serialized to all workers. The aim is that the same class implementations are used by all the engines (and fileview in esp).

MasterFileCollection : RemoteFileCollection : FileCollection(eclReadOptions, eclFormatOptions, wuid, user, expectedMeta, projectedMeta); MasterFileCollection //Master has access to dali RemoteFileCollection : has access to remote esp // think some more

FileCollection::GatherFileInformation(<logical-filename>, gatherOptions); - potentially called once per query. - class is responsible for optimizing case where it matches the previous call (e.g. in a child query). - possibly responsible for retrieving the split points ()

Following options are used to control whether split points are retrieved when file information is gathered * number of channels reading the data? * number of strands reading each channel? * preserve order?

gatherOptions: * is it a temporary file?

This class serializes all information to every worker, where it is used to recereate a copy of the master filecollection. This will contain information derived from dali, and locally e.g. options specified in the activity helper. Each worker has a complete copy of the file information. (This is similar to dafilesrv with security tokens.)

The files that are actually required by a worker are calculated by calling the following function. (Note the derived information is not serialized.)

FilePartition FileCollection::calculatePartition(numChannels, partitionOptions)

partitionOptions: * number of channels reading the data? * number of strands reading each channel? * which channel? * preserve order? * myIP

A file partition contains a list of file slices:

class FileSlice (not serialized) { IMagicRowStream * createRowStream(filter, ...); // MORE! File * logicalFile; offset_t startOffset; offset_t endOffset; };

Things to bear in mind: - Optimize same file reused in a child query (filter likely to change) - Optimize same format reused in a child query (filename may be dynamic) - Intergrating third party file formats and distributed file systems may require extra information. - optimize reusing the format options. - ideally fail over to a backup copy midstream.. and retry in failed read e.g. if network fault

Examples

Example definition for a thor400, and two thor200s on the same nodes:

hostGroup: - name: thor400Group host: [node400_01,node400_02,node400_03,...node400_400]

storage:

: planes: #Simple 400 way thor - name: thor400 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group #The storage plane used for replicating files on thor. - name: thor400_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group offset: 1 # A 200 way thor using the first 200 nodes as the thor 400 - name: thor200A prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 # A 200 way thor using the second 200 nodes as the thor 400 - name: thor200B prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 start: 200 # The replication plane for a 200 way thor using the second 200 nodes as the thor 400 - name: thor200B_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 start: 200 offset: 1 # A roxie storage where 50way files are stored on a 100 way roxie - name: roxie100 prefix: /var/lib/HPCCSystems/roxie100 hosts: thor400Group size: 50 # The replica of the roxie storage where 50way files are stored on a 100 way roxie - name: roxie100_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group start: 50 size: 50

device = (start + (part + offset) % size;

size <= numDevices offset < numDevices device <= numDevices;

There is no special casing of roxie replication, and each file exists on multiple storage planes. All of these should be considered when determining which is the best copy to read from a particular engine node.

Creating storage planes from an existing systems [implemented]

Milestones:

a) Create baremetal storage planes [done]

b) [a] Start simplifying information in dali meta (e.g. partmask, remove full path name) c) [a] Switch reading code to use storageplane away from using dali path and environment paths - in ALL disk reading and writing code - change numDevices so it matches the container d) [c] Convert dali information from using copies to multiple groups/planese) [a] Reimplement the current code to create an IPropertyTree from dali file information (in a form that can be reused in dali) *f) [e] Refactor existing PR to use data in an IPropertyTree and cleanly separate the interfaces. g) Switch hthor over to using the new classes by default and work through all issues h) Refactor stream reading code. Look at the spark interfaces for inspiration/compatibility i) Refactor disk writing code into common class? j) [e] create esp service for accessing meta information k) [h] Refactor and review azure blob code l) [k] Re-implement S3 reading and writing code.

m) Switch fileview over to using the new classes. (Great test they can be used in another context + fixes a longstanding bug.)

) Implications for index reading? Will they end up being treated as a normal file? Don't implement for 8.0, although interface may support it.

*) My primary focus for initial work.

File reading refactoring

Buffer sizes: - storage plane specifies an optimal reading minimum - compression may have a requirement - the use for the data may impose a requirement e.g. a subset of the data, or only fetching a single record

  • parallel disk reading may want to read a big chunk, but then process in sections. groan.

Look at lambda functions to create split points for a file. Can we use the java classes to implement it on binary files (and csv/xml)?

****************** Reading classes and meta information****************** meta comes from a combination of the information in dfs and the helper

The main meta information uses the same structure that is return by the function that returns file infromation from dali. The format specific options are contained in a nested attribute so they can be completely arbitrary

The helper class also generates a meta structure. Some options fill in root elements - e.g. compressed. Some fill in a new section (hints: @x=y). The format options are generated from the paramaters to the dataset format.

note normally there is only a single (or very few) files, so merging isn't too painful. queryMeta() queryOptions() rename meta to format? ???

DFU server

Where does DFUserver fit in in a container system?

DFU has the following main functionality in a bare metal system: a) Spray a file from a 1 way landing zone to an N-way thor b) Convert file format when spraying. I suspect utf-16->utf8 is the only option actually used. c) Spray multiple files from a landing zone to a single logical file on an N-way thor d) Copy a logical file from a remote environment e) Despray a logical file to an external landing zone. f) Replicate an existing logical file on a given group. g) Copy logical files between groups h) File monitoring i) logical file operations j) superfile operations

ECL has the ability to read a logical file directly from a landingzone using 'FILE::<ip>' file syntax, but I don't think it is used very frequently.

How does this map to a containerized system? I think the same basic operations are likely to be useful. a) In most scenarios Landing zones are likely to be replaced with (blob) storage accounts. But for security reasons these are likely to remain distinct from the main location used by HPCC to store datasets. (The customer will have only access keys to copy files to and from those storage accounts.) The containerized system has a way for ECL to directly read from a blob storage account ('PLANE::<plane'), but I imagine users will still want to copy the files in many situations to control the lifetime of the copies etc. b) We still need a way to convert from utf16 to utf8, or extend the platform to allow utf16 to be read directly. c) This is still equally useful, allowing a set of files to be stored as a single file in a form that is easy for ECL to process. d) Important for copying data from an existing bare metal system to the cloud, and from a cloud system back to a bare metal system. e) Useful for exporting results to customers f+g) Essentially the same thing in the cloud world. It might still be useful to have h) I suspect we will need to map this to cloud-specific apis. i+j) Just as applicable in the container world.

Broadly, landing zones in bare metal map to special storage planes in containerized, and groups also map to more general storage planes.

There are a couple of complications connected with the implementation:

  1. Copying is currently done by starting an ftslave process on either the source or the target nodes. In the container world there is no local node, and I think we would prefer not to start a process in order to copy each file. 2) Copying between storage groups should be done using the cloud provider api, rather than transferring data via a k8s job.

Suggestions:

  • Have a load balanced dafilesrv which supports multiple replicas. It would have a secure external service, and an internal service for trusted components.
  • Move the ftslave logic into dafilesrv. Move the current code for ftslave actions into dafilesrv with new operations.
  • When copying from/to a bare metal system the requests are sent to the dafilesrv for the node that currently runs ftslave. For a container system the requests are sent to the loadbalanced service.
  • It might be possible to migrate to lamda style functions for some of the work...
  • A later optimization would use a cloud service where it was possible.
  • When local split points are supported it may be better to spray a file 1:1 along with partition information. Even without local split points it may still be better to spray a file 1:1 (cheaper).
  • What are the spray targets? It may need to be storage plane + number of parts, rather than a target cluster. The default number of parts is the #devices on the storage plane.

=> Milestones a) Move ftslave code to dafilesrv (partition, pull, push) [Should be included in 7.12.x stream to allow remote read compatibility?] b) Create a dafilesrv component to the helm charts, with internal and external services. c) use storage planes to determine how files are sprayed etc. (bare-metal, #devices) Adapt dfu/fileservices calls to take (storageplane,number) instead of cluster. There should already be a 1:1 mapping from existing cluster to storage planes in a bare-metal system, so this may not involve much work. [May also need a flag to indicate if ._1_of_1 is appended?] d) Select correct dafilesrv for bare-metal storage planes, or load balanced service for other. (May need to think through how remote files are represented.)

=> Can import from a bare metal system or a containerized system using command line??

: NOTE: Bare-metal to containerized will likely need push operations on the bare-metal system. (And therefore serialized security information) This may still cause issues since it is unlikely containerized will be able to pull from bare-metal. Pushing, but not creating a logical file entry on the containerized system should be easier since it can use a local storage plane definition.

e) Switch over to using the esp based meta information, so that it can include details of storage planes and secrets.

: [Note this would also need to be in 7.12.x to allow remote export to containerized, that may well be a step too far]

f) Add option to configure the number of file parts for spray/copy/despray g) Ensure that eclwatch picks up the list of storage planes (and the default number of file parts), and has ability to specify #parts.

Later: h) plan how cloud-services can be used for some of the copies i) investigate using serverless functions to calculate split points. j) Use refactored disk read/write interfaces to clean up read and copy code. k) we may not want to expose access keys to allow remote reads/writes - in which they would need to be pushed from a bare-metal dafilesrv to a containerized dafilesrv.

Other dependencies: * Refactored file meta information. If this is switching to being plane based, then the meta information should also be plane based. Main difference is not including the path in the meta information (can just be ignored) * esp service for getting file information. When reading remotely it needs to go via this now...

`,80)]))}const m=t(r,[["render",s]]);export{f as __pageData,m as default}; diff --git a/assets/devdoc_README.md.D15419X_.js b/assets/devdoc_README.md.D15419X_.js new file mode 100644 index 00000000000..41f34472bd1 --- /dev/null +++ b/assets/devdoc_README.md.D15419X_.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as i,o}from"./chunks/framework.DkhCEVKm.js";const c=JSON.parse('{"title":"Developer Documentation","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/README.md","filePath":"devdoc/README.md","lastUpdated":1731340314000}'),r={name:"devdoc/README.md"};function n(l,e,s,d,m,u){return o(),a("div",null,e[0]||(e[0]=[i('

Developer Documentation

This directory contains the documentation specifically targeted at developers of the HPCC system.

TIP

These documents are generated from Markdown by VitePress. See VitePress Markdown for more details.

General documentation

Implementation details for different parts of the system

  • Workunit Workflow: An explanation of workunits, and a walk-through of the steps in executing a query.
  • Code Generator: Details of the internals of eclcc.
  • Roxie: History and design details for roxie.
  • Memory Manager: Details of the memory manager (roxiemem) used by the query engines.
  • Metrics: Metrics Framework Design.

Other documentation

The ECL language is documented in the ecl language reference manual (generated as ECLLanguageReference-<version>.pdf).

',9)]))}const f=t(r,[["render",n]]);export{c as __pageData,f as default}; diff --git a/assets/devdoc_README.md.D15419X_.lean.js b/assets/devdoc_README.md.D15419X_.lean.js new file mode 100644 index 00000000000..41f34472bd1 --- /dev/null +++ b/assets/devdoc_README.md.D15419X_.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as i,o}from"./chunks/framework.DkhCEVKm.js";const c=JSON.parse('{"title":"Developer Documentation","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/README.md","filePath":"devdoc/README.md","lastUpdated":1731340314000}'),r={name:"devdoc/README.md"};function n(l,e,s,d,m,u){return o(),a("div",null,e[0]||(e[0]=[i('

Developer Documentation

This directory contains the documentation specifically targeted at developers of the HPCC system.

TIP

These documents are generated from Markdown by VitePress. See VitePress Markdown for more details.

General documentation

Implementation details for different parts of the system

  • Workunit Workflow: An explanation of workunits, and a walk-through of the steps in executing a query.
  • Code Generator: Details of the internals of eclcc.
  • Roxie: History and design details for roxie.
  • Memory Manager: Details of the memory manager (roxiemem) used by the query engines.
  • Metrics: Metrics Framework Design.

Other documentation

The ECL language is documented in the ecl language reference manual (generated as ECLLanguageReference-<version>.pdf).

',9)]))}const f=t(r,[["render",n]]);export{c as __pageData,f as default}; diff --git a/assets/devdoc_SecurityConfig.md.C2-pRa18.js b/assets/devdoc_SecurityConfig.md.C2-pRa18.js new file mode 100644 index 00000000000..3dbc504c3d4 --- /dev/null +++ b/assets/devdoc_SecurityConfig.md.C2-pRa18.js @@ -0,0 +1 @@ +import{_ as t,c as r,a3 as a,o}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Security Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/SecurityConfig.md","filePath":"devdoc/SecurityConfig.md","lastUpdated":1731340314000}'),n={name:"devdoc/SecurityConfig.md"};function d(s,e,i,u,c,l){return o(),r("div",null,e[0]||(e[0]=[a('

Security Configuration

This document covers security configuration values and meanings. It does not serve as the source for how to configure security, but rather what the different values mean. These are not covered in the docs nor does any reasonable help information exist in the config manager or yaml files.

Supported Configurations

Security is configured either through an LDAP server or a plugin. Additionally, these are supported in both legacy deployments that use environment.xml and containerized deployments using Kubernetes and Helm charts. While these methods differ, the configuration values remain the same. Focus is placed on the different values and not the deployment target. Differences based on deployment can be found in the relevant platform documents.

Security Managers

Security is implemented via a security manager interface. Managers are loaded and used by components within the system to check authorization and authentication. LDAP is an exception to the loadable manager model. It is not a compliant loadable module like other security plugins. For that reason, the configuration for each is separated into two sections below: LDAP and Plugin Security Managers.

LDAP

LDAP is a protocol that connects to an Active Directory server (AD). The term LDAP is used interchangeably with AD. Below are the configuration values for an LDAP connection. These are valid for both legacy (environment.xml) and containerized deployments. For legacy deployments the configuration manager is the primary vehicle for setting these values. However, some values are not available through the tool and must be set manually in the environment.xml if needed for a legacy deployment.

In containerized environments, a LDAP configuration block is required for each component. Currently, this results in a verbose configuration where much of the information is repeated.

LDAP is capable if handling user authentication and feature access authorization (such as filescopes).

ValueExampleMeaning
adminGroupNameHPCCAdminsGroup name containing admin users for the AD
cacheTimeout60Timeout in minutes to keep cached security data
ldapCipherSuiteN/AUsed when AD is not up to date with latest SSL libs.
AD admin must provide
ldapPort389 (default)Insecure port
ldapSecurePort636 (default)Secure port over TLS
ldapProtocolldapldap for insecure (default), using ldapPort
ldaps for secure using ldapSecurePort
ldapTimeoutSec60 (default 5 for debug, 60 otherwise)Connection timeout to an AD before rollint to next AD
serverTypeActiveDirectoryIdentifies the type of AD server. (2)
filesBasednou=files,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where filescopes are stored
groupsBasednou=groups,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where groups are stored
modulesBaseDnou=modules,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where permissions for resource are stored (1)
systemBasednOU=AADDC Users,DC=z0lpf,DC=onmicrosoft,DC=comDN where the system user is stored
usersBasednOU=AADDC Users,DC=z0lpf,DC=onmicrosoft,DC=comDN where users are stored (3)
systemUserhpccAdminAppears to only be used for IPlanet type ADs, but may still be required
systemCommonNamehpccAdminAD username of user to proxy all AD operations
systemPasswordSystem user passwordAD user password
ldapAdminSecretKeynoneKey for Kubernetes secrets (4) (5)
ldapAdminVaultIdnoneVault ID used to load system username and password (5)
ldapDomainnoneAppears to be a comma separated version of the AD domain name components (5)
ldapAddress192.168.10.42IP address to the AD
commonBasednDC=z0lpf,DC=onmicrosoft,DC=comOverrides the domain retrieved from the AD for the system user (5)
templateNamenoneTemplate used when adding resources (5)
authMethodnoneNot sure yet

Notes:

  1. modulesBaseDn is the same as resourcesBaseDn The code looks for first for modulesBaseDn and if not found will search for resourcesBaseDn
  2. Allowed values for serverType are ActiveDirectory, AzureActiveDirectory, 389DirectoryServer, OpenLDAP, Fedora389
  3. For AzureAD, users are managed from the AD dashboard, not via ECLWatch or through LDAP
  4. If present, ldapAdminVaultId is read and systemCommonName and systemPassword are read from the Kubernetes secrets store and not from the LDAP config values
  5. Must be configured manually in the environment.xml in legacy environments

Plugin Security Managers

Plugin security managers are separate shared objects loaded and initialized by the system. The manager interface is passed to components in order to provide necessary security functions. Each plugin has its own configuration. HPCC components can be configured to use a plugin as needed.

httpasswd Security Manager

See documentation for the settings and how to enable.

Single User Security Manager

To be added.

JWT Security Manager

To be added

',21)]))}const p=t(n,[["render",d]]);export{h as __pageData,p as default}; diff --git a/assets/devdoc_SecurityConfig.md.C2-pRa18.lean.js b/assets/devdoc_SecurityConfig.md.C2-pRa18.lean.js new file mode 100644 index 00000000000..3dbc504c3d4 --- /dev/null +++ b/assets/devdoc_SecurityConfig.md.C2-pRa18.lean.js @@ -0,0 +1 @@ +import{_ as t,c as r,a3 as a,o}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Security Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/SecurityConfig.md","filePath":"devdoc/SecurityConfig.md","lastUpdated":1731340314000}'),n={name:"devdoc/SecurityConfig.md"};function d(s,e,i,u,c,l){return o(),r("div",null,e[0]||(e[0]=[a('

Security Configuration

This document covers security configuration values and meanings. It does not serve as the source for how to configure security, but rather what the different values mean. These are not covered in the docs nor does any reasonable help information exist in the config manager or yaml files.

Supported Configurations

Security is configured either through an LDAP server or a plugin. Additionally, these are supported in both legacy deployments that use environment.xml and containerized deployments using Kubernetes and Helm charts. While these methods differ, the configuration values remain the same. Focus is placed on the different values and not the deployment target. Differences based on deployment can be found in the relevant platform documents.

Security Managers

Security is implemented via a security manager interface. Managers are loaded and used by components within the system to check authorization and authentication. LDAP is an exception to the loadable manager model. It is not a compliant loadable module like other security plugins. For that reason, the configuration for each is separated into two sections below: LDAP and Plugin Security Managers.

LDAP

LDAP is a protocol that connects to an Active Directory server (AD). The term LDAP is used interchangeably with AD. Below are the configuration values for an LDAP connection. These are valid for both legacy (environment.xml) and containerized deployments. For legacy deployments the configuration manager is the primary vehicle for setting these values. However, some values are not available through the tool and must be set manually in the environment.xml if needed for a legacy deployment.

In containerized environments, a LDAP configuration block is required for each component. Currently, this results in a verbose configuration where much of the information is repeated.

LDAP is capable if handling user authentication and feature access authorization (such as filescopes).

ValueExampleMeaning
adminGroupNameHPCCAdminsGroup name containing admin users for the AD
cacheTimeout60Timeout in minutes to keep cached security data
ldapCipherSuiteN/AUsed when AD is not up to date with latest SSL libs.
AD admin must provide
ldapPort389 (default)Insecure port
ldapSecurePort636 (default)Secure port over TLS
ldapProtocolldapldap for insecure (default), using ldapPort
ldaps for secure using ldapSecurePort
ldapTimeoutSec60 (default 5 for debug, 60 otherwise)Connection timeout to an AD before rollint to next AD
serverTypeActiveDirectoryIdentifies the type of AD server. (2)
filesBasednou=files,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where filescopes are stored
groupsBasednou=groups,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where groups are stored
modulesBaseDnou=modules,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where permissions for resource are stored (1)
systemBasednOU=AADDC Users,DC=z0lpf,DC=onmicrosoft,DC=comDN where the system user is stored
usersBasednOU=AADDC Users,DC=z0lpf,DC=onmicrosoft,DC=comDN where users are stored (3)
systemUserhpccAdminAppears to only be used for IPlanet type ADs, but may still be required
systemCommonNamehpccAdminAD username of user to proxy all AD operations
systemPasswordSystem user passwordAD user password
ldapAdminSecretKeynoneKey for Kubernetes secrets (4) (5)
ldapAdminVaultIdnoneVault ID used to load system username and password (5)
ldapDomainnoneAppears to be a comma separated version of the AD domain name components (5)
ldapAddress192.168.10.42IP address to the AD
commonBasednDC=z0lpf,DC=onmicrosoft,DC=comOverrides the domain retrieved from the AD for the system user (5)
templateNamenoneTemplate used when adding resources (5)
authMethodnoneNot sure yet

Notes:

  1. modulesBaseDn is the same as resourcesBaseDn The code looks for first for modulesBaseDn and if not found will search for resourcesBaseDn
  2. Allowed values for serverType are ActiveDirectory, AzureActiveDirectory, 389DirectoryServer, OpenLDAP, Fedora389
  3. For AzureAD, users are managed from the AD dashboard, not via ECLWatch or through LDAP
  4. If present, ldapAdminVaultId is read and systemCommonName and systemPassword are read from the Kubernetes secrets store and not from the LDAP config values
  5. Must be configured manually in the environment.xml in legacy environments

Plugin Security Managers

Plugin security managers are separate shared objects loaded and initialized by the system. The manager interface is passed to components in order to provide necessary security functions. Each plugin has its own configuration. HPCC components can be configured to use a plugin as needed.

httpasswd Security Manager

See documentation for the settings and how to enable.

Single User Security Manager

To be added.

JWT Security Manager

To be added

',21)]))}const p=t(n,[["render",d]]);export{h as __pageData,p as default}; diff --git a/assets/devdoc_SecurityUserAuthentication.md.DbBHZTGf.js b/assets/devdoc_SecurityUserAuthentication.md.DbBHZTGf.js new file mode 100644 index 00000000000..30f445cdd01 --- /dev/null +++ b/assets/devdoc_SecurityUserAuthentication.md.DbBHZTGf.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as i,o as s}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"User Authentication","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/SecurityUserAuthentication.md","filePath":"devdoc/SecurityUserAuthentication.md","lastUpdated":1731340314000}'),r={name:"devdoc/SecurityUserAuthentication.md"};function n(o,e,h,c,u,d){return s(),a("div",null,e[0]||(e[0]=[i('

User Authentication

This document covers user authentication, the process of verifying the identity of the user. Authorization is a separate topic covering whether a user should be allowed to perform a specific operation of access a specific resource.

Each supported security manager is covered.

Generally, when authentication is needed, the security manager client should call the ISecManagerauthenticateUser method. The method also allows the caller to detect if the user being authenticated is a superuser. Use of that feature is beyond the scope of this document. In practice, this method is rarely if ever called. User authentication is generally performed as part of authorization. This is covered in more detail below.

Security Manager User Authentication

This section covers how each supported security manager handles user authentication. As stated above, the method authenticateUser is defined for this purpose. However, other methods also perform user authentication. The sections that follow describe in general how each security manager performs user authentication, whether from directly calling the authenticateUser method, or as an ancillary action taken when another method is called.

LDAP

The LDAP security manager uses the configured Active Directory to authenticate users. Once authenticated, the user is added to the permissions cache, if enabled, to prevent repeated trips to the AD whenever an authentication check is required.

If caching is enabled, a lookup is done to see if the user is already cached. If so, the cached user authentication status is returned. Note that the cached status remains until either the cache time to live expires or is cleared either manually or through some other programmatic action.

If caching is not enabled, a request is sent to the AD to validate the user credentials.

In either case, if digital signatures are configured, the user is also digitally signed using the username. Digitally signing the user allows for quick authentication by validating the signature against the username. During initial authentication, if the digital signature exists, it is verified to provide a fast way to authenticate the user. If the signature is not verified, the user is marked as not authenticated.

Authentication status is stored in the security user object so that further checks are not necessary when the same user object is used in multiple calls to the security manager.

HTPasswd

Authentication in the htpasswd manager does not support singularly authenticating the user without also authorizing resource access. See the special case for authentication with authorization below.

Regardless, the htpasswd manager authenticates users using the .htpasswd file that is installed on the cluster. It does so by finding the user in the file and verifying that the input hashed password matches the stored hashed password in file.

Single User

The single user security manager allows the definition of a single username with a password. The values are set in the environment configuration and are read during the initialization of the manager. All authentication requests validate against the configured username and password. The process is a simple comparison. Note that the password stored in the environment is hashed.

User Authentication During Authorization

Since resource access authorization requires an authenticated user, the authorization process also authenticates the user before checking authorization. There are a couple of advantages to this

  • Two separate calls are not required to check authorization (one to verify the user and one to check authorization)
  • The caller can perform third party authorization for specific user access

The authenticate method, or any of its overloads or derivatives, accepts a resource or resource list and a user. These methods authenticate the user first before checking access to the specified resource.

ECL Watch uses user authentication during authorization during its log in process. Instead of first authenticating the user, it calls an authenticate method passing both the user and the necessary resources for which the user must have access in order to log into ECL Watch.

',22)]))}const m=t(r,[["render",n]]);export{p as __pageData,m as default}; diff --git a/assets/devdoc_SecurityUserAuthentication.md.DbBHZTGf.lean.js b/assets/devdoc_SecurityUserAuthentication.md.DbBHZTGf.lean.js new file mode 100644 index 00000000000..30f445cdd01 --- /dev/null +++ b/assets/devdoc_SecurityUserAuthentication.md.DbBHZTGf.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as i,o as s}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"User Authentication","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/SecurityUserAuthentication.md","filePath":"devdoc/SecurityUserAuthentication.md","lastUpdated":1731340314000}'),r={name:"devdoc/SecurityUserAuthentication.md"};function n(o,e,h,c,u,d){return s(),a("div",null,e[0]||(e[0]=[i('

User Authentication

This document covers user authentication, the process of verifying the identity of the user. Authorization is a separate topic covering whether a user should be allowed to perform a specific operation of access a specific resource.

Each supported security manager is covered.

Generally, when authentication is needed, the security manager client should call the ISecManagerauthenticateUser method. The method also allows the caller to detect if the user being authenticated is a superuser. Use of that feature is beyond the scope of this document. In practice, this method is rarely if ever called. User authentication is generally performed as part of authorization. This is covered in more detail below.

Security Manager User Authentication

This section covers how each supported security manager handles user authentication. As stated above, the method authenticateUser is defined for this purpose. However, other methods also perform user authentication. The sections that follow describe in general how each security manager performs user authentication, whether from directly calling the authenticateUser method, or as an ancillary action taken when another method is called.

LDAP

The LDAP security manager uses the configured Active Directory to authenticate users. Once authenticated, the user is added to the permissions cache, if enabled, to prevent repeated trips to the AD whenever an authentication check is required.

If caching is enabled, a lookup is done to see if the user is already cached. If so, the cached user authentication status is returned. Note that the cached status remains until either the cache time to live expires or is cleared either manually or through some other programmatic action.

If caching is not enabled, a request is sent to the AD to validate the user credentials.

In either case, if digital signatures are configured, the user is also digitally signed using the username. Digitally signing the user allows for quick authentication by validating the signature against the username. During initial authentication, if the digital signature exists, it is verified to provide a fast way to authenticate the user. If the signature is not verified, the user is marked as not authenticated.

Authentication status is stored in the security user object so that further checks are not necessary when the same user object is used in multiple calls to the security manager.

HTPasswd

Authentication in the htpasswd manager does not support singularly authenticating the user without also authorizing resource access. See the special case for authentication with authorization below.

Regardless, the htpasswd manager authenticates users using the .htpasswd file that is installed on the cluster. It does so by finding the user in the file and verifying that the input hashed password matches the stored hashed password in file.

Single User

The single user security manager allows the definition of a single username with a password. The values are set in the environment configuration and are read during the initialization of the manager. All authentication requests validate against the configured username and password. The process is a simple comparison. Note that the password stored in the environment is hashed.

User Authentication During Authorization

Since resource access authorization requires an authenticated user, the authorization process also authenticates the user before checking authorization. There are a couple of advantages to this

  • Two separate calls are not required to check authorization (one to verify the user and one to check authorization)
  • The caller can perform third party authorization for specific user access

The authenticate method, or any of its overloads or derivatives, accepts a resource or resource list and a user. These methods authenticate the user first before checking access to the specified resource.

ECL Watch uses user authentication during authorization during its log in process. Instead of first authenticating the user, it calls an authenticate method passing both the user and the necessary resources for which the user must have access in order to log into ECL Watch.

',22)]))}const m=t(r,[["render",n]]);export{p as __pageData,m as default}; diff --git a/assets/devdoc_StyleGuide.md.6RHWelMX.js b/assets/devdoc_StyleGuide.md.6RHWelMX.js new file mode 100644 index 00000000000..16271d18871 --- /dev/null +++ b/assets/devdoc_StyleGuide.md.6RHWelMX.js @@ -0,0 +1,51 @@ +import{_ as e,c as i,a3 as a,o as t}from"./chunks/framework.DkhCEVKm.js";const k=JSON.parse('{"title":"Coding conventions","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/StyleGuide.md","filePath":"devdoc/StyleGuide.md","lastUpdated":1731340314000}'),n={name:"devdoc/StyleGuide.md"};function l(o,s,h,r,p,d){return t(),i("div",null,s[0]||(s[0]=[a(`

Coding conventions

Why coding conventions?

Everyone has their own ideas of what the best code formatting style is, but most would agree that code in a mixture of styles is the worst of all worlds. A consistent coding style makes unfamiliar code easier to understand and navigate.

In an ideal world, the HPCC sources would adhere to the coding standards described perfectly. In reality, there are many places that do not. These are being cleaned up as and when we find time.

C++ coding conventions

Unlike most software projects around, HPCC has some very specific constraints that makes most basic design decisions difficult, and often the results are odd to developers getting acquainted with its code base. For example, when HPCC was initially developed, most common-place libraries we have today (like STL and Boost) weren't available or stable enough at the time.

Also, at the beginning, both C++ and Java were being considered as the language of choice, but development started with C++. So a C++ library that copied most behaviour of the Java standard library (At the time, Java 1.4) was created (see jlib below) to make the transition, if ever taken, easier. The transition never happened, but the decisions were taken and the whole platform is designed on those terms.

Most importantly, the performance constraints in HPCC can make no-brainer decisions look impossible in HPCC. One example is the use of traditional smart pointers implementations (such as boost::shared_ptr or C++'s auto_ptr), that can lead to up to 20% performance hit if used instead of our internal shared pointer implementation.

The last important point to consider is that some libraries/systems were designed to replace older ones but haven't got replaced yet. There is a slow movement to deprecate old systems in favour of consolidating a few ones as the elected official ways to use HPCC (Thor, Roxie) but old systems still could be used for years in tests or legacy sub-systems.

In a nutshell, expect re-implementation of well-known containers and algorithms, expect duplicated functionality of sub-systems and expect to be required to use less-friendly libraries for the sake of performance, stability and longevity.

For the most part out coding style conventions match those described at http://geosoft.no/development/cppstyle.html, with a few exceptions or extensions as noted below.

Source files

We use the extension .cpp for C++ source files, and .h or .hpp for header files. Header files with the .hpp extension should be used for headers that are internal to a single library, while header files with the .h extension should be used for the interface that the library exposes. There will typically be one .h file per library, and one .hpp file per cpp file.

Source file names within a single shared library should share a common prefix to aid in identifying where they belong.

Header files with extension .ipp (i for internal) and .tpp (t for template) will be phased out in favour of the scheme described above.

Java-style

We adopted a Java-like inheritance model, with macro substitution for the basic Java keywords. This changes nothing on the code, but make it clearer for the reader on what's the recipient of the inheritance doing with it's base.

  • interface (struct): declares an interface (pure virtual class)
  • extends (public): One interface extending another, both are pure virtual
  • implements (public): Concrete class implementing an interface

There is no semantic check, which makes it difficult to enforce such scheme, which has led to code not using it intermixed with code using it. You should use it when possible, most importantly on code that already uses it.

We also tend to write methods inline, which matches well with C++ Templates requirements. We, however, do not enforce the one-class-per-file rule.

See the Interfaces section for more information on our implementation of interfaces.

Identifiers

Class and interface names are in CamelCase with a leading capital letter. Interface names should be prefixed capital I followed by another capital. Class names may be prefixed with a C if there is a corresponding I-prefixed interface name, e.g. when the interface is primarily used to create an opaque type, but need not be otherwise.

Variables, function and method names, and parameters use camelCase starting with a lower case letter. Parameters may be prefixed with underscore when the parameter is used to initialize a member variable of the same name. Common cases are constructors and setter methods.

Example:

cpp
class MySQLSuperClass
+{
+    bool haslocalcopy = false;
+    void mySQLFunctionIsCool(int _haslocalcopy, bool enablewrite)
+    {
+        if (enablewrite)
+            haslocalcopy = _haslocalcopy;
+    }
+};

Pointers

Use real pointers when you can, and smart pointers when you have to. Take extra care on understanding the needs of your pointers and their scope. Most programs can afford a few dangling pointers, but a high-performance clustering platform cannot.

Most importantly, use common sense and a lot of thought. Here are a few guidelines:

  • Use real pointers for return values, parameter passing.
  • For .md variables use real pointers if their lifetime is guaranteed to be longer than the function (and no exception is thrown from functions you call), shared pointers otherwise.
  • Use Shared pointers for member variables - unless there is a strong guarantee the object has a longer lifetime.
  • Create Shared<X> with either:
    • Owned<X>: if your new pointer will take ownership of the pointer
    • Linked<X>: if you are sharing the ownership (shared)

Warning: Direct manipulation of the ownership might cause Shared<> pointers to lose the pointers, so subsequent calls to it (like o2->doIt() after o3 gets ownership) will cause segmentation faults.

Refer to [Reference counted objects]{.title-ref} for more information on our smart pointer implementation, Shared<>.

Methods that return pointers to link counted objects, or that use them, should use a common naming standard:

  • Foo * queryFoo() Does not return a linked pointer since lifetime is guaranteed for a set period. Caller should link if it needs to retain it for longer.
  • Foo * getFoo() Returned value is linked and should be assigned to an owned, or returned directly.
  • void setFoo(Foo * x) Generally parameters to functions are assumed to be owned by the caller, the callee needs to link them if they are retained.
  • void setFoo(Foo * ownedX) Some calls do transfer ownership of parameters - the parameter should be named to indicate this. If the function only has a single signficant parameter then sometimes the name of the function indicates the ownership.

Indentation

We use 4 spaces to indent each level. TAB characters should not be used.

The { that starts a new scope and the corresponding } to close it are placed on a new line by themselves, and are not indented. This is sometimes known as the Allman or ANSI style.

Comments

We generally believe in the philosophy that well written code is self-documenting. Comments are also encouraged to describe why something is done, rather than how - which should be clear from the code.

javadoc-formatted comments for classes and interfaces are being added.

Classes

The virtual keyword should be included on the declaration of all virtual functions - including those in derived classes, and the override keyword should be used on all virtual functions in derived classes.

Namespaces

MORE: Update!!!

We do not use namespaces. We probably should, following the Google style guide's guidelines - see http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces

Other

We often pretend we are coding in Java and write all our class members inline.

C++11

Other coding conventions

ECL code

The ECL style guide is published separately.

Javascript, XML, XSL etc

We use the commonly accepted conventions for formatting these files.


Design Patterns

Why Design Patterns?

Consistent use of design patterns helps make the code easy to understand.

Interfaces

While C++ does not have explicit support for interfaces (in the java sense), an abstract class with no data members and all functions pure virtual can be used in the same way.

Interfaces are pure virtual classes. They are similar concepts to Java's interfaces and should be used on public APIs. If you need common code, use policies (see below).

An interface's name must start with an 'I' and the base class for its concrete implementations should start with a 'C' and have the same name, ex:

cpp
CFoo : implements IFoo { };

When an interface has multiple implementations, try to stay as close as possible to this rule. Ex:

cpp
CFooCool : implements IFoo { };
+CFooWarm : implements IFoo { };
+CFooALot : implements IFoo { };

Or, for partial implementation, use something like this:

cpp
CFoo : implements IFoo { };
+CFooCool : public CFoo { };
+CFooWarm : public CFoo { };

Extend current interfaces only on a 'is-a' approach, not to aggregate functionality. Avoid pollution of public interfaces by having only the public methods on the most-base interface in the header, and internal implementation in the source file. Prefer pImpl idiom (pointer-to-implementation) for functionality-only requirements and policy based design for interface requirements.

Example 1: You want to decouple part of the implementation from your class, and this part does not implements the interface your contract requires.:

cpp
interface IFoo
+{
+    virtual void foo()=0;
+};
+
+// Following is implemented in a separate private file...
+class CFoo : implements IFoo
+{
+    MyImpl *pImpl;
+public:
+    virtual void foo() override { pImpl->doSomething(); }
+};

Example2: You want to implement the common part of one (or more) interface(s) in a range of sub-classes.:

cpp
interface ICommon
+{
+    virtual void common()=0;
+};
+interface IFoo : extends ICommon
+{
+    virtual void foo()=0;
+};
+interface IBar : extends ICommon
+{
+    virtual void bar()=0;
+};
+
+template <class IFACE>
+class Base : implements IFACE
+{
+    virtual void common() override { ... };
+}; // Still virtual
+
+class CFoo : public Base<IFoo>
+{
+    void foo() override { 1+1; };
+};
+class CBar : public Base<IBar>
+{
+    void bar() override { 2+2; };
+};

NOTE: Interfaces deliberately do not contain virtual destructors. This is to help ensure that they are never destroyed by calling delete directly.

Reference counted objects

Shared<> is an in-house intrusive smart pointer implementation. It is close to boost's intrusive_ptr. It has two derived implementations: Linked and Owned, which are used to control whether the pointer is linked when a shared pointer is created from a real pointer or not, respectively. Ex:

cpp
Owned<Foo> myFoo = new Foo; // Take owenership of the pointers
+Linked<Foo> anotherFoo = = myFoo; // Shared ownership

Shared<> is thread-safe and uses atomic reference count handled by each object (rather than by the smart pointer itself, like boost's shared_ptr).

This means that, to use Shared<>, your class must implement the Link() and Release() methods - most commonly by extending the CInterfaceOf<> class, or the CInterface class (and using the IMPLEMENT_IINTERFACE macro in the public section of your class declaration).

This interface controls how you Link() and Release() the pointer. This is necessary because in some inner parts of HPCC, the use of a "really smart" smart pointer would add too many links and releases (on temporaries, local variables, members, etc) that could add to a significant performance hit.

The CInterface implementation also include a virtual function beforeDispose() which is called before the object is deleted. This allows resources to be cleanly freed up, with the full class hierarchy (including virtual functions) available even when freeing items in base classes. It is often used for caches that do not cause the objects to be retained.

STL

MORE: This needs documenting

Structure of the HPCC source tree

MORE!

Requiring more work: * namespaces * STL * c++11 * Review all documentation * Better examples for shared

`,84)]))}const g=e(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/devdoc_StyleGuide.md.6RHWelMX.lean.js b/assets/devdoc_StyleGuide.md.6RHWelMX.lean.js new file mode 100644 index 00000000000..16271d18871 --- /dev/null +++ b/assets/devdoc_StyleGuide.md.6RHWelMX.lean.js @@ -0,0 +1,51 @@ +import{_ as e,c as i,a3 as a,o as t}from"./chunks/framework.DkhCEVKm.js";const k=JSON.parse('{"title":"Coding conventions","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/StyleGuide.md","filePath":"devdoc/StyleGuide.md","lastUpdated":1731340314000}'),n={name:"devdoc/StyleGuide.md"};function l(o,s,h,r,p,d){return t(),i("div",null,s[0]||(s[0]=[a(`

Coding conventions

Why coding conventions?

Everyone has their own ideas of what the best code formatting style is, but most would agree that code in a mixture of styles is the worst of all worlds. A consistent coding style makes unfamiliar code easier to understand and navigate.

In an ideal world, the HPCC sources would adhere to the coding standards described perfectly. In reality, there are many places that do not. These are being cleaned up as and when we find time.

C++ coding conventions

Unlike most software projects around, HPCC has some very specific constraints that makes most basic design decisions difficult, and often the results are odd to developers getting acquainted with its code base. For example, when HPCC was initially developed, most common-place libraries we have today (like STL and Boost) weren't available or stable enough at the time.

Also, at the beginning, both C++ and Java were being considered as the language of choice, but development started with C++. So a C++ library that copied most behaviour of the Java standard library (At the time, Java 1.4) was created (see jlib below) to make the transition, if ever taken, easier. The transition never happened, but the decisions were taken and the whole platform is designed on those terms.

Most importantly, the performance constraints in HPCC can make no-brainer decisions look impossible in HPCC. One example is the use of traditional smart pointers implementations (such as boost::shared_ptr or C++'s auto_ptr), that can lead to up to 20% performance hit if used instead of our internal shared pointer implementation.

The last important point to consider is that some libraries/systems were designed to replace older ones but haven't got replaced yet. There is a slow movement to deprecate old systems in favour of consolidating a few ones as the elected official ways to use HPCC (Thor, Roxie) but old systems still could be used for years in tests or legacy sub-systems.

In a nutshell, expect re-implementation of well-known containers and algorithms, expect duplicated functionality of sub-systems and expect to be required to use less-friendly libraries for the sake of performance, stability and longevity.

For the most part out coding style conventions match those described at http://geosoft.no/development/cppstyle.html, with a few exceptions or extensions as noted below.

Source files

We use the extension .cpp for C++ source files, and .h or .hpp for header files. Header files with the .hpp extension should be used for headers that are internal to a single library, while header files with the .h extension should be used for the interface that the library exposes. There will typically be one .h file per library, and one .hpp file per cpp file.

Source file names within a single shared library should share a common prefix to aid in identifying where they belong.

Header files with extension .ipp (i for internal) and .tpp (t for template) will be phased out in favour of the scheme described above.

Java-style

We adopted a Java-like inheritance model, with macro substitution for the basic Java keywords. This changes nothing on the code, but make it clearer for the reader on what's the recipient of the inheritance doing with it's base.

  • interface (struct): declares an interface (pure virtual class)
  • extends (public): One interface extending another, both are pure virtual
  • implements (public): Concrete class implementing an interface

There is no semantic check, which makes it difficult to enforce such scheme, which has led to code not using it intermixed with code using it. You should use it when possible, most importantly on code that already uses it.

We also tend to write methods inline, which matches well with C++ Templates requirements. We, however, do not enforce the one-class-per-file rule.

See the Interfaces section for more information on our implementation of interfaces.

Identifiers

Class and interface names are in CamelCase with a leading capital letter. Interface names should be prefixed capital I followed by another capital. Class names may be prefixed with a C if there is a corresponding I-prefixed interface name, e.g. when the interface is primarily used to create an opaque type, but need not be otherwise.

Variables, function and method names, and parameters use camelCase starting with a lower case letter. Parameters may be prefixed with underscore when the parameter is used to initialize a member variable of the same name. Common cases are constructors and setter methods.

Example:

cpp
class MySQLSuperClass
+{
+    bool haslocalcopy = false;
+    void mySQLFunctionIsCool(int _haslocalcopy, bool enablewrite)
+    {
+        if (enablewrite)
+            haslocalcopy = _haslocalcopy;
+    }
+};

Pointers

Use real pointers when you can, and smart pointers when you have to. Take extra care on understanding the needs of your pointers and their scope. Most programs can afford a few dangling pointers, but a high-performance clustering platform cannot.

Most importantly, use common sense and a lot of thought. Here are a few guidelines:

  • Use real pointers for return values, parameter passing.
  • For .md variables use real pointers if their lifetime is guaranteed to be longer than the function (and no exception is thrown from functions you call), shared pointers otherwise.
  • Use Shared pointers for member variables - unless there is a strong guarantee the object has a longer lifetime.
  • Create Shared<X> with either:
    • Owned<X>: if your new pointer will take ownership of the pointer
    • Linked<X>: if you are sharing the ownership (shared)

Warning: Direct manipulation of the ownership might cause Shared<> pointers to lose the pointers, so subsequent calls to it (like o2->doIt() after o3 gets ownership) will cause segmentation faults.

Refer to [Reference counted objects]{.title-ref} for more information on our smart pointer implementation, Shared<>.

Methods that return pointers to link counted objects, or that use them, should use a common naming standard:

  • Foo * queryFoo() Does not return a linked pointer since lifetime is guaranteed for a set period. Caller should link if it needs to retain it for longer.
  • Foo * getFoo() Returned value is linked and should be assigned to an owned, or returned directly.
  • void setFoo(Foo * x) Generally parameters to functions are assumed to be owned by the caller, the callee needs to link them if they are retained.
  • void setFoo(Foo * ownedX) Some calls do transfer ownership of parameters - the parameter should be named to indicate this. If the function only has a single signficant parameter then sometimes the name of the function indicates the ownership.

Indentation

We use 4 spaces to indent each level. TAB characters should not be used.

The { that starts a new scope and the corresponding } to close it are placed on a new line by themselves, and are not indented. This is sometimes known as the Allman or ANSI style.

Comments

We generally believe in the philosophy that well written code is self-documenting. Comments are also encouraged to describe why something is done, rather than how - which should be clear from the code.

javadoc-formatted comments for classes and interfaces are being added.

Classes

The virtual keyword should be included on the declaration of all virtual functions - including those in derived classes, and the override keyword should be used on all virtual functions in derived classes.

Namespaces

MORE: Update!!!

We do not use namespaces. We probably should, following the Google style guide's guidelines - see http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces

Other

We often pretend we are coding in Java and write all our class members inline.

C++11

Other coding conventions

ECL code

The ECL style guide is published separately.

Javascript, XML, XSL etc

We use the commonly accepted conventions for formatting these files.


Design Patterns

Why Design Patterns?

Consistent use of design patterns helps make the code easy to understand.

Interfaces

While C++ does not have explicit support for interfaces (in the java sense), an abstract class with no data members and all functions pure virtual can be used in the same way.

Interfaces are pure virtual classes. They are similar concepts to Java's interfaces and should be used on public APIs. If you need common code, use policies (see below).

An interface's name must start with an 'I' and the base class for its concrete implementations should start with a 'C' and have the same name, ex:

cpp
CFoo : implements IFoo { };

When an interface has multiple implementations, try to stay as close as possible to this rule. Ex:

cpp
CFooCool : implements IFoo { };
+CFooWarm : implements IFoo { };
+CFooALot : implements IFoo { };

Or, for partial implementation, use something like this:

cpp
CFoo : implements IFoo { };
+CFooCool : public CFoo { };
+CFooWarm : public CFoo { };

Extend current interfaces only on a 'is-a' approach, not to aggregate functionality. Avoid pollution of public interfaces by having only the public methods on the most-base interface in the header, and internal implementation in the source file. Prefer pImpl idiom (pointer-to-implementation) for functionality-only requirements and policy based design for interface requirements.

Example 1: You want to decouple part of the implementation from your class, and this part does not implements the interface your contract requires.:

cpp
interface IFoo
+{
+    virtual void foo()=0;
+};
+
+// Following is implemented in a separate private file...
+class CFoo : implements IFoo
+{
+    MyImpl *pImpl;
+public:
+    virtual void foo() override { pImpl->doSomething(); }
+};

Example2: You want to implement the common part of one (or more) interface(s) in a range of sub-classes.:

cpp
interface ICommon
+{
+    virtual void common()=0;
+};
+interface IFoo : extends ICommon
+{
+    virtual void foo()=0;
+};
+interface IBar : extends ICommon
+{
+    virtual void bar()=0;
+};
+
+template <class IFACE>
+class Base : implements IFACE
+{
+    virtual void common() override { ... };
+}; // Still virtual
+
+class CFoo : public Base<IFoo>
+{
+    void foo() override { 1+1; };
+};
+class CBar : public Base<IBar>
+{
+    void bar() override { 2+2; };
+};

NOTE: Interfaces deliberately do not contain virtual destructors. This is to help ensure that they are never destroyed by calling delete directly.

Reference counted objects

Shared<> is an in-house intrusive smart pointer implementation. It is close to boost's intrusive_ptr. It has two derived implementations: Linked and Owned, which are used to control whether the pointer is linked when a shared pointer is created from a real pointer or not, respectively. Ex:

cpp
Owned<Foo> myFoo = new Foo; // Take owenership of the pointers
+Linked<Foo> anotherFoo = = myFoo; // Shared ownership

Shared<> is thread-safe and uses atomic reference count handled by each object (rather than by the smart pointer itself, like boost's shared_ptr).

This means that, to use Shared<>, your class must implement the Link() and Release() methods - most commonly by extending the CInterfaceOf<> class, or the CInterface class (and using the IMPLEMENT_IINTERFACE macro in the public section of your class declaration).

This interface controls how you Link() and Release() the pointer. This is necessary because in some inner parts of HPCC, the use of a "really smart" smart pointer would add too many links and releases (on temporaries, local variables, members, etc) that could add to a significant performance hit.

The CInterface implementation also include a virtual function beforeDispose() which is called before the object is deleted. This allows resources to be cleanly freed up, with the full class hierarchy (including virtual functions) available even when freeing items in base classes. It is often used for caches that do not cause the objects to be retained.

STL

MORE: This needs documenting

Structure of the HPCC source tree

MORE!

Requiring more work: * namespaces * STL * c++11 * Review all documentation * Better examples for shared

`,84)]))}const g=e(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/devdoc_UserBuildAssets.md.RFQFpFTM.js b/assets/devdoc_UserBuildAssets.md.RFQFpFTM.js new file mode 100644 index 00000000000..2501d958ea6 --- /dev/null +++ b/assets/devdoc_UserBuildAssets.md.RFQFpFTM.js @@ -0,0 +1,34 @@ +import{_ as s,c as a,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const o="/HPCC-Platform/assets/repository-tag-tab.DSfut5r1.png",i="/HPCC-Platform/assets/actions-secrets-and-variables.BfAcH5xn.png",r="/HPCC-Platform/assets/HPCC-12345-build-in-progress.DU0-PikH.png",f=JSON.parse('{"title":"Build Assets for individual developer","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/UserBuildAssets.md","filePath":"devdoc/UserBuildAssets.md","lastUpdated":1731340314000}'),l={name:"devdoc/UserBuildAssets.md"};function p(c,e,d,u,h,g){return n(),a("div",null,e[0]||(e[0]=[t('

Build Assets for individual developer

Build Assets

The modern tool used for generating all our official assets is the Github Actions build-asset workflow on the hpcc-systems/HPCC-Platform repository, located here. Developers and contributors can utilize this same workflow on their own forked repository. This allows developers to quickly create assets for testing changes and test for errors before the peer review process.

Build assets will generate every available project under the HPCC-Platform namespace. There currently is not an option to control which packages in the build matrix get generated. But most packages get built in parallel, and released after the individual matrix job is completed, so there is no waiting on packages you don't need. Exceptions to this are for packages that require other builds to complete, such as the ECLIDE.

Upon completion of each step and matrix job in the workflow, the assets will be output to the repositories tags tab. An example for the hpcc-systems user repository is hpcc-systems/HPCC-Platform/tags.

Tag tab screenshot

Dependent variables

The build assets workflow requires several repository secrets be available on a developers machine in order to run properly. You can access these secrets and variables by going to the settings tab in your forked repository, and then clicking on the Secrets and Variables - Actions drop down under Security on the lefthand side of the settings screen.

Actions secrets and variables

Create a secret by clicking the green New Repository Secret button. The following secrets are needed;

  • LNB_ACTOR - Your Github username
  • LNB_TOKEN - Classic Github token for your user with LN repo access
  • DOCKER_USERNAME - Your docker.io username
  • DOCKER_PASSWORD - Your docker.io password
  • SIGNING_CERTIFICATE - pks12 self signed cert encoded to base64 for windows signing
  • SIGNING_CERTIFICATE_PASSPHRASE - passphrase for pks12 cert
  • SIGNING_SECRET - ssh-keygen private key for signing linux builds
  • SIGN_MODULES_KEYID - email used to generate key
  • SIGN_MODULES_PASSPHRASE - passphrase for private key

Generating the windows signing certificate

To generate the self signed certificate for windows packages, you will need to do the following steps.

  1. Generate a root certificate authority

openssl req -x509 -sha256 -days 365 -nodes -newkey rsa:2048 -subj "/CN=example.com/C=US/L=Boca Raton" -keyout rootCA.key -out rootCA.crt

  1. Create the server secret key

openssl genrsa -out server.key 2048

  1. generate a csr.conf file
cat > csr.conf <<EOF
+[ req ]
+default_bits = 2048
+prompt = no
+default_md = sha256
+req_extensions = req_ext
+distinguished_name = dn
+
+[ dn ]
+C = US
+ST = Florida
+L = Boca Raton
+O = LexisNexis Risk
+OU = HPCCSystems Development
+CN = example.com
+
+[ req_ext ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = example.com
+IP.1 = 127.0.0.1
+
+EOF
  1. Generate Cert Signing Request

openssl req -new -key server.key -out server.csr -config csr.conf

  1. Create cert.conf file
cat > cert.conf <<EOF
+
+authorityKeyIdentifier=keyid,issuer
+basicConstraints=CA:FALSE
+keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = example.com
+
+EOF
  1. Generate SSL cert with self signed CA

openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256 -extfile cert.conf

  1. Use server.crt to generate PKCS12 needed for windows tools

openssl pkcs12 -inkey server.key -in server.crt -export -name "hpcc_sign_cert" -out hpcc_sign_cert.pfx

You will be asked to "enter export password", this will be what goes in the variable SIGNING_CERTIFICATE_PASSPHRASE in Github Actions.

  1. Convert to base64

On linux: base64 hpcc_sign_cert.pfx > hpcc_sign_cert.base64

On MacOS: base64 -i hpcc_sign_cert.pfx -o hpcc_sign_cert.base64

From here you can cat the output of hpcc_sign_cert.base64 and copy the output into the variable SIGNING_CERTIFICATE in Github Actions.

Generating a signing key for linux builds

For linux builds we're going to generate a private key using GnuPG (gpg).

Start the process by entering a terminal and run the command;gpg --full-generate-key

You will be given several options in this process.

For type of key, select RSA and RSA default.

For keysize, enter 4096.

For expiration date, select 0 = key does not expire.

Input your real name.

Input your company email address.

For comment, input something like Github actions key for signing linux builds.

Then it will ask you to enter a passphrase for the key, and confirm the passphrase. Do not leave this blank.

A key should be output and entered into your gpg keychain. Now we need to export the key for use in the github actions secret.

To extract your key run gpg --output private.pgp --armor --export-secret-key <email-address-used>.

Now open private.pgp, copy all, and go to github actions secrets. Paste the output into the secret "SIGNING_SECRET"

Starting a build

The build-asset workflow is kicked off by a tag being pushed to the developers HPCC-Platform repository. Before we push the tag to our HPCC-Platform repository, we will want to have other tags in place if we want LN and ECLIDE builds to function correctly. Suggested tag patterns are community_HPCC-12345-rc1 or HPCC-12345-rc1.

If you choose not to tag the LN and ECLIDE builds, the community builds will generate but errors will be thrown for any build utilizing the LN repository. ECLIDE will not even attempt a build unless you are also successfully building LN due to the dependency scheme we use. The 'Baremetal' builds are designed to generate our clienttools targets for windows-2022 and macos-12 distributions. These jobs contain both the COMMUNITY and LN builds. If the LN build is not tagged, the COMMUNITY section of the job will run, and the assets will be uploaded, but the job will fail when it tries to build LN.

If you choose to precede your Jira number with community_ then you must tag LN with internal_ and ECLIDE with eclide_. Otherwise just use the Jira tag in all three repositories.

Once the LN and ECLIDE repository tags have been created and pushed with the same base branch that your work is based on for the HPCC-Platform, then you are free to push the HPCC-Platform tag which will initiate the build process.

The summary of the build-asset workflow can then be viewed for progress, and individual jobs can be selected to check build outputs. Build Summary HPCC-12345

Asset output

Assets from the workflow will be released into the corresponding tag location, either in the HPCC-Platform repository for all community based builds, or the LN repository for any builds containing proprietary plugins. Simply browse to the releases or tag tab of your repository and select the tag name you just built. The assets will show up there as the build completes. An example of this on the hpcc-systems repository is hpcc-systems/HPCC-Platform/releases.

',54)]))}const m=s(l,[["render",p]]);export{f as __pageData,m as default}; diff --git a/assets/devdoc_UserBuildAssets.md.RFQFpFTM.lean.js b/assets/devdoc_UserBuildAssets.md.RFQFpFTM.lean.js new file mode 100644 index 00000000000..2501d958ea6 --- /dev/null +++ b/assets/devdoc_UserBuildAssets.md.RFQFpFTM.lean.js @@ -0,0 +1,34 @@ +import{_ as s,c as a,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const o="/HPCC-Platform/assets/repository-tag-tab.DSfut5r1.png",i="/HPCC-Platform/assets/actions-secrets-and-variables.BfAcH5xn.png",r="/HPCC-Platform/assets/HPCC-12345-build-in-progress.DU0-PikH.png",f=JSON.parse('{"title":"Build Assets for individual developer","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/UserBuildAssets.md","filePath":"devdoc/UserBuildAssets.md","lastUpdated":1731340314000}'),l={name:"devdoc/UserBuildAssets.md"};function p(c,e,d,u,h,g){return n(),a("div",null,e[0]||(e[0]=[t('

Build Assets for individual developer

Build Assets

The modern tool used for generating all our official assets is the Github Actions build-asset workflow on the hpcc-systems/HPCC-Platform repository, located here. Developers and contributors can utilize this same workflow on their own forked repository. This allows developers to quickly create assets for testing changes and test for errors before the peer review process.

Build assets will generate every available project under the HPCC-Platform namespace. There currently is not an option to control which packages in the build matrix get generated. But most packages get built in parallel, and released after the individual matrix job is completed, so there is no waiting on packages you don't need. Exceptions to this are for packages that require other builds to complete, such as the ECLIDE.

Upon completion of each step and matrix job in the workflow, the assets will be output to the repositories tags tab. An example for the hpcc-systems user repository is hpcc-systems/HPCC-Platform/tags.

Tag tab screenshot

Dependent variables

The build assets workflow requires several repository secrets be available on a developers machine in order to run properly. You can access these secrets and variables by going to the settings tab in your forked repository, and then clicking on the Secrets and Variables - Actions drop down under Security on the lefthand side of the settings screen.

Actions secrets and variables

Create a secret by clicking the green New Repository Secret button. The following secrets are needed;

  • LNB_ACTOR - Your Github username
  • LNB_TOKEN - Classic Github token for your user with LN repo access
  • DOCKER_USERNAME - Your docker.io username
  • DOCKER_PASSWORD - Your docker.io password
  • SIGNING_CERTIFICATE - pks12 self signed cert encoded to base64 for windows signing
  • SIGNING_CERTIFICATE_PASSPHRASE - passphrase for pks12 cert
  • SIGNING_SECRET - ssh-keygen private key for signing linux builds
  • SIGN_MODULES_KEYID - email used to generate key
  • SIGN_MODULES_PASSPHRASE - passphrase for private key

Generating the windows signing certificate

To generate the self signed certificate for windows packages, you will need to do the following steps.

  1. Generate a root certificate authority

openssl req -x509 -sha256 -days 365 -nodes -newkey rsa:2048 -subj "/CN=example.com/C=US/L=Boca Raton" -keyout rootCA.key -out rootCA.crt

  1. Create the server secret key

openssl genrsa -out server.key 2048

  1. generate a csr.conf file
cat > csr.conf <<EOF
+[ req ]
+default_bits = 2048
+prompt = no
+default_md = sha256
+req_extensions = req_ext
+distinguished_name = dn
+
+[ dn ]
+C = US
+ST = Florida
+L = Boca Raton
+O = LexisNexis Risk
+OU = HPCCSystems Development
+CN = example.com
+
+[ req_ext ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = example.com
+IP.1 = 127.0.0.1
+
+EOF
  1. Generate Cert Signing Request

openssl req -new -key server.key -out server.csr -config csr.conf

  1. Create cert.conf file
cat > cert.conf <<EOF
+
+authorityKeyIdentifier=keyid,issuer
+basicConstraints=CA:FALSE
+keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = example.com
+
+EOF
  1. Generate SSL cert with self signed CA

openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256 -extfile cert.conf

  1. Use server.crt to generate PKCS12 needed for windows tools

openssl pkcs12 -inkey server.key -in server.crt -export -name "hpcc_sign_cert" -out hpcc_sign_cert.pfx

You will be asked to "enter export password", this will be what goes in the variable SIGNING_CERTIFICATE_PASSPHRASE in Github Actions.

  1. Convert to base64

On linux: base64 hpcc_sign_cert.pfx > hpcc_sign_cert.base64

On MacOS: base64 -i hpcc_sign_cert.pfx -o hpcc_sign_cert.base64

From here you can cat the output of hpcc_sign_cert.base64 and copy the output into the variable SIGNING_CERTIFICATE in Github Actions.

Generating a signing key for linux builds

For linux builds we're going to generate a private key using GnuPG (gpg).

Start the process by entering a terminal and run the command;gpg --full-generate-key

You will be given several options in this process.

For type of key, select RSA and RSA default.

For keysize, enter 4096.

For expiration date, select 0 = key does not expire.

Input your real name.

Input your company email address.

For comment, input something like Github actions key for signing linux builds.

Then it will ask you to enter a passphrase for the key, and confirm the passphrase. Do not leave this blank.

A key should be output and entered into your gpg keychain. Now we need to export the key for use in the github actions secret.

To extract your key run gpg --output private.pgp --armor --export-secret-key <email-address-used>.

Now open private.pgp, copy all, and go to github actions secrets. Paste the output into the secret "SIGNING_SECRET"

Starting a build

The build-asset workflow is kicked off by a tag being pushed to the developers HPCC-Platform repository. Before we push the tag to our HPCC-Platform repository, we will want to have other tags in place if we want LN and ECLIDE builds to function correctly. Suggested tag patterns are community_HPCC-12345-rc1 or HPCC-12345-rc1.

If you choose not to tag the LN and ECLIDE builds, the community builds will generate but errors will be thrown for any build utilizing the LN repository. ECLIDE will not even attempt a build unless you are also successfully building LN due to the dependency scheme we use. The 'Baremetal' builds are designed to generate our clienttools targets for windows-2022 and macos-12 distributions. These jobs contain both the COMMUNITY and LN builds. If the LN build is not tagged, the COMMUNITY section of the job will run, and the assets will be uploaded, but the job will fail when it tries to build LN.

If you choose to precede your Jira number with community_ then you must tag LN with internal_ and ECLIDE with eclide_. Otherwise just use the Jira tag in all three repositories.

Once the LN and ECLIDE repository tags have been created and pushed with the same base branch that your work is based on for the HPCC-Platform, then you are free to push the HPCC-Platform tag which will initiate the build process.

The summary of the build-asset workflow can then be viewed for progress, and individual jobs can be selected to check build outputs. Build Summary HPCC-12345

Asset output

Assets from the workflow will be released into the corresponding tag location, either in the HPCC-Platform repository for all community based builds, or the LN repository for any builds containing proprietary plugins. Simply browse to the releases or tag tab of your repository and select the tag name you just built. The assets will show up there as the build completes. An example of this on the hpcc-systems repository is hpcc-systems/HPCC-Platform/releases.

',54)]))}const m=s(l,[["render",p]]);export{f as __pageData,m as default}; diff --git a/assets/devdoc_VersionSupport.md.DqUAdEd9.js b/assets/devdoc_VersionSupport.md.DqUAdEd9.js new file mode 100644 index 00000000000..3bd74af2ecb --- /dev/null +++ b/assets/devdoc_VersionSupport.md.DqUAdEd9.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as a,o as s}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"Current versions","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/VersionSupport.md","filePath":"devdoc/VersionSupport.md","lastUpdated":1731340314000}'),o={name:"devdoc/VersionSupport.md"};function r(n,e,l,h,c,u){return s(),i("div",null,e[0]||(e[0]=[a('

Current versions

nameversion
current9.8.x
previous9.6.x
critical9.4.x
security9.2.x

Supported versions

We release a new version of the platform every 3 months. If there are major changes in functionality, or significant backward compatibility issues then it will be tagged as a new major version, otherwise a new minor version. We normally maintain 4 versions of the system, which means that each new release will typically be supported for a year. Once a new major or minor version has been tagged gold it should not have any changes that change the behavior of queries.

Which versions should changes be applied to? The following gives some examples of the types of changes and which version they would be most relevant to target.

"master":

  • New features.
  • Bug fixes that will change the semantics of existing queries or processes.
  • Refactoring.
  • Performance improvements (unless simple and safe)

"<current>":

  • Bug fixes that only change behavior where it previously crashes or had undefined behavior (If well defined but wrong need to have very strong justification to change.)
  • Fixes for race conditions (the behavior was previously indeterminate so less of an argument against it changing)
  • Data corruption fixes - on a case by case basis if they change existing query results.
  • Missing functionality that prevents features from working.
  • Changes for tech-preview work that only effect those who are using it.
  • Regressions.
  • Improvements to logging and error messages (possibly in "previous" if simple and added to help diagnose problems).
  • Occasional simple refactoring that makes up-merging simpler..
  • Changes to improve backward compatibility of new features. (E.g. adding an ignored syntax to the compiler.)
  • Performance improvements - if simple and safe

"<previous>":

  • Simple bug fixes that do not change behavior
  • Simple changes for missing functionality
  • Regressions with simple fixes (but care is needed if it caused a change in behavior)
  • Serious regressions
  • Complex security fixes

"<critical>" fixes only:

  • Simple security fixes
  • Complex security fixes if sufficiently serious

"<security>" fixes only:

  • Serious security fixes

Occasionally earlier branches will be chosen, (e.g. security fixes to even older versions) but they should always be carefully discussed (and documented).

Patches and images

We aim to produce new point releases once a week. The point releases will contain

a) Any changes to the code base for that branch. b) Any security fixes for libraries that are project dependencies. We will upgrade to the latest point release for the library that fixes the security issue. c) For the cloud any security fixes in the base image or the packages installed in that image.

If there are no changes in any of those areas for a particular version then a new point release will not be created.

If you are deploying a system to the cloud you have one of two options

a) Use the images that are automatically built and published as part of the build pipeline. This image is currently based on ubuntu 22.04 and contains the majority of packages users will require.

b) Use your own hardened base image, and install the containerized package that we publish into that image.

Package versions.

We currently generate the following versions of the package and images:

  • debug
  • release with symbols
  • release without symbols.

It is recommended that you deploy the "release with symbols" version to all bare-metal and non-production cloud deployments. The extra symbols allow the system to generate stack backtraces which make it much easier to diagnose problems if they occur. The "release without symbols" version is recommended for Kubernetes production deployments. Deploying a system without symbols reduces the size of the images. This reduces the time it takes Kubernetes to copy the image before provisioning a new node.

',27)]))}const m=t(o,[["render",r]]);export{p as __pageData,m as default}; diff --git a/assets/devdoc_VersionSupport.md.DqUAdEd9.lean.js b/assets/devdoc_VersionSupport.md.DqUAdEd9.lean.js new file mode 100644 index 00000000000..3bd74af2ecb --- /dev/null +++ b/assets/devdoc_VersionSupport.md.DqUAdEd9.lean.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as a,o as s}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"Current versions","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/VersionSupport.md","filePath":"devdoc/VersionSupport.md","lastUpdated":1731340314000}'),o={name:"devdoc/VersionSupport.md"};function r(n,e,l,h,c,u){return s(),i("div",null,e[0]||(e[0]=[a('

Current versions

nameversion
current9.8.x
previous9.6.x
critical9.4.x
security9.2.x

Supported versions

We release a new version of the platform every 3 months. If there are major changes in functionality, or significant backward compatibility issues then it will be tagged as a new major version, otherwise a new minor version. We normally maintain 4 versions of the system, which means that each new release will typically be supported for a year. Once a new major or minor version has been tagged gold it should not have any changes that change the behavior of queries.

Which versions should changes be applied to? The following gives some examples of the types of changes and which version they would be most relevant to target.

"master":

  • New features.
  • Bug fixes that will change the semantics of existing queries or processes.
  • Refactoring.
  • Performance improvements (unless simple and safe)

"<current>":

  • Bug fixes that only change behavior where it previously crashes or had undefined behavior (If well defined but wrong need to have very strong justification to change.)
  • Fixes for race conditions (the behavior was previously indeterminate so less of an argument against it changing)
  • Data corruption fixes - on a case by case basis if they change existing query results.
  • Missing functionality that prevents features from working.
  • Changes for tech-preview work that only effect those who are using it.
  • Regressions.
  • Improvements to logging and error messages (possibly in "previous" if simple and added to help diagnose problems).
  • Occasional simple refactoring that makes up-merging simpler..
  • Changes to improve backward compatibility of new features. (E.g. adding an ignored syntax to the compiler.)
  • Performance improvements - if simple and safe

"<previous>":

  • Simple bug fixes that do not change behavior
  • Simple changes for missing functionality
  • Regressions with simple fixes (but care is needed if it caused a change in behavior)
  • Serious regressions
  • Complex security fixes

"<critical>" fixes only:

  • Simple security fixes
  • Complex security fixes if sufficiently serious

"<security>" fixes only:

  • Serious security fixes

Occasionally earlier branches will be chosen, (e.g. security fixes to even older versions) but they should always be carefully discussed (and documented).

Patches and images

We aim to produce new point releases once a week. The point releases will contain

a) Any changes to the code base for that branch. b) Any security fixes for libraries that are project dependencies. We will upgrade to the latest point release for the library that fixes the security issue. c) For the cloud any security fixes in the base image or the packages installed in that image.

If there are no changes in any of those areas for a particular version then a new point release will not be created.

If you are deploying a system to the cloud you have one of two options

a) Use the images that are automatically built and published as part of the build pipeline. This image is currently based on ubuntu 22.04 and contains the majority of packages users will require.

b) Use your own hardened base image, and install the containerized package that we publish into that image.

Package versions.

We currently generate the following versions of the package and images:

  • debug
  • release with symbols
  • release without symbols.

It is recommended that you deploy the "release with symbols" version to all bare-metal and non-production cloud deployments. The extra symbols allow the system to generate stack backtraces which make it much easier to diagnose problems if they occur. The "release without symbols" version is recommended for Kubernetes production deployments. Deploying a system without symbols reduces the size of the images. This reduces the time it takes Kubernetes to copy the image before provisioning a new node.

',27)]))}const m=t(o,[["render",r]]);export{p as __pageData,m as default}; diff --git a/assets/devdoc_Workunits.md.DJg1TI-D.js b/assets/devdoc_Workunits.md.DJg1TI-D.js new file mode 100644 index 00000000000..b025fe5667f --- /dev/null +++ b/assets/devdoc_Workunits.md.DJg1TI-D.js @@ -0,0 +1,403 @@ +import{_ as i,c as a,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const g=JSON.parse('{"title":"Understanding workunits","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/Workunits.md","filePath":"devdoc/Workunits.md","lastUpdated":1731340314000}'),h={name:"devdoc/Workunits.md"};function e(l,s,k,p,r,E){return n(),a("div",null,s[0]||(s[0]=[t(`

Understanding workunits

Introduction

A workunit contains all the information that the system requires about a query - including the parameters it takes, how to execute it, and how to format the results. Understanding the contents of a workunit is a key step to understanding how the HPCC system fits together. This document begins with an overview of the different elements in a workunit. That is then followed by a walk-through executing a simple query, with a more detailed description of some of the workunit components to show how they all tie together.

Before looking at the contents of a workunit it is important to understand one of the design goals behind the HPCC system. The HPCC system logically splits the code that is needed to execute a query in two. On the one hand there are the algorithms that are used to perform different dataset operations (e.g. sorting, deduping). The same algorithms are used by all the queries that execute on the system. On the other hand there is the meta-data that describes the columns present in the datasets, which columns you need to sort by, and the order of operations required by the query. These are typically different for each query. This "meta-data" includes generated compare functions, classes that describe the record formats, serialized data and graphs.

A workunit only contains data and code relating to the meta data for the query, i.e. "what to do", while the different engines (hthor, roxie, and thor) implement the algorithms - "how to do it". If you look at a workunit for an ECL query that sorts a dataset you will not find code to perform the sort itself in the workunit - you will not even find a call to a sort library function - that logic is contained in the engine that executes the query.

One consequence of this split, which can be initially confusing, is that execution continually passes back and forth between the generated code and the engines. By the end of this document you should have a better understanding of how the generated code is structured and the part it plays in executing a query.

Note the term "Query" is used as a generic term to cover read-only queries (typically run in roxie) and ETL (Extract, Transform, and Load) style queries that create lots of persistent datafiles (typically run in Thor). Also, the term "workunit" is used ambiguously. The dll created from a query is called a workunit (which is static), but "workunit" is also used to describe a single execution of a query (which includes the parameters and results). It should be clear from the context which of these is meant.

Throughout this document "dll" is a generic term used to refer to a dynamically loaded library. These correspond to shared objects in Linux (extension '.so'), dynamic libraries in Max OS X ('.dylib'), and dynamic link libraries in windows ('.dll').

Contents of a workunit

A workunit is generated by the ecl compiler, and consists of a single dll. That dll contains several different elements:

  • Various C++ helper classes, and exported factory methods are used to create instances of those classes.
  • An XML resource containing different pieces of information about a workunit, including workflow and graphs.
  • Other user-defined resources included in the manifest.

A workunit dll contains everything that the engines need to execute the query. When a workunit is executed, key elements of the xml information are cloned from the workunit dll and copied into a database. This is then augmented with other information as the query is executed - e.g. input parameters, results, statistics, etc.. The contents of the workunit are accessed through an "IWorkUnit" interface (defined in common/workunit/workunit.hpp) that hides the implementation details.

(Workunit information is currently stored in the Dali database - one of the components within the HPCC platform. Work is in-progress to allow the bulk of this workunit data to be stored in Cassandra or another third-party database instead.)

How is the workunit used?

The workunit information is used by most of the components in the system. The following is a quick outline:

  • eclcc

    Creates a workunit dll from an ecl query.

  • eclccserver

    Executes eclcc to create a workunit dll, and then clones some of the information into dali to create an active instance, ready to execute.

  • esp

    Uses information in the workunit dll to publish workunits. This includes details of the parameters that the query takes, how they should be formatted, and the results it returns.

  • eclscheduler

    Monitors workunits that are waiting for events, and updates them when those events occur.

  • eclagent/Roxie

    Process the different workflow actions, and workflow code.

  • hThor/Roxie/Thor

    Execute graphs within the workflow items.

  • Dali

    This database is used to store the state of the workunit state.

Example

The following ECL will be used as an example for the rest of the discussion. It is a very simple search that takes a string parameter 'searchName', which is the name of the person to search for, and returns the matching records from an index. It also outputs the word 'Done!' as a separate result.

ecl
STRING searchName := 'Smith' : STORED('searchName');
+nameIndex := INDEX({ STRING40 name, STRING80 address }, 'names');
+results := nameIndex(KEYED(name = searchName));
+OUTPUT(results);
+OUTPUT('Done!');

Extracts from the XML and C++ that are generated from this example will be included in the following discussion.

Workunit Main Elements

This section outlines the different sections in a workunit. This is followed by a walk-through of the stages that take place when a workunit is executed, together with a more detailed explanation of the workunit contents.

Workflow

The workflow is the highest level of control within a workunit. It is used for two related purposes:

  • Scheduling The HPCC system allows ECL code to be executed when certain events occur - e.g. every hour or when files are uploaded to a landing zone. Each piece of ECL code that is triggered by an external event creates a separate workflow action. This allows each of those events to be processed independently.
  • Splitting up queries. There are situations where it is useful to break parts of an ECL query into independent sections. The simplest example is the PERSIST workflow operation, which allows results to be shared between different work units. Each workflow operation creates one (or sometimes more) independent workflow items, which are then connected together.

Each piece of independent ECL is given a unique workflow id (wfid). Often workflow items need to be executed in a particular order, e.g. ensuring a persist exists before using it, which is managed with dependencies between different workflow items.

Our example above generates the following XML entry in the workunit:

xml
<Workflow>
+    <Item .... wfid="1"/>
+    <Item .... wfid="2">
+    <Dependency wfid="1"/>
+    <Schedule/>
+    </Item>
+</Workflow>

This contains two workflow items. The first workflow item (wfid=1) ensures that the stored value has a default value if it has not been supplied. The second item (with wfid=2) is the main code for the query. This has a dependency on the first workflow item because the stored variable needs to be intialised before it is executed.

MyProcess

The generated code contains a class instance that is used for executing the code associated with the workflow items. It is generated at the end of the main C++ module. E.g.:

cpp
struct MyEclProcess : public EclProcess {
+    virtual int perform(IGlobalCodeContext * gctx, unsigned wfid) {
+        ....
+        switch (wfid) {
+            case 1U:
+                ... code for workflow item 1 ...
+            case 2U:
+                ... code for workflow item 2 ...
+            break;
+        }
+        return 2U;
+    }
+};

The main element is a switch statement inside the perform() function that allows the workflow engines to execute the code associated with a particular workflow item.

There is also an associated factory function that is exported from the dll, and is used by the engines to create instances of the class:

cpp
extern "C" ECL_API IEclProcess* createProcess()
+{
+    return new MyEclProcess;
+}

Graph

Most of the work executing a query involves processing dataset operations, which are implemented as a graph of activities. Each graph is represented in the workunit as an xml graph structure (currently it uses the xgmml format). The graph xml includes details of which types of activities are required to be executed, how they are linked together, and any other dependencies.

The graph in our example is particularly simple:

xml
<Graph name="graph1" type="activities">
+    <xgmml>
+    <graph wfid="2">
+    <node id="1">
+    <att>
+        <graph>
+        <att name="rootGraph" value="1"/>
+        <edge id="2_0" source="2" target="3"/>
+        <node id="2" label="Index Read&#10;&apos;names&apos;">
+        ... attributes for activity 2 ...
+        </node>
+        <node id="3" label="Output&#10;Result #1">
+        ... attributes for activity 3 ...
+        </node>
+        </graph>
+    </att>
+    </node>
+    </graph>
+    </xgmml>
+</Graph>

This graph contains a single subgraph (node id=2) that contains two activities - an index read activity and an output result activity. These activities are linked by a single edge (id "2_0"). The details of the contents are covered in the section on executing graphs below.

Generated Activity Helpers

Each activity has a corresponding class instance in the generated code, and a factory function for creating instances of that class:

cpp
struct cAc2 : public CThorIndexReadArg {
+    ... Implementation of the helper for activity #2 ...
+};
+extern "C" ECL_API IHThorArg * fAc2() { return new cAc2; }
+
+struct cAc3 : public CThorWorkUnitWriteArg {
+    ... Implementation of the helper for activity #3 ...
+};
+extern "C" ECL_API IHThorArg * fAc3() { return new cAc3; }

The helper class for an activity implements the interface that is required for that particular kind. (The interfaces are defined in rtl/include/eclhelper.hpp - further details below.)

Other

The are several other items, detailed below, that are logically associated with a workunit. The information may be stored in the workunit dll or in various other location e.g. Dali, Sasha or Cassandra. It is all accessed through the IWorkUnit interface in common/workunit/workunit.hpp that hides the implementation details. For instance information generated at runtime cannot by definition be included in the workunit dll.

Options

Options that are supplied to eclcc via the -f command line option, or the #option statement are included in the <Debug> section of the workunit xml:

xml
<Debug>
+    <addtimingtoworkunit>0</addtimingtoworkunit>
+    <noterecordsizeingraph>1</noterecordsizeingraph>
+    <showmetaingraph>1</showmetaingraph>
+    <showrecordcountingraph>1</showrecordcountingraph>
+    <spanmultiplecpp>0</spanmultiplecpp>
+    <targetclustertype>hthor</targetclustertype>
+</Debug>

Note, the names of workunit options are case insensitive, and converted to lower case.

Input Parameters

Many queries contain input parameters that modify their behaviour. These correspond to STORED definitions in the ECL. Our example contains a single string "searchName", so the workunit contains a single input parameter:

xml
<Variables>
+    <Variable name="searchname">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+        searchname&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;   
+    </SchemaRaw>
+    </Variable>
+</Variables>

The implementation details of the schema information is encapsulated by the IConstWUResult interface in workunit.hpp.

Results

The workunit xml also contains details of each result that the query generates, including a serialized description of the output record format:

xml
<Results>
+    <Result isScalar="0"
+            name="Result 1"
+            recordSizeEntry="mf1"
+            rowLimit="-1"
+            sequence="0">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+    name&#xe000;&#xe004;(&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;address&#xe000;&#xe004;P&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;%&#xe000;&#xe000;&#xe000;{ string40 name, string80 address };&#10;   </SchemaRaw>
+    </Result>
+    <Result name="Result 2" sequence="1">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+    Result_2&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;   </SchemaRaw>
+    </Result>
+</Results>

in our example there are two - the dataset of results and the text string "Done!". The values of the results for a query are associated with the workunit. (They are currently saved in dali, but this may change in version 6.0.)

Timings and Statistics

Any timings generated when compiling the query are included in the workunit dll:

xml
<Statistics>
+    <Statistic c="eclcc"
+            count="1"
+            creator="eclcc"
+            kind="SizePeakMemory"
+            s="compile"
+            scope=">compile"
+            ts="1428933081084000"
+            unit="sz"
+            value="27885568"/>
+</Statistics>

Other statistics and timings created when running the query are stored in the runtime copy of the workunit. (Statistics for graph elements are stored in a different format from global statistics, but the IWorkUnit interface ensures the implementation details are hidden.)

Manifests

It is possible to include other user-defined resources in the workunit dll - e.g. web pages, or dashboard layouts. I have to confess I do not understand them... ??Tony please provide some more information....!

Stages of Execution

Once a workunit has been compiled to a dll it is ready to be executed. Execution can be triggered in different ways, E.g.:

  • The ECL for a query is submitted to esp
    • A workunit entry, containing the ECL, is created in dali and added to an eclccserver queue.
    • An eclccserver instance removes the workunit form the queue, and compiles the ECL to a workunit dll.
    • The dali workunit entry is updated with the information from the workunit dll.
    • The dali workunit is added to the agent execution queue associated with the target cluster.
    • The associated engine (actually agentexec for hThor and Thor) pulls a query form the queue and executes it.
  • A query is submitted and published with a name. Another request is then submitted to execute this previously compiled query.
    • A workunit entry, containing the ECL, is created in dali and added to an eclccserver queue.
    • An eclccserver instance removes the workunit form the queue, and compiles the ECL to a workunit dll.
    • There is a 'query set' for each combination of query name and the target cluster. The new workunit dll is added to the appropriate query set, and marked as the current active implementation.
    • Later, a query that references a named query is submitted to esp.
    • The name and target cluster are mapped via the query set to the active implementation, and a workunit instance is created from the active workunit dll.
    • The workunit is added to a roxie or eclagentexec queue ready to be executed.
    • The associated engine pulls a query form the queue and executes it.
  • A query is compiled as a stand alone executable. The executable is then run.
    • eclcc is executed on the command line without the -shared command line option.
    • The resulting executable is run. The engine used to execute the query depends on the -platform parameter supplied to eclcc.

Most queries create persistent workunits in dali and then update those workunits with results as they are calculated, however for some roxie queries (e.g. in a production system) the execution workunits are transient.

The following walk-through details the main stages executing a query, and the effect each of the query elements has.

Queues

The system uses several inter-process queues to communicate between the different components in the system. These queues are implemented by dali. Components can subscribe to one or more queues, and receive notifications when entries are avaialable.

Some example queues are:

  • <cluster>.eclserver - workunits to be compiled
  • <cluster>.roxie - workunits to execute on roxie
  • <cluster>.thor - graphs to execute on thor
  • <cluster>.eclscheduler - workunits that need to wait for events
  • <cluster>.agent - workunits to be executed on hthor or thor.
  • dfuserver_queue - dfu workunits for sprays/file copies etc.

Workflow

When a workunit is ready to be run, the workflow controls the flow of execution. The workflow engine (defined in common/workunit/workflow.cpp) is responsible for determining which workflow item should be executed next.

The workflow for Thor and hThor jobs is coordinated by eclagent, while roxie includes the workflow engine in its process. The eclscheduler also uses the workflow engine to process events and mark workflow items ready for execution.

eclagent, or roxie calls the createProcess() function from the workunit dll to create an instance of the generated workflow helper class, and passes it to the workflow engine. The workflow engine walks the workflow items to find any items that are ready to be executed (have the state "reqd" - i.e. required). If a required workflow item has dependencies on other child workflow items then those children are executed first. Once all dependencies have executed successfully the parent workflow item is executed. The example has the following workflow entries:

xml
<Workflow>
+    <Item mode="normal"
+        state="null"
+        type="normal"
+        wfid="1"/>
+    <Item mode="normal"
+        state="reqd"
+        type="normal"
+        wfid="2">
+        <Dependency wfid="1"/>
+        <Schedule/>
+    </Item>
+</Workflow>

Item 2 has a state of "reqd", so it should be evaluated now. Item 2 has a dependency on item 1, so that must be evaluated first. This is achieved by calling MyEclProcess::perform() on the object that was previously created from the workunit dll, passing in wfid = 1. That will execute the following code:

cpp
switch (wfid) {
+    case 1U:
+        if (!gctx->isResult("searchname",4294967295U)) {
+            ctx->setResultString("searchname",4294967295U,5U,"Smith");
+        }
+        break;
+    break;
+}

This checks if a value has been provided for the input parameter, and if not assigns a default value of "Smith". The function returns control to the workflow engine. With the dependencies for wfid 2 now satisfied, the generated code for that workflow id is now executed:

cpp
switch (wfid) {
+    case 2U: {
+        ctx->executeGraph("graph1",false,0,NULL);
+        ctx->setResultString(0,1U,5U,"Done!");
+    }
+    break;
+}

Most of the work for this workflow item involves executing graph1 (by calling back into eclagent/roxie). However, the code also directly sets another result. This is fairly typical - the code inside MyProcess::perform often combines evaluating scalar results, executing graphs, and calling functions that cannot (currently) be called inside a graph (e.g. those involving superfile transactions).

Once all of the required workflow items are executed, the workunit is marked as completed. Alternatively, if there are workflow items that are waiting to be triggered by an event, the workunit will be passed to the scheduler, which will keep monitoring for events.

Note that most items in the xml workflow begin in the state WFStateNull. This means that it is valid to execute them, but they haven't been executed yet. Typically, only a few items begin with the state WFStateReqd.

There are various specialised types of workflow items - e.g. sequential, wait, independent, but they all follow the same basic approach of executing dependencies and then executing that particular item.

Most of the interesting work in an ECL query is done within a graph. The call ctx->executeGraph will either execute the graph locally (in the case of hthor and roxie), or add the workunit onto a queue (for Thor). Whichever happens, control will pass to that engine.

Specialised Workflow Items

Each item mode/type can affect the dependency structure of the workflow:

  • Sequential/Ordered

    The workflow structure for sequential and ordered is the same. An item is made to contain all of the actions in the statement. This is achieved by making each action a dependency of this item. The dependencies, and consequently the actions, must be executed in order. An extra item is always inserted before each dependency. This means that if other statements reference the same dependency, it will only be performed once.

  • Persist

    When the persist workflow service is used, two items are created. One item contains the graphs that perform the expression defined in ECL. It also stores the wfid for the second item. The second item is used to determine whether the persist is up to date.

  • Condition (IF)

    The IF function has either 2 or 3 arguments: the expression, the trueresult, and sometimes the falseresult. For each argument, a workflow item is created. These items are stored as dependencies to the condition, in the order stated above.

  • Contingency (SUCCESS/FAILURE)

    When a contingency clause is defined for an attribute, the attribute item stores the wfid of the contingency. If both success and failure are used, then the item will store the wfid of each contingency. The contingency is composed of items, just like the larger query.

  • Recovery

    When a workflow item fails, if it has a recovery clause, the item will be re-executed. The clause contains actions defined by the programmer to remedy the problem. This clause is stored differently to SUCCESS/FAILURE, in that the recovery clause is a dependency of the failed item. In order to stop the recovery clause from being executed like the other dependencies, it is marked with WFStateSkip.

  • Independent

    This specifier is used when a programmer wants to common up code for the query. It prevents the same piece of code from being executed twice in different contexts. To achieve this, an extra item is inserted between the expression and whichever items depend on it. This means that although the attribute can be referenced several times, it will only be executed once.

Graph Execution

All the engines (roxie, hThor, Thor) execute graphs in a very similar way. The main differences are that hThor and Thor execute a sub graph at a time, while roxie executes a complete graph as one. Roxie is also optimized to minimize the overhead of executing a query - since the same query tends to be run multiple times. This means that roxie creates a graph of factory objects and those are then used to create the activities. The core details are the same for each of them though.

Details of the graph structure

First, a recap of the structure of the graph together with the full xml for the graph definition in our example:

xml
<Graph name="graph1" type="activities">
+    <xgmml>
+        <graph wfid="2">
+            <node id="1">
+                <att>
+                    <graph>
+                        <att name="rootGraph" value="1"/>
+                        <edge id="2_0" source="2" target="3"/>
+                        <node id="2" label="Index Read&#10;&apos;names&apos;">
+                            <att name="definition" value="workuniteg1.ecl(3,1)"/>
+                            <att name="name" value="results"/>
+                            <att name="_kind" value="77"/>
+                            <att name="ecl" value="INDEX({ string40 name, string80 address }, &apos;names&apos;, fileposition(false));&#10;FILTER(KEYED(name = STORED(&apos;searchname&apos;)));&#10;"/>
+                            <att name="recordSize" value="120"/>
+                            <att name="predictedCount" value="0..?[disk]"/>
+                            <att name="_fileName" value="names"/>
+                        </node>
+                        <node id="3" label="Output&#10;Result #1">
+                            <att name="definition" value="workuniteg1.ecl(4,1)"/>
+                            <att name="_kind" value="16"/>
+                            <att name="ecl" value="OUTPUT(..., workunit);&#10;"/>
+                            <att name="recordSize" value="120"/>
+                        </node>
+                    </graph>
+                </att>
+            </node>
+        </graph>
+    </xgmml>
+</Graph>

Each graph (e.g. graph1) consists of 1 or more subgraphs (in the example above, node id=1). Each of those subgraphs contains 1 or more activities (node id=2, node id=3).

The xml for each activity might contain the following information:

  • A unique id (e.g. id="2").
  • The "kind" of the activity, e.g. <att name="_kind" value="77"/>. The value is an item from the enum ThorActivityKind in eclhelper.hpp.
  • The ECL that created the activity. E.g. <att name="ecl" value="...">
  • The identifier of the ecl definition. E.g. <att name="name" value="results"/>
  • Location (e.g. file, line number, column) of the original ECL. E.g. <att name="definition" value="workuniteg1.ecl(3,1)"/>
  • Meta information the code generator has deduced about the activity output. Examples include the record size, expected number of rows, sort order etc. E.g. <att name="recordSize" value="120"/>
  • Hints, which are used for fine control of options for a particular activity (e.g,, the number of threads to use while sorting).
  • Record counts and stats once the job has executed. (These are logically associated with the activities in the graph, but stored separately.)

Graphs also contain edges that can take one of 3 forms:

Edges within graphs

: These are used to indicate how the activities are connected. The source activity is used as the input to the target activity. These edges have the following format:

    <edge id="<source-activity-id>_<output-count>" source="<source-activity-id>" target="<target-activity-id">
+
+There is only one edge in our example workunit: \\<edge id="2\\_0"
+source="2" target="3"/\\>.
+

Edges between graphs

: These are used to indicate direct dependencies between activities. For instance there will be an edge connecting the activity that writes a spill file to the activity that reads it. These edges have the following format:

    <edge id="<source-activity-id>_<target-activity-id>" source="<source-subgraph-id>" target="<target-subgraph-id>"
+       <att name="_sourceActivity" value="<source-activity-id>"/>
+       <att name="_targetActivity" value="<target-activity-id>"/>
+    </edge>
+
+Roxie often optimizes spilled datasets and treats these edges as
+equivalent to the edges between activities.
+

Other dependencies.

: These are similar to the edges between graphs, but they are used for values that are used within an activity. For instance one part of the graph may calculate the maximum value of a column, and another activity may filter out all records that do not match that maximum value. The format is the same as the edges between graphs except that the edge contains the following attribute:

    <att name="_dependsOn" value="1"/>
+

Each activity in a graph also has a corresponding helper class instance in the generated code. (The name of the class is "cAc" followed by the activity number, and the exported factory method is "fAc" followed by the activity number.) Each helper class implements a specialised interface (derived from IHThorArg) - the particular interface is determined by the value of the "_kind" attribute for the activity.

The contents of file rtl/include/eclhelper.hpp is key to understanding how the generated code relates to the activities. Each kind of activity requires a helper class that implements a specific interface. The helpers allow the engine to tailor the generalised activity implementation to the the particular instance - e.g. for a filter activity whether a row should be included or excluded. The appendix at the end of this document contains some further information about this file.

The classes in the generated workunits are normally derived from base implementations of those interfaces (which are implemented in rtl/include/eclhelper_base.hpp). This reduces the size of the generated code by providing default implementations for various functions.

For instance the helper for the index read (activity 2) is defined as:

cpp
struct cAc2 : public CThorIndexReadArg {
+    virtual unsigned getFormatCrc() {
+        return 470622073U;
+    }
+    virtual bool getIndexLayout(size32_t & __lenResult, void * & __result) { getLayout5(__lenResult, __result, ctx); return true; }
+    virtual IOutputMetaData * queryDiskRecordSize() { return &mx1; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void onCreate(ICodeContext * _ctx, IHThorArg *, MemoryBuffer * in) {
+        ctx = _ctx;
+        ctx->getResultString(v2,v1.refstr(),"searchname",4294967295U);
+    }
+    rtlDataAttr v1;
+    unsigned v2;
+    virtual const char * getFileName() {
+        return "names";
+    }
+    virtual void createSegmentMonitors(IIndexReadContext *irc) {
+        Owned<IStringSet> set3;
+        set3.setown(createRtlStringSet(40));
+        char v4[40];
+        rtlStrToStr(40U,v4,v2,v1.getstr());
+        if (rtlCompareStrStr(v2,v1.getstr(),40U,v4) == 0) {
+            set3->addRange(v4,v4);
+        }
+        irc->append(createKeySegmentMonitor(false, set3.getClear(), 0, 40));
+        irc->append(createWildKeySegmentMonitor(40, 80));
+    }
+    virtual size32_t transform(ARowBuilder & crSelf, const void * _left) {
+        crSelf.getSelf();
+        unsigned char * left = (unsigned char *)_left;
+        memcpy(crSelf.row() + 0U,left + 0U,120U);
+        return 120U;
+    }
+};

Some of the methods to highlight are:

a) onCreate() - common to all activities. It is called by the engine when the helper is first created, and allows the helper to cache information that does not change - in this case the name that is being searched for. b) getFileName() - determines the name of the index being read. c) createSegmentMonitors() - defines which columns to filter, and which values to match against. d) transform() - create the record to return from the activity. It controls which columns should be included from the index row in the output. (In this case all.)

Executing the graph

To execute a graph, the engine walks the activities in the graph xml and creates, in memory, a graph of implementation activities.

For each activity, the name of the helper factory is calculated from the activity number (e.g. fAc2 for activity 2). That function is imported from the loaded dll, and then called to create an instance of the generated helper class - in this case cAc2.

The engine then creates an instance of the class for implementing the activity, and passes the previously created helper object to the constructor. The engine uses the _kind attribute in the graph to determine which activity class should be used. E.g. In the example above activity 2 has a _kind of 77, which corresponds to TAKindexread. For an index-read activity roxie will create an instance of CRoxieServerIndexReadActivity. (The generated helper that is passed to the activity instance will implement IHThorIndexReadArg). The activity implementations may also extract other information from the xml for the activity - e.g. hints. Once all the activities are created the edge information is used to link inputs activities to output activities and add other dependencies.

Note: Any subgraph that is marked with <att name="rootGraph" value="1"/> is a root subgraph. An activity within a subgraph that has no outputs is called a 'sink' (and an activity without any inputs is called a 'source').

Executing a graph involves executing all the root subgraphs that it contains. All dependencies of the activities within the subgraph must be executed before a subgraph is executed. To execute a subgraph, the engine executes each of the sink activities on separate threads, and then waits for each of those threads to complete. Each sink activity lazily pulls input rows on demand from activities further up the graph, processes them and returns when complete.

(If you examine the code you will find that this is a simplification. The implementation for processing dependencies is more fine grained to ensure IF datasets, OUPUT(,UPDATE) and other ECL constructs are executed correctly.)

In our example the execution flows as follows:

  1. Only a single root subgraph, so need to execute that.
  2. The engine will execute activity 3 - the workunit-write activity (TAKworkunitwrite).
  3. The workunit-write activity will start its input.
  4. The index-read activity will call the helper functions to obtain the filename, resolve the index, and create the filter.
  5. The workunit-write activity requests a row from its input.
  6. The index-read finds the first row, and calls the helper's transform() method to create an output row.
  7. The workunit-write activity persists the row to a buffer (using the serializer provided by the IOutputMetaData interface implemented by the class mx1).
  8. Back to step 5, workunit-write reading a row from its input, until end of file is returned (notified as two consecutive NULL rows.
  9. Workunit-write commits the results and finishes.

The execution generally switches back and forth between the code in the engines, and the members of the generated helper classes.

There are some other details of query execution that are worth highlighting:

Child Queries

: Some activities perform complicated operations on child datasets of the input rows. E.g. remove all duplicate people who are marked as living at this address. This will create a "child query" in the graph - i.e. a nested graph within a subgraph, which may be executed each time a new input row is processed by the containing activity. (The graph of activities for each child query is created at the same time as the parent activity. The activity instances are reinitialised and re-executed for each input row processed by the parent activity to minimise the create-time overhead.)

Other helper functions

: The generated code contains other functions that are used to describe the meta information for the rows processed within the graph. E.g. the following class describes the output from the index read activity:

cpp
struct mi1 : public CFixedOutputMetaData {
+    inline mi1() : CFixedOutputMetaData(120) {}
+    virtual const RtlTypeInfo * queryTypeInfo() const { return &ty1; }
+} mx1;
This represents a fixed size row that occupies 120 bytes. The object
+returned by the queryTypeInfo() function provides information about
+the types of the fields:
+
cpp
const RtlStringTypeInfo ty2(0x4,40);
+const RtlFieldStrInfo rf1("name",NULL,&ty2);
+const RtlStringTypeInfo ty3(0x4,80);
+const RtlFieldStrInfo rf2("address",NULL,&ty3);
+const RtlFieldInfo * const tl4[] = { &rf1,&rf2, 0 };
+const RtlRecordTypeInfo ty1(0xd,120,tl4);
I.e. a string column, length 40 called "name", followed by a
+string column, length 80 called "address". The interface
+IOutputMetaData in eclhelper.hpp is key to understanding how the
+rows are processed.
+

Inline dataset operations

: The rule mentioned at the start - that the generated code does not contain any knowledge of how to perform a particular dataset operation - does have one notable exception. Some operations on child datasets are very simple to implement, and more efficient if they are implemented using inline C++ code. (The generated code is smaller, and they avoid the overhead of setting up a child graph.) Examples include filtering and aggregating column values from a child dataset.

The full code in the different engines is more complicated than the simplified process outlined above, especially when it comes to executing dependencies, but the broad outline is the same.

Appendix

More information on the work done in the code generator to create the workunit can be found in ecl/eclcc/DOCUMENTATION.rst.

The C++ code can be generated as a single C++ file or multiple files. The system defaults to multiple C++ files, so that they can be compiled in parallel (and to avoid problems some compilers have with very large files). When multipe C++ files are generated the metadata classes and workflow classes are generated in the main module, and the activities are generated in the files suffixed with a number. It may be easier to understand the generated code if it is in one place. In which case, compile your query with the option -fspanMultipleCpp=0. Use -fsaveCppTempFiles to ensure the C++ files are not deleted (the C++ files will appear as helpers in the workunit details).

Key types and interfaces from eclhelper.hpp

IEclProcess

: The interface that is used by the workflow engine to execute the different workflow items in the generated code.

ThorActivityKind

: This enumeration contains one entry for each activity supported by the engines.

ICodeContext

: This interface is implemented by the engine, and provides a mechanism for the generated code to call back into the engine. For example resolveChildQuery() is used to obtain a reference to a child query that can then be executed later.

IOutputMetaData

: This interface is used to describe any meta data associated with the data being processed by the queries.

IHThorArg

: The base interface for defining information about an activity. Each activity has an associated interface that is derived from this interface. E.g. each instance of the sort activity will have a helper class implementing IHThorSortArg in the generated query. There is normally a corresponding base class for each interface in eclhelper_base.hpp that is used by the generated code e.g. CThorSortArg.

ARowBuilder

: This abstract base class is used by the transform functions to reserve memory for the rows that are created.

IEngineRowAllocator

: Used by the generated code to allocate rows and rowsets. Can also be used to release rows (or call the global function rtlReleaseRow()).

IGlobalCodeContext

: Provides access to functions that cannot be called inside a graph - i.e. can only be called from the global workflow code. Most functions are related to the internal implementation of particular workflow item types (e.g. persists).

Glossary

activity

: An activity is the basic unit of dataset processing implemented by the engines. Each activity corresponds to a node in the thor execution graph. Instances of the activities are connnected together to create the graph.

dll

: A dynamically loaded library. These correspond to shared objects in Linux (extension '.so'), dynamic libraries in Max OS X ('.dylib'), and dynamic link libraries in windows ('.dll').

superfile

: A composite file which allows a collection of files to be treated as a single compound file.

?What else should go here?

Full text of the workunit XML

The XML for a workunit can be viewed on the XML tag in eclwatch, or generated by compiling the ECL using the -wu option with eclcc. Alternatively eclcc -b -S can be used to generate the XML and the C++ at the same time (the output filenames are derived from the input name).

xml
<W_LOCAL buildVersion="internal_5.3.0-closedown0"
+    cloneable="1"
+    codeVersion="157"
+    eclVersion="5.3.0"
+    hash="2344844820"
+    state="completed"
+    xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
+    <Debug>
+        <addtimingtoworkunit>0</addtimingtoworkunit>
+        <debugnlp>1</debugnlp>
+        <expandpersistinputdependencies>1</expandpersistinputdependencies>
+        <forcegenerate>1</forcegenerate>
+        <noterecordsizeingraph>1</noterecordsizeingraph>
+        <regressiontest>1</regressiontest>
+        <showmetaingraph>1</showmetaingraph>
+        <showrecordcountingraph>1</showrecordcountingraph>
+        <spanmultiplecpp>0</spanmultiplecpp>
+        <targetclustertype>hthor</targetclustertype>
+    </Debug>
+    <Graphs>
+        <Graph name="graph1" type="activities">
+            <xgmml>
+                <graph wfid="2">
+                    <node id="1">
+                        <att>
+                            <graph>
+                                <att name="rootGraph" value="1" />
+                                <edge id="2_0" source="2" target="3" />
+                                <node id="2" label="Index Read&#10;&apos;names&apos;">
+                                    <att name="definition" value="workuniteg1.ecl(3,1)" />
+                                    <att name="name" value="results" />
+                                    <att name="_kind" value="77" />
+                                    <att name="ecl"
+                                        value="INDEX({ string40 name, string80 address }, &apos;names&apos;, fileposition(false));&#10;FILTER(KEYED(name = STORED(&apos;searchname&apos;)));&#10;" />
+                                    <att name="recordSize" value="120" />
+                                    <att name="predictedCount" value="0..?[disk]" />
+                                    <att name="_fileName" value="names" />
+                                </node>
+                                <node id="3" label="Output&#10;Result #1">
+                                    <att name="definition" value="workuniteg1.ecl(4,1)" />
+                                    <att name="_kind" value="16" />
+                                    <att name="ecl" value="OUTPUT(..., workunit);&#10;" />
+                                    <att name="recordSize" value="120" />
+                                </node>
+                            </graph>
+                        </att>
+                    </node>
+                </graph>
+            </xgmml>
+        </Graph>
+    </Graphs>
+    <Query fetchEntire="1">
+        <Associated>
+            <File desc="workuniteg1.ecl.cpp"
+                filename="c:\\regressout\\workuniteg1.ecl.cpp"
+                ip="10.121.159.73"
+                type="cpp" />
+        </Associated>
+    </Query>
+    <Results>
+        <Result isScalar="0"
+            name="Result 1"
+            recordSizeEntry="mf1"
+            rowLimit="-1"
+            sequence="0">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                name&#xe000;&#xe004;(&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;address&#xe000;&#xe004;P&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;%&#xe000;&#xe000;&#xe000;{
+                string40 name, string80 address };&#10; </SchemaRaw>
+        </Result>
+        <Result name="Result 2" sequence="1">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                Result_2&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;
+            </SchemaRaw>
+        </Result>
+    </Results>
+    <Statistics>
+        <Statistic c="eclcc"
+            count="1"
+            creator="eclcc"
+            kind="SizePeakMemory"
+            s="compile"
+            scope=">compile"
+            ts="1428933081084000"
+            unit="sz"
+            value="27885568" />
+    </Statistics>
+    <Variables>
+        <Variable name="searchname">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                searchname&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;
+            </SchemaRaw>
+        </Variable>
+    </Variables>
+    <Workflow>
+        <Item mode="normal"
+            state="null"
+            type="normal"
+            wfid="1" />
+        <Item mode="normal"
+            state="reqd"
+            type="normal"
+            wfid="2">
+            <Dependency wfid="1" />
+            <Schedule />
+        </Item>
+    </Workflow>
+</W_LOCAL>

Full contents of the generated C++ (as a single file)

cpp
/* Template for generating thor/hthor/roxie output */
+#include "eclinclude4.hpp"
+#include "eclrtl.hpp"
+#include "rtlkey.hpp"
+
+extern RTL_API void rtlStrToStr(size32_t lenTgt,void * tgt,size32_t lenSrc,const void * src);
+extern RTL_API int rtlCompareStrStr(size32_t lenL,const char * l,size32_t lenR,const char * r);
+
+
+const RtlStringTypeInfo ty2(0x4,40);
+const RtlFieldStrInfo rf1("name",NULL,&ty2);
+const RtlStringTypeInfo ty3(0x4,80);
+const RtlFieldStrInfo rf2("address",NULL,&ty3);
+const RtlFieldInfo * const tl4[] = { &rf1,&rf2, 0 };
+const RtlRecordTypeInfo ty1(0xd,120,tl4);
+void getLayout5(size32_t & __lenResult, void * & __result, IResourceContext * ctx) {
+    rtlStrToDataX(__lenResult,__result,87U,"\\000R\\000\\000\\000\\001x\\000\\000\\000\\002\\000\\000\\000\\003\\004\\000\\000\\000name\\004(\\000\\000\\000\\001ascii\\000\\001ascii\\000\\000\\000\\000\\000\\000\\003\\007\\000\\000\\000address\\004P\\000\\000\\000\\001ascii\\000\\001ascii\\000\\000\\000\\000\\000\\000\\002\\000\\000\\000");
+}
+struct mi1 : public CFixedOutputMetaData {
+    inline mi1() : CFixedOutputMetaData(120) {}
+    virtual const RtlTypeInfo * queryTypeInfo() const { return &ty1; }
+} mx1;
+extern "C" ECL_API IOutputMetaData * mf1() { mx1.Link(); return &mx1; }
+
+struct cAc2 : public CThorIndexReadArg {
+    virtual unsigned getFormatCrc() {
+        return 470622073U;
+    }
+    virtual bool getIndexLayout(size32_t & __lenResult, void * & __result) { getLayout5(__lenResult, __result, ctx); return true; }
+    virtual IOutputMetaData * queryDiskRecordSize() { return &mx1; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void onCreate(ICodeContext * _ctx, IHThorArg *, MemoryBuffer * in) {
+        ctx = _ctx;
+        ctx->getResultString(v2,v1.refstr(),"searchname",4294967295U);
+    }
+    rtlDataAttr v1;
+    unsigned v2;
+    virtual const char * getFileName() {
+        return "names";
+    }
+    virtual void createSegmentMonitors(IIndexReadContext *irc) {
+        Owned<IStringSet> set3;
+        set3.setown(createRtlStringSet(40));
+        char v4[40];
+        rtlStrToStr(40U,v4,v2,v1.getstr());
+        if (rtlCompareStrStr(v2,v1.getstr(),40U,v4) == 0) {
+            set3->addRange(v4,v4);
+        }
+        irc->append(createKeySegmentMonitor(false, set3.getClear(), 0, 40));
+        irc->append(createWildKeySegmentMonitor(40, 80));
+    }
+    virtual size32_t transform(ARowBuilder & crSelf, const void * _left) {
+        crSelf.getSelf();
+        unsigned char * left = (unsigned char *)_left;
+        memcpy(crSelf.row() + 0U,left + 0U,120U);
+        return 120U;
+    }
+};
+extern "C" ECL_API IHThorArg * fAc2() { return new cAc2; }
+struct cAc3 : public CThorWorkUnitWriteArg {
+    virtual int getSequence() { return 0; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void serializeXml(const byte * self, IXmlWriter & out) {
+        mx1.toXML(self, out);
+    }
+};
+extern "C" ECL_API IHThorArg * fAc3() { return new cAc3; }
+
+
+struct MyEclProcess : public EclProcess {
+    virtual unsigned getActivityVersion() const { return ACTIVITY_INTERFACE_VERSION; }
+    virtual int perform(IGlobalCodeContext * gctx, unsigned wfid) {
+        ICodeContext * ctx;
+        ctx = gctx->queryCodeContext();
+        switch (wfid) {
+            case 1U:
+                if (!gctx->isResult("searchname",4294967295U)) {
+                    ctx->setResultString("searchname",4294967295U,5U,"Smith");
+                }
+                break;
+            case 2U: {
+                ctx->executeGraph("graph1",false,0,NULL);
+                ctx->setResultString(0,1U,5U,"Done!");
+            }
+            break;
+        }
+        return 2U;
+    }
+};
+
+
+extern "C" ECL_API IEclProcess* createProcess()
+{
+
+    return new MyEclProcess;
+}
`,170)]))}const o=i(h,[["render",e]]);export{g as __pageData,o as default}; diff --git a/assets/devdoc_Workunits.md.DJg1TI-D.lean.js b/assets/devdoc_Workunits.md.DJg1TI-D.lean.js new file mode 100644 index 00000000000..b025fe5667f --- /dev/null +++ b/assets/devdoc_Workunits.md.DJg1TI-D.lean.js @@ -0,0 +1,403 @@ +import{_ as i,c as a,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const g=JSON.parse('{"title":"Understanding workunits","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/Workunits.md","filePath":"devdoc/Workunits.md","lastUpdated":1731340314000}'),h={name:"devdoc/Workunits.md"};function e(l,s,k,p,r,E){return n(),a("div",null,s[0]||(s[0]=[t(`

Understanding workunits

Introduction

A workunit contains all the information that the system requires about a query - including the parameters it takes, how to execute it, and how to format the results. Understanding the contents of a workunit is a key step to understanding how the HPCC system fits together. This document begins with an overview of the different elements in a workunit. That is then followed by a walk-through executing a simple query, with a more detailed description of some of the workunit components to show how they all tie together.

Before looking at the contents of a workunit it is important to understand one of the design goals behind the HPCC system. The HPCC system logically splits the code that is needed to execute a query in two. On the one hand there are the algorithms that are used to perform different dataset operations (e.g. sorting, deduping). The same algorithms are used by all the queries that execute on the system. On the other hand there is the meta-data that describes the columns present in the datasets, which columns you need to sort by, and the order of operations required by the query. These are typically different for each query. This "meta-data" includes generated compare functions, classes that describe the record formats, serialized data and graphs.

A workunit only contains data and code relating to the meta data for the query, i.e. "what to do", while the different engines (hthor, roxie, and thor) implement the algorithms - "how to do it". If you look at a workunit for an ECL query that sorts a dataset you will not find code to perform the sort itself in the workunit - you will not even find a call to a sort library function - that logic is contained in the engine that executes the query.

One consequence of this split, which can be initially confusing, is that execution continually passes back and forth between the generated code and the engines. By the end of this document you should have a better understanding of how the generated code is structured and the part it plays in executing a query.

Note the term "Query" is used as a generic term to cover read-only queries (typically run in roxie) and ETL (Extract, Transform, and Load) style queries that create lots of persistent datafiles (typically run in Thor). Also, the term "workunit" is used ambiguously. The dll created from a query is called a workunit (which is static), but "workunit" is also used to describe a single execution of a query (which includes the parameters and results). It should be clear from the context which of these is meant.

Throughout this document "dll" is a generic term used to refer to a dynamically loaded library. These correspond to shared objects in Linux (extension '.so'), dynamic libraries in Max OS X ('.dylib'), and dynamic link libraries in windows ('.dll').

Contents of a workunit

A workunit is generated by the ecl compiler, and consists of a single dll. That dll contains several different elements:

  • Various C++ helper classes, and exported factory methods are used to create instances of those classes.
  • An XML resource containing different pieces of information about a workunit, including workflow and graphs.
  • Other user-defined resources included in the manifest.

A workunit dll contains everything that the engines need to execute the query. When a workunit is executed, key elements of the xml information are cloned from the workunit dll and copied into a database. This is then augmented with other information as the query is executed - e.g. input parameters, results, statistics, etc.. The contents of the workunit are accessed through an "IWorkUnit" interface (defined in common/workunit/workunit.hpp) that hides the implementation details.

(Workunit information is currently stored in the Dali database - one of the components within the HPCC platform. Work is in-progress to allow the bulk of this workunit data to be stored in Cassandra or another third-party database instead.)

How is the workunit used?

The workunit information is used by most of the components in the system. The following is a quick outline:

  • eclcc

    Creates a workunit dll from an ecl query.

  • eclccserver

    Executes eclcc to create a workunit dll, and then clones some of the information into dali to create an active instance, ready to execute.

  • esp

    Uses information in the workunit dll to publish workunits. This includes details of the parameters that the query takes, how they should be formatted, and the results it returns.

  • eclscheduler

    Monitors workunits that are waiting for events, and updates them when those events occur.

  • eclagent/Roxie

    Process the different workflow actions, and workflow code.

  • hThor/Roxie/Thor

    Execute graphs within the workflow items.

  • Dali

    This database is used to store the state of the workunit state.

Example

The following ECL will be used as an example for the rest of the discussion. It is a very simple search that takes a string parameter 'searchName', which is the name of the person to search for, and returns the matching records from an index. It also outputs the word 'Done!' as a separate result.

ecl
STRING searchName := 'Smith' : STORED('searchName');
+nameIndex := INDEX({ STRING40 name, STRING80 address }, 'names');
+results := nameIndex(KEYED(name = searchName));
+OUTPUT(results);
+OUTPUT('Done!');

Extracts from the XML and C++ that are generated from this example will be included in the following discussion.

Workunit Main Elements

This section outlines the different sections in a workunit. This is followed by a walk-through of the stages that take place when a workunit is executed, together with a more detailed explanation of the workunit contents.

Workflow

The workflow is the highest level of control within a workunit. It is used for two related purposes:

  • Scheduling The HPCC system allows ECL code to be executed when certain events occur - e.g. every hour or when files are uploaded to a landing zone. Each piece of ECL code that is triggered by an external event creates a separate workflow action. This allows each of those events to be processed independently.
  • Splitting up queries. There are situations where it is useful to break parts of an ECL query into independent sections. The simplest example is the PERSIST workflow operation, which allows results to be shared between different work units. Each workflow operation creates one (or sometimes more) independent workflow items, which are then connected together.

Each piece of independent ECL is given a unique workflow id (wfid). Often workflow items need to be executed in a particular order, e.g. ensuring a persist exists before using it, which is managed with dependencies between different workflow items.

Our example above generates the following XML entry in the workunit:

xml
<Workflow>
+    <Item .... wfid="1"/>
+    <Item .... wfid="2">
+    <Dependency wfid="1"/>
+    <Schedule/>
+    </Item>
+</Workflow>

This contains two workflow items. The first workflow item (wfid=1) ensures that the stored value has a default value if it has not been supplied. The second item (with wfid=2) is the main code for the query. This has a dependency on the first workflow item because the stored variable needs to be intialised before it is executed.

MyProcess

The generated code contains a class instance that is used for executing the code associated with the workflow items. It is generated at the end of the main C++ module. E.g.:

cpp
struct MyEclProcess : public EclProcess {
+    virtual int perform(IGlobalCodeContext * gctx, unsigned wfid) {
+        ....
+        switch (wfid) {
+            case 1U:
+                ... code for workflow item 1 ...
+            case 2U:
+                ... code for workflow item 2 ...
+            break;
+        }
+        return 2U;
+    }
+};

The main element is a switch statement inside the perform() function that allows the workflow engines to execute the code associated with a particular workflow item.

There is also an associated factory function that is exported from the dll, and is used by the engines to create instances of the class:

cpp
extern "C" ECL_API IEclProcess* createProcess()
+{
+    return new MyEclProcess;
+}

Graph

Most of the work executing a query involves processing dataset operations, which are implemented as a graph of activities. Each graph is represented in the workunit as an xml graph structure (currently it uses the xgmml format). The graph xml includes details of which types of activities are required to be executed, how they are linked together, and any other dependencies.

The graph in our example is particularly simple:

xml
<Graph name="graph1" type="activities">
+    <xgmml>
+    <graph wfid="2">
+    <node id="1">
+    <att>
+        <graph>
+        <att name="rootGraph" value="1"/>
+        <edge id="2_0" source="2" target="3"/>
+        <node id="2" label="Index Read&#10;&apos;names&apos;">
+        ... attributes for activity 2 ...
+        </node>
+        <node id="3" label="Output&#10;Result #1">
+        ... attributes for activity 3 ...
+        </node>
+        </graph>
+    </att>
+    </node>
+    </graph>
+    </xgmml>
+</Graph>

This graph contains a single subgraph (node id=2) that contains two activities - an index read activity and an output result activity. These activities are linked by a single edge (id "2_0"). The details of the contents are covered in the section on executing graphs below.

Generated Activity Helpers

Each activity has a corresponding class instance in the generated code, and a factory function for creating instances of that class:

cpp
struct cAc2 : public CThorIndexReadArg {
+    ... Implementation of the helper for activity #2 ...
+};
+extern "C" ECL_API IHThorArg * fAc2() { return new cAc2; }
+
+struct cAc3 : public CThorWorkUnitWriteArg {
+    ... Implementation of the helper for activity #3 ...
+};
+extern "C" ECL_API IHThorArg * fAc3() { return new cAc3; }

The helper class for an activity implements the interface that is required for that particular kind. (The interfaces are defined in rtl/include/eclhelper.hpp - further details below.)

Other

The are several other items, detailed below, that are logically associated with a workunit. The information may be stored in the workunit dll or in various other location e.g. Dali, Sasha or Cassandra. It is all accessed through the IWorkUnit interface in common/workunit/workunit.hpp that hides the implementation details. For instance information generated at runtime cannot by definition be included in the workunit dll.

Options

Options that are supplied to eclcc via the -f command line option, or the #option statement are included in the <Debug> section of the workunit xml:

xml
<Debug>
+    <addtimingtoworkunit>0</addtimingtoworkunit>
+    <noterecordsizeingraph>1</noterecordsizeingraph>
+    <showmetaingraph>1</showmetaingraph>
+    <showrecordcountingraph>1</showrecordcountingraph>
+    <spanmultiplecpp>0</spanmultiplecpp>
+    <targetclustertype>hthor</targetclustertype>
+</Debug>

Note, the names of workunit options are case insensitive, and converted to lower case.

Input Parameters

Many queries contain input parameters that modify their behaviour. These correspond to STORED definitions in the ECL. Our example contains a single string "searchName", so the workunit contains a single input parameter:

xml
<Variables>
+    <Variable name="searchname">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+        searchname&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;   
+    </SchemaRaw>
+    </Variable>
+</Variables>

The implementation details of the schema information is encapsulated by the IConstWUResult interface in workunit.hpp.

Results

The workunit xml also contains details of each result that the query generates, including a serialized description of the output record format:

xml
<Results>
+    <Result isScalar="0"
+            name="Result 1"
+            recordSizeEntry="mf1"
+            rowLimit="-1"
+            sequence="0">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+    name&#xe000;&#xe004;(&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;address&#xe000;&#xe004;P&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;%&#xe000;&#xe000;&#xe000;{ string40 name, string80 address };&#10;   </SchemaRaw>
+    </Result>
+    <Result name="Result 2" sequence="1">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+    Result_2&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;   </SchemaRaw>
+    </Result>
+</Results>

in our example there are two - the dataset of results and the text string "Done!". The values of the results for a query are associated with the workunit. (They are currently saved in dali, but this may change in version 6.0.)

Timings and Statistics

Any timings generated when compiling the query are included in the workunit dll:

xml
<Statistics>
+    <Statistic c="eclcc"
+            count="1"
+            creator="eclcc"
+            kind="SizePeakMemory"
+            s="compile"
+            scope=">compile"
+            ts="1428933081084000"
+            unit="sz"
+            value="27885568"/>
+</Statistics>

Other statistics and timings created when running the query are stored in the runtime copy of the workunit. (Statistics for graph elements are stored in a different format from global statistics, but the IWorkUnit interface ensures the implementation details are hidden.)

Manifests

It is possible to include other user-defined resources in the workunit dll - e.g. web pages, or dashboard layouts. I have to confess I do not understand them... ??Tony please provide some more information....!

Stages of Execution

Once a workunit has been compiled to a dll it is ready to be executed. Execution can be triggered in different ways, E.g.:

  • The ECL for a query is submitted to esp
    • A workunit entry, containing the ECL, is created in dali and added to an eclccserver queue.
    • An eclccserver instance removes the workunit form the queue, and compiles the ECL to a workunit dll.
    • The dali workunit entry is updated with the information from the workunit dll.
    • The dali workunit is added to the agent execution queue associated with the target cluster.
    • The associated engine (actually agentexec for hThor and Thor) pulls a query form the queue and executes it.
  • A query is submitted and published with a name. Another request is then submitted to execute this previously compiled query.
    • A workunit entry, containing the ECL, is created in dali and added to an eclccserver queue.
    • An eclccserver instance removes the workunit form the queue, and compiles the ECL to a workunit dll.
    • There is a 'query set' for each combination of query name and the target cluster. The new workunit dll is added to the appropriate query set, and marked as the current active implementation.
    • Later, a query that references a named query is submitted to esp.
    • The name and target cluster are mapped via the query set to the active implementation, and a workunit instance is created from the active workunit dll.
    • The workunit is added to a roxie or eclagentexec queue ready to be executed.
    • The associated engine pulls a query form the queue and executes it.
  • A query is compiled as a stand alone executable. The executable is then run.
    • eclcc is executed on the command line without the -shared command line option.
    • The resulting executable is run. The engine used to execute the query depends on the -platform parameter supplied to eclcc.

Most queries create persistent workunits in dali and then update those workunits with results as they are calculated, however for some roxie queries (e.g. in a production system) the execution workunits are transient.

The following walk-through details the main stages executing a query, and the effect each of the query elements has.

Queues

The system uses several inter-process queues to communicate between the different components in the system. These queues are implemented by dali. Components can subscribe to one or more queues, and receive notifications when entries are avaialable.

Some example queues are:

  • <cluster>.eclserver - workunits to be compiled
  • <cluster>.roxie - workunits to execute on roxie
  • <cluster>.thor - graphs to execute on thor
  • <cluster>.eclscheduler - workunits that need to wait for events
  • <cluster>.agent - workunits to be executed on hthor or thor.
  • dfuserver_queue - dfu workunits for sprays/file copies etc.

Workflow

When a workunit is ready to be run, the workflow controls the flow of execution. The workflow engine (defined in common/workunit/workflow.cpp) is responsible for determining which workflow item should be executed next.

The workflow for Thor and hThor jobs is coordinated by eclagent, while roxie includes the workflow engine in its process. The eclscheduler also uses the workflow engine to process events and mark workflow items ready for execution.

eclagent, or roxie calls the createProcess() function from the workunit dll to create an instance of the generated workflow helper class, and passes it to the workflow engine. The workflow engine walks the workflow items to find any items that are ready to be executed (have the state "reqd" - i.e. required). If a required workflow item has dependencies on other child workflow items then those children are executed first. Once all dependencies have executed successfully the parent workflow item is executed. The example has the following workflow entries:

xml
<Workflow>
+    <Item mode="normal"
+        state="null"
+        type="normal"
+        wfid="1"/>
+    <Item mode="normal"
+        state="reqd"
+        type="normal"
+        wfid="2">
+        <Dependency wfid="1"/>
+        <Schedule/>
+    </Item>
+</Workflow>

Item 2 has a state of "reqd", so it should be evaluated now. Item 2 has a dependency on item 1, so that must be evaluated first. This is achieved by calling MyEclProcess::perform() on the object that was previously created from the workunit dll, passing in wfid = 1. That will execute the following code:

cpp
switch (wfid) {
+    case 1U:
+        if (!gctx->isResult("searchname",4294967295U)) {
+            ctx->setResultString("searchname",4294967295U,5U,"Smith");
+        }
+        break;
+    break;
+}

This checks if a value has been provided for the input parameter, and if not assigns a default value of "Smith". The function returns control to the workflow engine. With the dependencies for wfid 2 now satisfied, the generated code for that workflow id is now executed:

cpp
switch (wfid) {
+    case 2U: {
+        ctx->executeGraph("graph1",false,0,NULL);
+        ctx->setResultString(0,1U,5U,"Done!");
+    }
+    break;
+}

Most of the work for this workflow item involves executing graph1 (by calling back into eclagent/roxie). However, the code also directly sets another result. This is fairly typical - the code inside MyProcess::perform often combines evaluating scalar results, executing graphs, and calling functions that cannot (currently) be called inside a graph (e.g. those involving superfile transactions).

Once all of the required workflow items are executed, the workunit is marked as completed. Alternatively, if there are workflow items that are waiting to be triggered by an event, the workunit will be passed to the scheduler, which will keep monitoring for events.

Note that most items in the xml workflow begin in the state WFStateNull. This means that it is valid to execute them, but they haven't been executed yet. Typically, only a few items begin with the state WFStateReqd.

There are various specialised types of workflow items - e.g. sequential, wait, independent, but they all follow the same basic approach of executing dependencies and then executing that particular item.

Most of the interesting work in an ECL query is done within a graph. The call ctx->executeGraph will either execute the graph locally (in the case of hthor and roxie), or add the workunit onto a queue (for Thor). Whichever happens, control will pass to that engine.

Specialised Workflow Items

Each item mode/type can affect the dependency structure of the workflow:

  • Sequential/Ordered

    The workflow structure for sequential and ordered is the same. An item is made to contain all of the actions in the statement. This is achieved by making each action a dependency of this item. The dependencies, and consequently the actions, must be executed in order. An extra item is always inserted before each dependency. This means that if other statements reference the same dependency, it will only be performed once.

  • Persist

    When the persist workflow service is used, two items are created. One item contains the graphs that perform the expression defined in ECL. It also stores the wfid for the second item. The second item is used to determine whether the persist is up to date.

  • Condition (IF)

    The IF function has either 2 or 3 arguments: the expression, the trueresult, and sometimes the falseresult. For each argument, a workflow item is created. These items are stored as dependencies to the condition, in the order stated above.

  • Contingency (SUCCESS/FAILURE)

    When a contingency clause is defined for an attribute, the attribute item stores the wfid of the contingency. If both success and failure are used, then the item will store the wfid of each contingency. The contingency is composed of items, just like the larger query.

  • Recovery

    When a workflow item fails, if it has a recovery clause, the item will be re-executed. The clause contains actions defined by the programmer to remedy the problem. This clause is stored differently to SUCCESS/FAILURE, in that the recovery clause is a dependency of the failed item. In order to stop the recovery clause from being executed like the other dependencies, it is marked with WFStateSkip.

  • Independent

    This specifier is used when a programmer wants to common up code for the query. It prevents the same piece of code from being executed twice in different contexts. To achieve this, an extra item is inserted between the expression and whichever items depend on it. This means that although the attribute can be referenced several times, it will only be executed once.

Graph Execution

All the engines (roxie, hThor, Thor) execute graphs in a very similar way. The main differences are that hThor and Thor execute a sub graph at a time, while roxie executes a complete graph as one. Roxie is also optimized to minimize the overhead of executing a query - since the same query tends to be run multiple times. This means that roxie creates a graph of factory objects and those are then used to create the activities. The core details are the same for each of them though.

Details of the graph structure

First, a recap of the structure of the graph together with the full xml for the graph definition in our example:

xml
<Graph name="graph1" type="activities">
+    <xgmml>
+        <graph wfid="2">
+            <node id="1">
+                <att>
+                    <graph>
+                        <att name="rootGraph" value="1"/>
+                        <edge id="2_0" source="2" target="3"/>
+                        <node id="2" label="Index Read&#10;&apos;names&apos;">
+                            <att name="definition" value="workuniteg1.ecl(3,1)"/>
+                            <att name="name" value="results"/>
+                            <att name="_kind" value="77"/>
+                            <att name="ecl" value="INDEX({ string40 name, string80 address }, &apos;names&apos;, fileposition(false));&#10;FILTER(KEYED(name = STORED(&apos;searchname&apos;)));&#10;"/>
+                            <att name="recordSize" value="120"/>
+                            <att name="predictedCount" value="0..?[disk]"/>
+                            <att name="_fileName" value="names"/>
+                        </node>
+                        <node id="3" label="Output&#10;Result #1">
+                            <att name="definition" value="workuniteg1.ecl(4,1)"/>
+                            <att name="_kind" value="16"/>
+                            <att name="ecl" value="OUTPUT(..., workunit);&#10;"/>
+                            <att name="recordSize" value="120"/>
+                        </node>
+                    </graph>
+                </att>
+            </node>
+        </graph>
+    </xgmml>
+</Graph>

Each graph (e.g. graph1) consists of 1 or more subgraphs (in the example above, node id=1). Each of those subgraphs contains 1 or more activities (node id=2, node id=3).

The xml for each activity might contain the following information:

  • A unique id (e.g. id="2").
  • The "kind" of the activity, e.g. <att name="_kind" value="77"/>. The value is an item from the enum ThorActivityKind in eclhelper.hpp.
  • The ECL that created the activity. E.g. <att name="ecl" value="...">
  • The identifier of the ecl definition. E.g. <att name="name" value="results"/>
  • Location (e.g. file, line number, column) of the original ECL. E.g. <att name="definition" value="workuniteg1.ecl(3,1)"/>
  • Meta information the code generator has deduced about the activity output. Examples include the record size, expected number of rows, sort order etc. E.g. <att name="recordSize" value="120"/>
  • Hints, which are used for fine control of options for a particular activity (e.g,, the number of threads to use while sorting).
  • Record counts and stats once the job has executed. (These are logically associated with the activities in the graph, but stored separately.)

Graphs also contain edges that can take one of 3 forms:

Edges within graphs

: These are used to indicate how the activities are connected. The source activity is used as the input to the target activity. These edges have the following format:

    <edge id="<source-activity-id>_<output-count>" source="<source-activity-id>" target="<target-activity-id">
+
+There is only one edge in our example workunit: \\<edge id="2\\_0"
+source="2" target="3"/\\>.
+

Edges between graphs

: These are used to indicate direct dependencies between activities. For instance there will be an edge connecting the activity that writes a spill file to the activity that reads it. These edges have the following format:

    <edge id="<source-activity-id>_<target-activity-id>" source="<source-subgraph-id>" target="<target-subgraph-id>"
+       <att name="_sourceActivity" value="<source-activity-id>"/>
+       <att name="_targetActivity" value="<target-activity-id>"/>
+    </edge>
+
+Roxie often optimizes spilled datasets and treats these edges as
+equivalent to the edges between activities.
+

Other dependencies.

: These are similar to the edges between graphs, but they are used for values that are used within an activity. For instance one part of the graph may calculate the maximum value of a column, and another activity may filter out all records that do not match that maximum value. The format is the same as the edges between graphs except that the edge contains the following attribute:

    <att name="_dependsOn" value="1"/>
+

Each activity in a graph also has a corresponding helper class instance in the generated code. (The name of the class is "cAc" followed by the activity number, and the exported factory method is "fAc" followed by the activity number.) Each helper class implements a specialised interface (derived from IHThorArg) - the particular interface is determined by the value of the "_kind" attribute for the activity.

The contents of file rtl/include/eclhelper.hpp is key to understanding how the generated code relates to the activities. Each kind of activity requires a helper class that implements a specific interface. The helpers allow the engine to tailor the generalised activity implementation to the the particular instance - e.g. for a filter activity whether a row should be included or excluded. The appendix at the end of this document contains some further information about this file.

The classes in the generated workunits are normally derived from base implementations of those interfaces (which are implemented in rtl/include/eclhelper_base.hpp). This reduces the size of the generated code by providing default implementations for various functions.

For instance the helper for the index read (activity 2) is defined as:

cpp
struct cAc2 : public CThorIndexReadArg {
+    virtual unsigned getFormatCrc() {
+        return 470622073U;
+    }
+    virtual bool getIndexLayout(size32_t & __lenResult, void * & __result) { getLayout5(__lenResult, __result, ctx); return true; }
+    virtual IOutputMetaData * queryDiskRecordSize() { return &mx1; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void onCreate(ICodeContext * _ctx, IHThorArg *, MemoryBuffer * in) {
+        ctx = _ctx;
+        ctx->getResultString(v2,v1.refstr(),"searchname",4294967295U);
+    }
+    rtlDataAttr v1;
+    unsigned v2;
+    virtual const char * getFileName() {
+        return "names";
+    }
+    virtual void createSegmentMonitors(IIndexReadContext *irc) {
+        Owned<IStringSet> set3;
+        set3.setown(createRtlStringSet(40));
+        char v4[40];
+        rtlStrToStr(40U,v4,v2,v1.getstr());
+        if (rtlCompareStrStr(v2,v1.getstr(),40U,v4) == 0) {
+            set3->addRange(v4,v4);
+        }
+        irc->append(createKeySegmentMonitor(false, set3.getClear(), 0, 40));
+        irc->append(createWildKeySegmentMonitor(40, 80));
+    }
+    virtual size32_t transform(ARowBuilder & crSelf, const void * _left) {
+        crSelf.getSelf();
+        unsigned char * left = (unsigned char *)_left;
+        memcpy(crSelf.row() + 0U,left + 0U,120U);
+        return 120U;
+    }
+};

Some of the methods to highlight are:

a) onCreate() - common to all activities. It is called by the engine when the helper is first created, and allows the helper to cache information that does not change - in this case the name that is being searched for. b) getFileName() - determines the name of the index being read. c) createSegmentMonitors() - defines which columns to filter, and which values to match against. d) transform() - create the record to return from the activity. It controls which columns should be included from the index row in the output. (In this case all.)

Executing the graph

To execute a graph, the engine walks the activities in the graph xml and creates, in memory, a graph of implementation activities.

For each activity, the name of the helper factory is calculated from the activity number (e.g. fAc2 for activity 2). That function is imported from the loaded dll, and then called to create an instance of the generated helper class - in this case cAc2.

The engine then creates an instance of the class for implementing the activity, and passes the previously created helper object to the constructor. The engine uses the _kind attribute in the graph to determine which activity class should be used. E.g. In the example above activity 2 has a _kind of 77, which corresponds to TAKindexread. For an index-read activity roxie will create an instance of CRoxieServerIndexReadActivity. (The generated helper that is passed to the activity instance will implement IHThorIndexReadArg). The activity implementations may also extract other information from the xml for the activity - e.g. hints. Once all the activities are created the edge information is used to link inputs activities to output activities and add other dependencies.

Note: Any subgraph that is marked with <att name="rootGraph" value="1"/> is a root subgraph. An activity within a subgraph that has no outputs is called a 'sink' (and an activity without any inputs is called a 'source').

Executing a graph involves executing all the root subgraphs that it contains. All dependencies of the activities within the subgraph must be executed before a subgraph is executed. To execute a subgraph, the engine executes each of the sink activities on separate threads, and then waits for each of those threads to complete. Each sink activity lazily pulls input rows on demand from activities further up the graph, processes them and returns when complete.

(If you examine the code you will find that this is a simplification. The implementation for processing dependencies is more fine grained to ensure IF datasets, OUPUT(,UPDATE) and other ECL constructs are executed correctly.)

In our example the execution flows as follows:

  1. Only a single root subgraph, so need to execute that.
  2. The engine will execute activity 3 - the workunit-write activity (TAKworkunitwrite).
  3. The workunit-write activity will start its input.
  4. The index-read activity will call the helper functions to obtain the filename, resolve the index, and create the filter.
  5. The workunit-write activity requests a row from its input.
  6. The index-read finds the first row, and calls the helper's transform() method to create an output row.
  7. The workunit-write activity persists the row to a buffer (using the serializer provided by the IOutputMetaData interface implemented by the class mx1).
  8. Back to step 5, workunit-write reading a row from its input, until end of file is returned (notified as two consecutive NULL rows.
  9. Workunit-write commits the results and finishes.

The execution generally switches back and forth between the code in the engines, and the members of the generated helper classes.

There are some other details of query execution that are worth highlighting:

Child Queries

: Some activities perform complicated operations on child datasets of the input rows. E.g. remove all duplicate people who are marked as living at this address. This will create a "child query" in the graph - i.e. a nested graph within a subgraph, which may be executed each time a new input row is processed by the containing activity. (The graph of activities for each child query is created at the same time as the parent activity. The activity instances are reinitialised and re-executed for each input row processed by the parent activity to minimise the create-time overhead.)

Other helper functions

: The generated code contains other functions that are used to describe the meta information for the rows processed within the graph. E.g. the following class describes the output from the index read activity:

cpp
struct mi1 : public CFixedOutputMetaData {
+    inline mi1() : CFixedOutputMetaData(120) {}
+    virtual const RtlTypeInfo * queryTypeInfo() const { return &ty1; }
+} mx1;
This represents a fixed size row that occupies 120 bytes. The object
+returned by the queryTypeInfo() function provides information about
+the types of the fields:
+
cpp
const RtlStringTypeInfo ty2(0x4,40);
+const RtlFieldStrInfo rf1("name",NULL,&ty2);
+const RtlStringTypeInfo ty3(0x4,80);
+const RtlFieldStrInfo rf2("address",NULL,&ty3);
+const RtlFieldInfo * const tl4[] = { &rf1,&rf2, 0 };
+const RtlRecordTypeInfo ty1(0xd,120,tl4);
I.e. a string column, length 40 called "name", followed by a
+string column, length 80 called "address". The interface
+IOutputMetaData in eclhelper.hpp is key to understanding how the
+rows are processed.
+

Inline dataset operations

: The rule mentioned at the start - that the generated code does not contain any knowledge of how to perform a particular dataset operation - does have one notable exception. Some operations on child datasets are very simple to implement, and more efficient if they are implemented using inline C++ code. (The generated code is smaller, and they avoid the overhead of setting up a child graph.) Examples include filtering and aggregating column values from a child dataset.

The full code in the different engines is more complicated than the simplified process outlined above, especially when it comes to executing dependencies, but the broad outline is the same.

Appendix

More information on the work done in the code generator to create the workunit can be found in ecl/eclcc/DOCUMENTATION.rst.

The C++ code can be generated as a single C++ file or multiple files. The system defaults to multiple C++ files, so that they can be compiled in parallel (and to avoid problems some compilers have with very large files). When multipe C++ files are generated the metadata classes and workflow classes are generated in the main module, and the activities are generated in the files suffixed with a number. It may be easier to understand the generated code if it is in one place. In which case, compile your query with the option -fspanMultipleCpp=0. Use -fsaveCppTempFiles to ensure the C++ files are not deleted (the C++ files will appear as helpers in the workunit details).

Key types and interfaces from eclhelper.hpp

IEclProcess

: The interface that is used by the workflow engine to execute the different workflow items in the generated code.

ThorActivityKind

: This enumeration contains one entry for each activity supported by the engines.

ICodeContext

: This interface is implemented by the engine, and provides a mechanism for the generated code to call back into the engine. For example resolveChildQuery() is used to obtain a reference to a child query that can then be executed later.

IOutputMetaData

: This interface is used to describe any meta data associated with the data being processed by the queries.

IHThorArg

: The base interface for defining information about an activity. Each activity has an associated interface that is derived from this interface. E.g. each instance of the sort activity will have a helper class implementing IHThorSortArg in the generated query. There is normally a corresponding base class for each interface in eclhelper_base.hpp that is used by the generated code e.g. CThorSortArg.

ARowBuilder

: This abstract base class is used by the transform functions to reserve memory for the rows that are created.

IEngineRowAllocator

: Used by the generated code to allocate rows and rowsets. Can also be used to release rows (or call the global function rtlReleaseRow()).

IGlobalCodeContext

: Provides access to functions that cannot be called inside a graph - i.e. can only be called from the global workflow code. Most functions are related to the internal implementation of particular workflow item types (e.g. persists).

Glossary

activity

: An activity is the basic unit of dataset processing implemented by the engines. Each activity corresponds to a node in the thor execution graph. Instances of the activities are connnected together to create the graph.

dll

: A dynamically loaded library. These correspond to shared objects in Linux (extension '.so'), dynamic libraries in Max OS X ('.dylib'), and dynamic link libraries in windows ('.dll').

superfile

: A composite file which allows a collection of files to be treated as a single compound file.

?What else should go here?

Full text of the workunit XML

The XML for a workunit can be viewed on the XML tag in eclwatch, or generated by compiling the ECL using the -wu option with eclcc. Alternatively eclcc -b -S can be used to generate the XML and the C++ at the same time (the output filenames are derived from the input name).

xml
<W_LOCAL buildVersion="internal_5.3.0-closedown0"
+    cloneable="1"
+    codeVersion="157"
+    eclVersion="5.3.0"
+    hash="2344844820"
+    state="completed"
+    xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
+    <Debug>
+        <addtimingtoworkunit>0</addtimingtoworkunit>
+        <debugnlp>1</debugnlp>
+        <expandpersistinputdependencies>1</expandpersistinputdependencies>
+        <forcegenerate>1</forcegenerate>
+        <noterecordsizeingraph>1</noterecordsizeingraph>
+        <regressiontest>1</regressiontest>
+        <showmetaingraph>1</showmetaingraph>
+        <showrecordcountingraph>1</showrecordcountingraph>
+        <spanmultiplecpp>0</spanmultiplecpp>
+        <targetclustertype>hthor</targetclustertype>
+    </Debug>
+    <Graphs>
+        <Graph name="graph1" type="activities">
+            <xgmml>
+                <graph wfid="2">
+                    <node id="1">
+                        <att>
+                            <graph>
+                                <att name="rootGraph" value="1" />
+                                <edge id="2_0" source="2" target="3" />
+                                <node id="2" label="Index Read&#10;&apos;names&apos;">
+                                    <att name="definition" value="workuniteg1.ecl(3,1)" />
+                                    <att name="name" value="results" />
+                                    <att name="_kind" value="77" />
+                                    <att name="ecl"
+                                        value="INDEX({ string40 name, string80 address }, &apos;names&apos;, fileposition(false));&#10;FILTER(KEYED(name = STORED(&apos;searchname&apos;)));&#10;" />
+                                    <att name="recordSize" value="120" />
+                                    <att name="predictedCount" value="0..?[disk]" />
+                                    <att name="_fileName" value="names" />
+                                </node>
+                                <node id="3" label="Output&#10;Result #1">
+                                    <att name="definition" value="workuniteg1.ecl(4,1)" />
+                                    <att name="_kind" value="16" />
+                                    <att name="ecl" value="OUTPUT(..., workunit);&#10;" />
+                                    <att name="recordSize" value="120" />
+                                </node>
+                            </graph>
+                        </att>
+                    </node>
+                </graph>
+            </xgmml>
+        </Graph>
+    </Graphs>
+    <Query fetchEntire="1">
+        <Associated>
+            <File desc="workuniteg1.ecl.cpp"
+                filename="c:\\regressout\\workuniteg1.ecl.cpp"
+                ip="10.121.159.73"
+                type="cpp" />
+        </Associated>
+    </Query>
+    <Results>
+        <Result isScalar="0"
+            name="Result 1"
+            recordSizeEntry="mf1"
+            rowLimit="-1"
+            sequence="0">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                name&#xe000;&#xe004;(&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;address&#xe000;&#xe004;P&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;%&#xe000;&#xe000;&#xe000;{
+                string40 name, string80 address };&#10; </SchemaRaw>
+        </Result>
+        <Result name="Result 2" sequence="1">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                Result_2&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;
+            </SchemaRaw>
+        </Result>
+    </Results>
+    <Statistics>
+        <Statistic c="eclcc"
+            count="1"
+            creator="eclcc"
+            kind="SizePeakMemory"
+            s="compile"
+            scope=">compile"
+            ts="1428933081084000"
+            unit="sz"
+            value="27885568" />
+    </Statistics>
+    <Variables>
+        <Variable name="searchname">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                searchname&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;
+            </SchemaRaw>
+        </Variable>
+    </Variables>
+    <Workflow>
+        <Item mode="normal"
+            state="null"
+            type="normal"
+            wfid="1" />
+        <Item mode="normal"
+            state="reqd"
+            type="normal"
+            wfid="2">
+            <Dependency wfid="1" />
+            <Schedule />
+        </Item>
+    </Workflow>
+</W_LOCAL>

Full contents of the generated C++ (as a single file)

cpp
/* Template for generating thor/hthor/roxie output */
+#include "eclinclude4.hpp"
+#include "eclrtl.hpp"
+#include "rtlkey.hpp"
+
+extern RTL_API void rtlStrToStr(size32_t lenTgt,void * tgt,size32_t lenSrc,const void * src);
+extern RTL_API int rtlCompareStrStr(size32_t lenL,const char * l,size32_t lenR,const char * r);
+
+
+const RtlStringTypeInfo ty2(0x4,40);
+const RtlFieldStrInfo rf1("name",NULL,&ty2);
+const RtlStringTypeInfo ty3(0x4,80);
+const RtlFieldStrInfo rf2("address",NULL,&ty3);
+const RtlFieldInfo * const tl4[] = { &rf1,&rf2, 0 };
+const RtlRecordTypeInfo ty1(0xd,120,tl4);
+void getLayout5(size32_t & __lenResult, void * & __result, IResourceContext * ctx) {
+    rtlStrToDataX(__lenResult,__result,87U,"\\000R\\000\\000\\000\\001x\\000\\000\\000\\002\\000\\000\\000\\003\\004\\000\\000\\000name\\004(\\000\\000\\000\\001ascii\\000\\001ascii\\000\\000\\000\\000\\000\\000\\003\\007\\000\\000\\000address\\004P\\000\\000\\000\\001ascii\\000\\001ascii\\000\\000\\000\\000\\000\\000\\002\\000\\000\\000");
+}
+struct mi1 : public CFixedOutputMetaData {
+    inline mi1() : CFixedOutputMetaData(120) {}
+    virtual const RtlTypeInfo * queryTypeInfo() const { return &ty1; }
+} mx1;
+extern "C" ECL_API IOutputMetaData * mf1() { mx1.Link(); return &mx1; }
+
+struct cAc2 : public CThorIndexReadArg {
+    virtual unsigned getFormatCrc() {
+        return 470622073U;
+    }
+    virtual bool getIndexLayout(size32_t & __lenResult, void * & __result) { getLayout5(__lenResult, __result, ctx); return true; }
+    virtual IOutputMetaData * queryDiskRecordSize() { return &mx1; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void onCreate(ICodeContext * _ctx, IHThorArg *, MemoryBuffer * in) {
+        ctx = _ctx;
+        ctx->getResultString(v2,v1.refstr(),"searchname",4294967295U);
+    }
+    rtlDataAttr v1;
+    unsigned v2;
+    virtual const char * getFileName() {
+        return "names";
+    }
+    virtual void createSegmentMonitors(IIndexReadContext *irc) {
+        Owned<IStringSet> set3;
+        set3.setown(createRtlStringSet(40));
+        char v4[40];
+        rtlStrToStr(40U,v4,v2,v1.getstr());
+        if (rtlCompareStrStr(v2,v1.getstr(),40U,v4) == 0) {
+            set3->addRange(v4,v4);
+        }
+        irc->append(createKeySegmentMonitor(false, set3.getClear(), 0, 40));
+        irc->append(createWildKeySegmentMonitor(40, 80));
+    }
+    virtual size32_t transform(ARowBuilder & crSelf, const void * _left) {
+        crSelf.getSelf();
+        unsigned char * left = (unsigned char *)_left;
+        memcpy(crSelf.row() + 0U,left + 0U,120U);
+        return 120U;
+    }
+};
+extern "C" ECL_API IHThorArg * fAc2() { return new cAc2; }
+struct cAc3 : public CThorWorkUnitWriteArg {
+    virtual int getSequence() { return 0; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void serializeXml(const byte * self, IXmlWriter & out) {
+        mx1.toXML(self, out);
+    }
+};
+extern "C" ECL_API IHThorArg * fAc3() { return new cAc3; }
+
+
+struct MyEclProcess : public EclProcess {
+    virtual unsigned getActivityVersion() const { return ACTIVITY_INTERFACE_VERSION; }
+    virtual int perform(IGlobalCodeContext * gctx, unsigned wfid) {
+        ICodeContext * ctx;
+        ctx = gctx->queryCodeContext();
+        switch (wfid) {
+            case 1U:
+                if (!gctx->isResult("searchname",4294967295U)) {
+                    ctx->setResultString("searchname",4294967295U,5U,"Smith");
+                }
+                break;
+            case 2U: {
+                ctx->executeGraph("graph1",false,0,NULL);
+                ctx->setResultString(0,1U,5U,"Done!");
+            }
+            break;
+        }
+        return 2U;
+    }
+};
+
+
+extern "C" ECL_API IEclProcess* createProcess()
+{
+
+    return new MyEclProcess;
+}
`,170)]))}const o=i(h,[["render",e]]);export{g as __pageData,o as default}; diff --git a/assets/devdoc_docs_ContributeDocs.md.B6UEh3AF.js b/assets/devdoc_docs_ContributeDocs.md.B6UEh3AF.js new file mode 100644 index 00000000000..2cce31b1926 --- /dev/null +++ b/assets/devdoc_docs_ContributeDocs.md.B6UEh3AF.js @@ -0,0 +1 @@ +import{_ as t,c as o,a3 as n,o as a}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"Contributing Documentation to the HPCC Systems Platform Project","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/docs/ContributeDocs.md","filePath":"devdoc/docs/ContributeDocs.md","lastUpdated":1731340314000}'),i={name:"devdoc/docs/ContributeDocs.md"};function r(s,e,l,d,u,h){return a(),o("div",null,e[0]||(e[0]=[n('

Contributing Documentation to the HPCC Systems Platform Project

This document is intended for anyone that wants to contribute documentation to our project. The first audience is platform developers, so we can streamline the process of documenting new features. However, these guidelines apply to anyone who wants to contribute to any of our documentation (Language Reference, Programmer’s Guide, etc.).

This set of guidelines should help you understand the information needed to create sufficient documentation for any new feature you add. The good news is that we are all here to help you and support your writing efforts. We will help by advising you along the way, and by reviewing and editing your submissions.

Documenting a New Software Feature--Required and Optional Components

When you create a new feature or function, clear documentation is crucial for both internal teams and external users. You worked hard on the feature, so it deserves proper notice and usage.

Contributions to the platform are always welcome, and we strongly encourage developers and users to contribute documentation.

You can contribute on many levels:

  1. Developer Notes

  2. End user “Readmes” in the form of MD files in the GitHub repository

  3. Blogs

  4. Formal documentation

Regardless of the form you are planning to deliver, here are the required and optional components to include in a document.

Tip: VS Code is very good at editing MD files. There is a built-in Preview panel available to be able to see the rendered form.

In addition, GitHub Copilot is MD-aware and can help you write and format. For example, you can ask the Copilot, “How can I align the content within the cells of my Markdown table?” GitHub copilot will show you the alignment options.

Required Components:

  1. Overview

    • What it is: Briefly describe the feature's purpose and the problem it solves.

    • Why it matters: Explain the value proposition for users and the overall impact on the software.

    • Target audience: Specify who this feature is designed for (for example, all users or specific user roles).

    • Use Cases: Provide concrete examples of how a user might leverage this feature in a real-world scenario.

  2. Installation and Configuration: Details on how to install and basic setup for use, if needed. This must include any system requirements or dependencies.

  3. User Guide / Functionality

    • How it works: Provide a task-oriented, step-by-step guide for using the feature. If possible, include screenshots for visual learners.

    • Tips, Tricks, and Techniques: Explain any shortcuts or clever uses of the feature that may be non-obvious.

    • Inputs and Outputs: Detail the information users need to provide to the feature and the format of the results.

    • Error Handling: Explain what happens if users encounter errors and how to troubleshoot common issues.

  4. Limitations and Considerations:

    • Limitations: Acknowledge any restrictions or boundaries associated with the feature's functionality.

Optional Components:

  1. Advanced Usage

    • Detailed configuration options: If the feature offers advanced settings or customizations, provide in-depth instructions for experienced users. This is similar to the way some options command line program's usage are only displayed using the verbose option.
  2. API Reference (for technical audiences)

    • Technical specifications: For features with an API component, include detailed API reference documentation for developers integrating it into their applications.
  3. FAQs

    • Frequently Asked Questions: Address any commonly anticipated user questions about the feature to pre-empt confusion.
  4. Additional Resources

    • Links to related documentation: Include links to relevant documentation for features that interact with this new addition.

    • Tutorials: Consider creating tutorials for a more interactive learning experience.

    • Videos: Consider requesting that a video be made to provide a more interactive visual learning experience. You should provide a simple script or outline of what should be shown in the video.

General Tips

  • Target your audience: Tailor the level of detail and technical jargon you use based on whether the documentation is for developers or end-users.

  • Clarity and Conciseness: Use clear, concise language and maintain a consistent structure for easy navigation. Always use present tense, active voice. Remember, you’re writing for users and programmers, not academics, so keep it simple and straightforward. See the HPCC Style Guide for additional guidance.

  • Visual Aids: Screenshots, diagrams, and flowcharts can significantly enhance understanding. A picture can communicate instantly what a thousand words cannot.

  • Maintain and Update: Regularly review and update documentation as the feature evolves or based on user feedback.

By following these guidelines and including the required and optional components, you can create comprehensive documentation that empowers users and streamlines the adoption of your new software feature.

Who should write it?

The boundary between a developer's responsibilities and the documentation team’s responsibility is not cast in stone. However, there are some guidelines that can help you decide what your responsibility is. Here are some examples:

Changing the default value of a configuration setting

This typically needs a simple one or two word change in the area of the documentation where that setting is documented. However, the change could impact existing deployments or existing code and therefore it might also require a short write-up for the Red Book and/or Release Announcement. If the setting is used by both bare-metal and containerized, you should provide information about how the new setting is used in each of those deployments.

Adding or modifying a Language keyword, Standard Library function, or command line tool action

This needs some changes to existing documentation so the best way to provide the information is in a documentation Jira issue. If it s a new keyword, function, or action, a brief overview should be included. For a Standard Library function, the developer should update the Javadoc comment in the appropriate ECL file. For a command line tool change, the developer should update the Usage section of the code.

Adding a new feature that requires an overview.

This is a candidate for either an MD file, a blog, or both. Since there should have been some sort of design specification document, that could easily be repurposed as a good start for this.

A feature/function that is only used internally to the system

Since this is information that is probably only of interest to other developers, a write-up in the form of an MD file in the repo is the best approach. If it affects end-users or operations, then a more formal document or a blog might be a good idea. If it affects existing deployments or existing code, then a Red Book notice might also be needed.

Extending the tests in the regression suite

New tests are frequently added and the regression suite readme files should be updated at the same time. If the tests are noteworthy, we could add a mention in the Platform Release Notes.

Placement

In general, it makes sense to keep simple documentation near the code. For example, a document about ECL Agent should go in the ECLAgent folder. However, there are times where that is either not possible or a document may cover more than one component. In those cases, there are a few options as shown below.

Other Folders

devdoc: This is a general folder for any developer document.

devdoc/docs: This is a folder for documents about documentation.

devdoc/userdoc: This is a collection of docs targeted toward the end-user rather than developers.

This is primarily for informal documents aimed at end-users. This info can and should be incorporated into the formal docs. devdoc/userdoc/troubleshoot: Information related to troubleshooting particular components

devdoc/userdoc/azure: Useful information about Azure Cloud portal and cli

devdoc/userdoc/roxie: Useful information for running roxie

devdoc/userdoc/thor: Useful information for running thor

devdoc/userdoc/blogs: COMING SOON: Location and storage of original text for blogs. It also has docs with guidelines and instructions on writing Blogs

Pull Requests

You can include your documentation with your code in a Pull Request or create a separate Jira and Pull Request for the documentation. This depends on the size of the code and doc. For a large project or change, a separate Pull request for the documentation is better. This might allow the code change to be merged faster.

Documentation Jira Issues

For minor code changes, for example the addition of a parameter to an existing ECL keyword, you can request a documentation change in a Jira issue. You should provide sufficient details in the Jira.

For example, If you add an optional parameter named Foo, you should provide details about what values can be passed in through the Foo parameter and what those values mean. You should also provide the default value used if the parameter is omitted.

',45)]))}const g=t(i,[["render",r]]);export{p as __pageData,g as default}; diff --git a/assets/devdoc_docs_ContributeDocs.md.B6UEh3AF.lean.js b/assets/devdoc_docs_ContributeDocs.md.B6UEh3AF.lean.js new file mode 100644 index 00000000000..2cce31b1926 --- /dev/null +++ b/assets/devdoc_docs_ContributeDocs.md.B6UEh3AF.lean.js @@ -0,0 +1 @@ +import{_ as t,c as o,a3 as n,o as a}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"Contributing Documentation to the HPCC Systems Platform Project","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/docs/ContributeDocs.md","filePath":"devdoc/docs/ContributeDocs.md","lastUpdated":1731340314000}'),i={name:"devdoc/docs/ContributeDocs.md"};function r(s,e,l,d,u,h){return a(),o("div",null,e[0]||(e[0]=[n('

Contributing Documentation to the HPCC Systems Platform Project

This document is intended for anyone that wants to contribute documentation to our project. The first audience is platform developers, so we can streamline the process of documenting new features. However, these guidelines apply to anyone who wants to contribute to any of our documentation (Language Reference, Programmer’s Guide, etc.).

This set of guidelines should help you understand the information needed to create sufficient documentation for any new feature you add. The good news is that we are all here to help you and support your writing efforts. We will help by advising you along the way, and by reviewing and editing your submissions.

Documenting a New Software Feature--Required and Optional Components

When you create a new feature or function, clear documentation is crucial for both internal teams and external users. You worked hard on the feature, so it deserves proper notice and usage.

Contributions to the platform are always welcome, and we strongly encourage developers and users to contribute documentation.

You can contribute on many levels:

  1. Developer Notes

  2. End user “Readmes” in the form of MD files in the GitHub repository

  3. Blogs

  4. Formal documentation

Regardless of the form you are planning to deliver, here are the required and optional components to include in a document.

Tip: VS Code is very good at editing MD files. There is a built-in Preview panel available to be able to see the rendered form.

In addition, GitHub Copilot is MD-aware and can help you write and format. For example, you can ask the Copilot, “How can I align the content within the cells of my Markdown table?” GitHub copilot will show you the alignment options.

Required Components:

  1. Overview

    • What it is: Briefly describe the feature's purpose and the problem it solves.

    • Why it matters: Explain the value proposition for users and the overall impact on the software.

    • Target audience: Specify who this feature is designed for (for example, all users or specific user roles).

    • Use Cases: Provide concrete examples of how a user might leverage this feature in a real-world scenario.

  2. Installation and Configuration: Details on how to install and basic setup for use, if needed. This must include any system requirements or dependencies.

  3. User Guide / Functionality

    • How it works: Provide a task-oriented, step-by-step guide for using the feature. If possible, include screenshots for visual learners.

    • Tips, Tricks, and Techniques: Explain any shortcuts or clever uses of the feature that may be non-obvious.

    • Inputs and Outputs: Detail the information users need to provide to the feature and the format of the results.

    • Error Handling: Explain what happens if users encounter errors and how to troubleshoot common issues.

  4. Limitations and Considerations:

    • Limitations: Acknowledge any restrictions or boundaries associated with the feature's functionality.

Optional Components:

  1. Advanced Usage

    • Detailed configuration options: If the feature offers advanced settings or customizations, provide in-depth instructions for experienced users. This is similar to the way some options command line program's usage are only displayed using the verbose option.
  2. API Reference (for technical audiences)

    • Technical specifications: For features with an API component, include detailed API reference documentation for developers integrating it into their applications.
  3. FAQs

    • Frequently Asked Questions: Address any commonly anticipated user questions about the feature to pre-empt confusion.
  4. Additional Resources

    • Links to related documentation: Include links to relevant documentation for features that interact with this new addition.

    • Tutorials: Consider creating tutorials for a more interactive learning experience.

    • Videos: Consider requesting that a video be made to provide a more interactive visual learning experience. You should provide a simple script or outline of what should be shown in the video.

General Tips

  • Target your audience: Tailor the level of detail and technical jargon you use based on whether the documentation is for developers or end-users.

  • Clarity and Conciseness: Use clear, concise language and maintain a consistent structure for easy navigation. Always use present tense, active voice. Remember, you’re writing for users and programmers, not academics, so keep it simple and straightforward. See the HPCC Style Guide for additional guidance.

  • Visual Aids: Screenshots, diagrams, and flowcharts can significantly enhance understanding. A picture can communicate instantly what a thousand words cannot.

  • Maintain and Update: Regularly review and update documentation as the feature evolves or based on user feedback.

By following these guidelines and including the required and optional components, you can create comprehensive documentation that empowers users and streamlines the adoption of your new software feature.

Who should write it?

The boundary between a developer's responsibilities and the documentation team’s responsibility is not cast in stone. However, there are some guidelines that can help you decide what your responsibility is. Here are some examples:

Changing the default value of a configuration setting

This typically needs a simple one or two word change in the area of the documentation where that setting is documented. However, the change could impact existing deployments or existing code and therefore it might also require a short write-up for the Red Book and/or Release Announcement. If the setting is used by both bare-metal and containerized, you should provide information about how the new setting is used in each of those deployments.

Adding or modifying a Language keyword, Standard Library function, or command line tool action

This needs some changes to existing documentation so the best way to provide the information is in a documentation Jira issue. If it s a new keyword, function, or action, a brief overview should be included. For a Standard Library function, the developer should update the Javadoc comment in the appropriate ECL file. For a command line tool change, the developer should update the Usage section of the code.

Adding a new feature that requires an overview.

This is a candidate for either an MD file, a blog, or both. Since there should have been some sort of design specification document, that could easily be repurposed as a good start for this.

A feature/function that is only used internally to the system

Since this is information that is probably only of interest to other developers, a write-up in the form of an MD file in the repo is the best approach. If it affects end-users or operations, then a more formal document or a blog might be a good idea. If it affects existing deployments or existing code, then a Red Book notice might also be needed.

Extending the tests in the regression suite

New tests are frequently added and the regression suite readme files should be updated at the same time. If the tests are noteworthy, we could add a mention in the Platform Release Notes.

Placement

In general, it makes sense to keep simple documentation near the code. For example, a document about ECL Agent should go in the ECLAgent folder. However, there are times where that is either not possible or a document may cover more than one component. In those cases, there are a few options as shown below.

Other Folders

devdoc: This is a general folder for any developer document.

devdoc/docs: This is a folder for documents about documentation.

devdoc/userdoc: This is a collection of docs targeted toward the end-user rather than developers.

This is primarily for informal documents aimed at end-users. This info can and should be incorporated into the formal docs. devdoc/userdoc/troubleshoot: Information related to troubleshooting particular components

devdoc/userdoc/azure: Useful information about Azure Cloud portal and cli

devdoc/userdoc/roxie: Useful information for running roxie

devdoc/userdoc/thor: Useful information for running thor

devdoc/userdoc/blogs: COMING SOON: Location and storage of original text for blogs. It also has docs with guidelines and instructions on writing Blogs

Pull Requests

You can include your documentation with your code in a Pull Request or create a separate Jira and Pull Request for the documentation. This depends on the size of the code and doc. For a large project or change, a separate Pull request for the documentation is better. This might allow the code change to be merged faster.

Documentation Jira Issues

For minor code changes, for example the addition of a parameter to an existing ECL keyword, you can request a documentation change in a Jira issue. You should provide sufficient details in the Jira.

For example, If you add an optional parameter named Foo, you should provide details about what values can be passed in through the Foo parameter and what those values mean. You should also provide the default value used if the parameter is omitted.

',45)]))}const g=t(i,[["render",r]]);export{p as __pageData,g as default}; diff --git a/assets/devdoc_docs_DocTemplate.md.DO145SYO.js b/assets/devdoc_docs_DocTemplate.md.DO145SYO.js new file mode 100644 index 00000000000..8ec8046024c --- /dev/null +++ b/assets/devdoc_docs_DocTemplate.md.DO145SYO.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as o,o as r}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Feature Documentation Template","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/docs/DocTemplate.md","filePath":"devdoc/docs/DocTemplate.md","lastUpdated":1731340314000}'),i={name:"devdoc/docs/DocTemplate.md"};function n(s,e,l,u,c,p){return r(),a("div",null,e[0]||(e[0]=[o('

Feature Documentation Template

Use this template to create new documentation for a feature, function, or process. Not every feature will require all six sections. Delete the ones that are not applicaple.

Overview

[In this section, provide a brief introduction to the new feature, highlighting its purpose and benefits.]

Setup & Configuration

[If applicable, explain the steps required to set up and configure the new feature, including any dependencies or prerequisites.]

User Guide

[Provide detailed instructions on how to use the new feature, including its functionality, options, and any relevant examples.]

API/CLI/Parameter Reference

[If applicable, document the references for the new feature, including any classes, methods, or parameters.]

Tutorial

[Offer a step-by-step tutorial on how to implement or utilize the new feature, with code snippets or screenshots if appropriate.]

Troubleshooting

[Address common issues or errors that users may encounter while using the new feature, along with possible solutions or workarounds.]

',14)]))}const m=t(i,[["render",n]]);export{h as __pageData,m as default}; diff --git a/assets/devdoc_docs_DocTemplate.md.DO145SYO.lean.js b/assets/devdoc_docs_DocTemplate.md.DO145SYO.lean.js new file mode 100644 index 00000000000..8ec8046024c --- /dev/null +++ b/assets/devdoc_docs_DocTemplate.md.DO145SYO.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as o,o as r}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Feature Documentation Template","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/docs/DocTemplate.md","filePath":"devdoc/docs/DocTemplate.md","lastUpdated":1731340314000}'),i={name:"devdoc/docs/DocTemplate.md"};function n(s,e,l,u,c,p){return r(),a("div",null,e[0]||(e[0]=[o('

Feature Documentation Template

Use this template to create new documentation for a feature, function, or process. Not every feature will require all six sections. Delete the ones that are not applicaple.

Overview

[In this section, provide a brief introduction to the new feature, highlighting its purpose and benefits.]

Setup & Configuration

[If applicable, explain the steps required to set up and configure the new feature, including any dependencies or prerequisites.]

User Guide

[Provide detailed instructions on how to use the new feature, including its functionality, options, and any relevant examples.]

API/CLI/Parameter Reference

[If applicable, document the references for the new feature, including any classes, methods, or parameters.]

Tutorial

[Offer a step-by-step tutorial on how to implement or utilize the new feature, with code snippets or screenshots if appropriate.]

Troubleshooting

[Address common issues or errors that users may encounter while using the new feature, along with possible solutions or workarounds.]

',14)]))}const m=t(i,[["render",n]]);export{h as __pageData,m as default}; diff --git a/assets/devdoc_docs_HPCCStyleGuide.md.ttTlrCG0.js b/assets/devdoc_docs_HPCCStyleGuide.md.ttTlrCG0.js new file mode 100644 index 00000000000..bb353876463 --- /dev/null +++ b/assets/devdoc_docs_HPCCStyleGuide.md.ttTlrCG0.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as s}from"./chunks/framework.DkhCEVKm.js";const d=JSON.parse('{"title":"HPCC Writing Style Guide","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/docs/HPCCStyleGuide.md","filePath":"devdoc/docs/HPCCStyleGuide.md","lastUpdated":1731340314000}'),a={name:"devdoc/docs/HPCCStyleGuide.md"};function n(r,e,l,p,u,h){return s(),i("div",null,e[0]||(e[0]=[o('

HPCC Writing Style Guide

This section covers the best practice information for writing and contributing to the HPCC Systems® Platform documentation.

General

We strive to maintain a consistent voice. These guidelines can help your writing match that voice.

  • Use present tense, active voice. Documentation should be you speaking directly to the reader. Simply tell them what to do.

    This example sentence: "The user selects the file menu." is passive voice. You wouldn’t say it that way in a conversation.

    You should use active voice wording, such as: "Select the file menu".

    Similarly, instructions like these are active voice: "Press the button" or "Submit the file".

    Documentation is you instructing the user. Just tell them what to do.

  • Be Brief and keep it simple. Be efficient with your words. Keep sentences short and concise. Keep paragraphs short as well. Use just a few sentences per paragraph. Use simple words wherever possible and try to avoid lengthy explanations.

  • Consistency: Be consistent. Use the same voice across all documents. Use the same term, use the same spelling, punctuation, etc. Follow the conventions in this guide.

  • When writing formal documentation that is more than a new feature announcement, do not refer to a new feature, or coming features as such, this does not hold up well over time.

    Do not refer to how things were done in the past. For example, "in the past we had to do X-Y-Z steps and now we no longer have to. This ‘new feature’ can do it in one step, Z". This only adds potential confusion. Just instruct on exactly what needs to be done now, using words efficiently as possible, so for this example just say “perform step Z”

Terms

There are many terms specific to HPCC Systems® and the HPCC Systems platform. Use the following style guide for word usage and capitalization guidelines to use when referring to system components or other HPCC-specific entities. Maintain consistent usage throughout all docs.

HPCC Systems®

Officially and legally the organizaion's name is HPCC Systems® and it is a registered trademark.

You should always refer to the platform as the HPCC Systems® platform and the registration mark ® should appear in the first and most prominent mention of the name.

While it is acceptable to use the ® anywhere in a document, it is required to be used in the first and most prominent mention - so the average reader will be aware. Any usage after that first and most prominent is optional.

Components and Tools

  • HPCC Systems Platform

  • Dali

  • Sasha

  • Thor

  • hThor

  • ROXIE

  • DFU Server (Distributed File Utility Server)

  • ESP Server (Enterprise Server Platform)

  • ESP Services

  • WsECL

  • ECL Watch

  • ECL Server

  • ECLCC Server

  • ECL Agent

  • ECL IDE

  • ECL Plug-in for Eclipse

  • ECL Playground

  • LDAP

  • dafilesrv

  • VS Code (no hyphen)

  • ECL Language Extension for VS Code

  • Configuration Manager (not ConfigMgr or ConfigManager)

Note: when referring to the startup command, use configmgr (always lowercase)

Other Terms

  • HPCCSystems.com or http://HPCCSystems.com (do not include the www portion)

  • ECL (Enterprise Control Language)

  • ECL command-line interface

Note: when referring to the ECL command-line tool command, ecl is lowercase

  • DFU Plus command-line interface
  • cloud-native (always hyphenated when used as an adjective)
  • DFU Workunits

  • ECL Workunits

  • Workunit

  • WUID

  • HPCC Systems®

  • multi-node

  • Superfiles, subfiles

  • package map

  • The username is the (usually unique) thing you type in with your password, for example: bobsmith66.

  • The user name is the name of the user, the user's real-life name, for example: Bob Smith.

Common Documentation terms

Use the following conventions for these commonly used terms:

  • right-click

  • double-click

  • drag-and-drop

  • click-and-drag

  • plug-in

  • drop list

  • bare metal (no hyphen)

  • blue/green (not blue-green)

  • Common Vulnerabilities and Exposures (CVEs)

Usage Instructions

  • You click a link.

  • You select a tab.

  • You press a button.

  • You check a (check)box.

Word Choices

Write up vs Write-up

Hyphenated when used as a noun. No Hyphen when used as a verb phrase.

Examples:

  • Did you read the write-up?

  • Would you write up the steps to reproduce?

Assure vs ensure vs insure

To “assure” a person of something is to make him or her confident of it.

According to the Associated Press style, to “ensure” that something happens is to make certain that it does, and to “insure” is to issue an insurance policy.

Other authorities, however, consider “ensure” and “insure” interchangeable. We prefer ensure when it is not talking about insurance.

If you have any questions, feel free to contact us at docfeedback@hpccsystems.com

',35)]))}const m=t(a,[["render",n]]);export{d as __pageData,m as default}; diff --git a/assets/devdoc_docs_HPCCStyleGuide.md.ttTlrCG0.lean.js b/assets/devdoc_docs_HPCCStyleGuide.md.ttTlrCG0.lean.js new file mode 100644 index 00000000000..bb353876463 --- /dev/null +++ b/assets/devdoc_docs_HPCCStyleGuide.md.ttTlrCG0.lean.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as s}from"./chunks/framework.DkhCEVKm.js";const d=JSON.parse('{"title":"HPCC Writing Style Guide","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/docs/HPCCStyleGuide.md","filePath":"devdoc/docs/HPCCStyleGuide.md","lastUpdated":1731340314000}'),a={name:"devdoc/docs/HPCCStyleGuide.md"};function n(r,e,l,p,u,h){return s(),i("div",null,e[0]||(e[0]=[o('

HPCC Writing Style Guide

This section covers the best practice information for writing and contributing to the HPCC Systems® Platform documentation.

General

We strive to maintain a consistent voice. These guidelines can help your writing match that voice.

  • Use present tense, active voice. Documentation should be you speaking directly to the reader. Simply tell them what to do.

    This example sentence: "The user selects the file menu." is passive voice. You wouldn’t say it that way in a conversation.

    You should use active voice wording, such as: "Select the file menu".

    Similarly, instructions like these are active voice: "Press the button" or "Submit the file".

    Documentation is you instructing the user. Just tell them what to do.

  • Be Brief and keep it simple. Be efficient with your words. Keep sentences short and concise. Keep paragraphs short as well. Use just a few sentences per paragraph. Use simple words wherever possible and try to avoid lengthy explanations.

  • Consistency: Be consistent. Use the same voice across all documents. Use the same term, use the same spelling, punctuation, etc. Follow the conventions in this guide.

  • When writing formal documentation that is more than a new feature announcement, do not refer to a new feature, or coming features as such, this does not hold up well over time.

    Do not refer to how things were done in the past. For example, "in the past we had to do X-Y-Z steps and now we no longer have to. This ‘new feature’ can do it in one step, Z". This only adds potential confusion. Just instruct on exactly what needs to be done now, using words efficiently as possible, so for this example just say “perform step Z”

Terms

There are many terms specific to HPCC Systems® and the HPCC Systems platform. Use the following style guide for word usage and capitalization guidelines to use when referring to system components or other HPCC-specific entities. Maintain consistent usage throughout all docs.

HPCC Systems®

Officially and legally the organizaion's name is HPCC Systems® and it is a registered trademark.

You should always refer to the platform as the HPCC Systems® platform and the registration mark ® should appear in the first and most prominent mention of the name.

While it is acceptable to use the ® anywhere in a document, it is required to be used in the first and most prominent mention - so the average reader will be aware. Any usage after that first and most prominent is optional.

Components and Tools

  • HPCC Systems Platform

  • Dali

  • Sasha

  • Thor

  • hThor

  • ROXIE

  • DFU Server (Distributed File Utility Server)

  • ESP Server (Enterprise Server Platform)

  • ESP Services

  • WsECL

  • ECL Watch

  • ECL Server

  • ECLCC Server

  • ECL Agent

  • ECL IDE

  • ECL Plug-in for Eclipse

  • ECL Playground

  • LDAP

  • dafilesrv

  • VS Code (no hyphen)

  • ECL Language Extension for VS Code

  • Configuration Manager (not ConfigMgr or ConfigManager)

Note: when referring to the startup command, use configmgr (always lowercase)

Other Terms

  • HPCCSystems.com or http://HPCCSystems.com (do not include the www portion)

  • ECL (Enterprise Control Language)

  • ECL command-line interface

Note: when referring to the ECL command-line tool command, ecl is lowercase

  • DFU Plus command-line interface
  • cloud-native (always hyphenated when used as an adjective)
  • DFU Workunits

  • ECL Workunits

  • Workunit

  • WUID

  • HPCC Systems®

  • multi-node

  • Superfiles, subfiles

  • package map

  • The username is the (usually unique) thing you type in with your password, for example: bobsmith66.

  • The user name is the name of the user, the user's real-life name, for example: Bob Smith.

Common Documentation terms

Use the following conventions for these commonly used terms:

  • right-click

  • double-click

  • drag-and-drop

  • click-and-drag

  • plug-in

  • drop list

  • bare metal (no hyphen)

  • blue/green (not blue-green)

  • Common Vulnerabilities and Exposures (CVEs)

Usage Instructions

  • You click a link.

  • You select a tab.

  • You press a button.

  • You check a (check)box.

Word Choices

Write up vs Write-up

Hyphenated when used as a noun. No Hyphen when used as a verb phrase.

Examples:

  • Did you read the write-up?

  • Would you write up the steps to reproduce?

Assure vs ensure vs insure

To “assure” a person of something is to make him or her confident of it.

According to the Associated Press style, to “ensure” that something happens is to make certain that it does, and to “insure” is to issue an insurance policy.

Other authorities, however, consider “ensure” and “insure” interchangeable. We prefer ensure when it is not talking about insurance.

If you have any questions, feel free to contact us at docfeedback@hpccsystems.com

',35)]))}const m=t(a,[["render",n]]);export{d as __pageData,m as default}; diff --git a/assets/devdoc_newActivity.md.Cwh2eRu1.js b/assets/devdoc_newActivity.md.Cwh2eRu1.js new file mode 100644 index 00000000000..f3621115ec6 --- /dev/null +++ b/assets/devdoc_newActivity.md.Cwh2eRu1.js @@ -0,0 +1,11 @@ +import{_ as t,c as i,a3 as a,o}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Quantile 1 - What is it?","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/newActivity.md","filePath":"devdoc/newActivity.md","lastUpdated":1731340314000}'),s={name:"devdoc/newActivity.md"};function n(r,e,l,h,d,c){return o(),i("div",null,e[0]||(e[0]=[a(`

Quantile 1 - What is it?

This series of blog posts started life as a series of walk-throughs and brainstorming sessions at a team offsite. This series will look at adding a new activity to the system. The idea is to give an walk through of the work involved, to highlight the different areas that need changing, and hopefully encourage others to add their own activities. In parallel with the description in this blog there is a series of commits to the github repository that correspond to the different stages in adding the activity. Once the blog is completed, the text will also be checked into the source control tree for future reference.

The new activity is going to be a QUANTILE activity, which can be used to find the records that split a dataset into equal sized blocks. Two common uses are to find the median of a set of data (split into 2) or percentiles (split into 100). It can also be used to split a dataset for distribution across the nodes in a system. One hope is that the classes used to implement quantile in Thor can also be used to improve the performance of the global sort operation.

It may seem fatuous, but the first task in adding any activity to the system is to work out what that activity is going to do! You can approach this in an iterative manner - starting with a minimal set of functionality and adding options as you think of them - or start with a more complete initial design. We have used both approaches in the past to add capabilities to the HPCC system, but on this occasion we will be starting from a more complete design - the conclusion of our initial design discussion:

"What are the inputs, options and capabilities that might be useful in a QUANTILE activity?"

The discussion produced the following items:

  • Which dataset is being processed?

    This is always required and should be the first argument to the activity.

  • How many parts to split the dataset into?

    This is always required, so it should be the next argument to the activity.

  • Which fields are being used to order (and split) the dataset?

    Again this is always required, so the list of fields should follow the number of partitions.

  • Which fields are returned?

    Normally the input row, but often it would be useful for the output to include details of which quantile a row corresponds to. To allow this an optional transform could be passed the input row as LEFT and the quantile number as COUNTER.

  • How about first and last rows in the dataset?

    Sometimes it is also useful to know the first and last rows. Add flags to allow them to be optionally returned.

  • How do you cope with too few input rows (including an empty input)?

    After some discussion we decided that QUANTILE should always return the number of parts requested. If there were fewer items in the input they would be duplicated as appropriate. We should provide a DEDUP flag for the situations when that is not desired. If there is an empty dataset as input then the default (blank) row will be created.

  • Should all rows have the same weighting?

    Generally you want the same weighting for each row. However, if you are using QUANTILE to split your dataset, and the cost of the next operation depends on some feature of the row (e.g., the frequency of the firstname) then you may want to weight the rows differently.

  • What if we are only interested in the 5th and 95th centiles?

    We could optionally allow a set of values to be selected from the results.

There were also some implementation details concluded from the discussions:

  • How accurate should the results be?

    The simplest implementation of QUANTILE (sort and then select the correct rows) will always produce accurate results. However, there may be some implementations that can produce an approximate answer more quickly. Therefore we could add a SKEW attribute to allow early termination.

  • Does the implementation need to be stable?

    In other words, if there are rows with identical values for the ordering fields, but other fields not part of the ordering with different values, does it matter which of those rows are returned? Does the relative order within those matching rows matter?

    The general principle in the HPCC system is that sort operations should be stable, and that where possible activities return consistent, reproducible results. However, that often has a cost - either in performance or memory consumption. The design discussion highlighted the fact that if all the fields from the row are included in the sort order then the relative order does not matter because the duplicate rows will be indistinguishable. (This is also true for sorts, and following the discussion an optimization was added to 5.2 to take advantage of this.) For the QUANTILE activity we will add an ECL flag, but the code generator should also aim to spot this automatically.

  • Returning counts of the numbers in each quantile might be interesting.

    This has little value when the results are exact, but may be more useful when a SKEW is specified to allow an approximate answer, or if a dataset might have a vast numbers of duplicates. It is possibly something to add to a future version of the activity. For an approximate answer, calculating the counts is likely to add an additional cost to the implementation, so the target engine should be informed if this is required.

  • Is the output always sorted by the partition fields?

    If this naturally falls out of the implementations then it would be worth including it in the specification. Initially we will assume not, but will revisit after it has been implemented.

After all the discussions we arrived at the following syntax:

QUANTILE(<dataset>, <number-of-ranges>, { sort-order } [, <transform>(LEFT, COUNTER)]
+        [,FIRST][,LAST][,SKEW(<n>)][,UNSTABLE][,SCORE(<score>)][,RANGE(set)][,DEDUP][,LOCAL]
+
+FIRST - Match the first row in the input dataset (as quantile 0)
+LAST -  Match the last row in the input dataset (as quantile <n>)
+SKEW -  The maximum deviation from the correct results allowed.  Defaults to 0.
+UNSTABLE - Is the order of the original input values unimportant?
+SCORE - What weighting should be applied for each row.  Defaults to 1.
+RANGE - Which quantiles should actually be returned.  (Defaults to ALL).
+DEDUP - Avoid returning a match for an input row more than once.
+

We also summarised a few implementation details:

  • The activity needs to be available in GLOBAL, LOCAL and GROUPED variants.
  • The code generator should derive UNSTABLE if no non-sort fields are returned.
  • Flags to indicate if a score/range is required.
  • Flag to indicate if a transform is required.

Finally, deciding on the name of the activity took almost as long as designing it!

The end result of this process was summarised in a JIRA issue: https://track.hpccsystems.com/browse/HPCC-12267, which contains details of the desired syntax and semantics. It also contains some details of the next blog topic - test cases.

Incidentally, a question that arose from of the design discussion was "What ECL can we use if we want to annotate a dataset with partition points?". Ideally the user needs a join activity which walks through a table of rows, and matches against the first row that contains key values less than or equal to the values in the search row. There are other situations where that operation would also be useful. Our conclusion was that the system does not have a simple way to achieve that, and that it was a deficiency in the current system, so another JIRA was created (see https://track.hpccsystems.com/browse/HPCC-13016). This is often how the design discussions proceed, with discussions in one area leading to new ideas in another. Similarly we concluded it would be useful to distribute rows in a dataset based on a partition (see https://track.hpccsystems.com/browse/HPCC-13260).

Quantile 2 - Test cases

When adding new features to the system, or changing the code generator, the first step is often to write some ECL test cases. They have proved very useful for several reasons:

  • Developing the test cases can help clarify issues, and other details that the implementation needs to take into account. (E.g., what happens if the input dataset is empty?)
  • They provide something concrete to aim towards when implementing the feature.
  • They provide a set of milestones to show progress.
  • They can be used to check the implementation on the different engines.

As part of the design discussion we also started to create a list of useful test cases (they follow below in the order they were discussed). The tests perform varying functions. Some of the tests are checking that the core functionality works correctly, while others check unusual situations and that strange boundary cases are covered. The tests are not exhaustive, but they are a good starting point and new tests can be added as the implementation progresses.

The following is the list of tests that should be created as part of implementing this activity:

  1. Compare with values extracted from a SORT. Useful to check the implementation, but also to ensure we clearly define which results we are expecting.
  2. QUANTILE with a number-of-ranges = 1, 0, and a very large number. Should also test the number of ranges can be dynamic as well as a constant.
  3. Empty dataset as input.
  4. All input entries are duplicates.
  5. Dataset smaller than number of ranges.
  6. Input sorted and reverse sorted.
  7. Normal data with small number of entries.
  8. Duplicates in the input dataset that cause empty ranges.
  9. Random distribution of numbers without duplicates.
  10. Local and grouped cases.
  11. SKEW that fails.
  12. Test scoring functions.
  13. Testing different skews that work on the same dataset.
  14. An example that uses all the keywords.
  15. Examples that do and do not have extra fields not included in the sort order. (Check that the unstable flag is correctly deduced.)
  16. Globally partitioned already (e.g., globally sorted). All partition points on a single node.
  17. Apply quantile to a dataset, and also to the same dataset that has been reordered/distributed. Check the resulting quantiles are the same.
  18. Calculate just the 5 and 95 centiles from a dataset.
  19. Check a non constant number of splits (and also in a child query where it depends on the parent row).
  20. A transform that does something interesting to the sort order. (Check any order is tracked correctly.)
  21. Check the counts are correct for grouped and local operations.
  22. Call in a child query with options that depend on the parent row (e.g., num partitions).
  23. Split points that fall in the middle of two items.
  24. No input rows and DEDUP attribute specified.

Ideally any test cases for features should be included in the runtime regression suite, which is found in the testing/regress directory in the github repository. Tests that check invalid syntax should go in the compiler regression suite (ecl/regress). Commit https://github.com/ghalliday/HPCC-Platform/commit/d75e6b40e3503f851265670a27889d8adc73f645 contains the test cases so far. Note, the test examples in that commit do not yet cover all the cases above. Before the final pull request for the feature is merged the list above should be revisited and the test suite extended to include any missing tests.

In practice it may be easier to write the test cases in parallel with implementing the parser -since that allows you to check their syntax. Some of the examples in the commit were created before work was started on the parser, others during, and some while implementing the feature itself.

Quantile 3 - The parser

The first stage in implementing QUANTILE will be to add it to the parser. This can sometimes highlight issues with the syntax and cause revisions to the design. In this case there were two technical issues integrating the syntax into the grammar. (If you are not interested in shift/reduce conflicts you may want to skip a few paragraphs and jump to the walkthrough of the changes.)

Originally, the optional transform was specified inside an attribute, e.g., something like OUTPUT(transform). However, this was not very consistent with the way that other transforms were implemented, so the syntax was updated so it became an optional transform following the partition field list.

When the syntax was added to the grammar we hit another problem: Currently, a single production (sortList) in the grammar is used for matching sort orders. As well as accepting fields from a dataset the sort order production has been extended to accept any named attribute that can follow a sort order (e.g., LOCAL). This is because (with one token lookahead) it is ambiguous where the sort order finishes and the list of attributes begins. Trying to include transforms in those productions revealed other problems:

  • If a production has a sortList followed by a transform (or attribute) then it introduces a shift/reduce error on ','. To avoid the ambiguity all trailing attributes or values need to be included in the sortList.
  • Including a transform production in the sortList elements causes problems with other transform disambiguation (e.g., DATASET[x] and AGGREGATE).
  • We could require an attribute around the transform e.g., OUTPUT(transform), but that does not really fit in with other activities in the language.
  • We could change the parameter order, e.g., move the transform earlier, but that would make the syntax counter-intuitive.
  • We could require { } around the list - but this is inconsistent with some of the other sort orders.

In order to make some progress I elected to choose the last option and require the sort order to be included in curly braces. There are already a couple of activities - subsort and a form of atmost that similarly require them (and if redesigning ECL from scratch I would be tempted to require them everywhere). The final syntax is something that will need further discussion as part of the review of the pull request though, and may need to be revisited.

Having decided how to solve the ambiguities in the grammar, the following is a walkthrough of the changes that were made as part of commit https://github.com/ghalliday/HPCC-Platform/commit/3d623d1c6cd151a0a5608aa20ae4739a008f6e44:

  • no_quantile in hqlexpr.hpp

    The ECL query is represented by a graph of "expression" nodes - each has a "kind" that comes from the enumeration _node_operator. The first requirement is to add a new enumeration to represent the new activity - in this case we elected to reuse an unused placeholder. (These placeholders correspond to some old operators that are no longer supported. They have not been removed because the other elements in the enumeration need to keep the same values since they are used for calculating derived persistent values e.g., the hashes for persists.)

  • New attribute names in hqlatoms.

    The quantile activity introduces some new attribute names that have not been used before. All names are represented in an atom table, so the code in hqlatoms.hpp/cpp is updated to define the new atoms.

  • Properties of no_quantile

    There are various places that need to be updated to allow the system to know about the properties of the new operator:

    • hqlattr

      This contains code to calculate derived attributes. The first entry in the case statement is currently unused (the function should be removed). The second, inside calcRowInformation(), is used to predict how many rows are generated by this activity. This information is percolated through the graph and is used for optimizations, and input counts can be used to select the best implementation for a particular activity.

    • hqlexpr

      Most changes are relatively simple including the text for the operator, whether it is constant, and the number of dataset arguments it has. One key function is getChildDatasetType() that indicates the kind of dataset arguments the operator has, which in turn controls how LEFT/RIGHT are interpreted. In this case some of the activity arguments (e.g., the number of quantiles) implicitly use fields within the parent dataset, and the transform uses LEFT, so the operator returns childdataset_datasetleft.

    • hqlir

      This entry is used for generating an intermediate representation of the graph. This can be useful for debugging issues. (Running eclcc with the logging options "--logfile xxx" and "--logdetail 999" will include details of the expression tree at each point in the code generation process in the log file. Also defining -ftraceIR will output the graphs in the IR format.)

    • hqlfold

      This is the constant folder. At the moment the only change is to ensure that fields that are assigned constants within the transform are processed correctly. Future work could add code to optimize quantile applied to an empty dataset, or selecting 1 division.

    • hqlmeta

      Similar to the functions in hqlattr that calculate derived attributes, these functions are used to calculate how the rows coming out of an activity are sorted, grouped and distributed. It is vital to only preserve information that is guaranteed to be true - otherwise invalid optimizations might be performed on the rest of the expression tree.

    • reservedwords.cpp

      A new entry indicating which category the keyword belongs to.

Finally we have the changes to the parser to recognise the new syntax:

  • hqllex.l

    This file contains the lexer that breaks the ecl file into tokens. There are two new tokens - QUANTILE and SCORE.

  • Hqlgram.y

    This file contains the grammar that matches the language. There are two productions - one that matches the version of QUANTILE with a transform and one without. (Two productions are used instead of an optional transform to avoid shift/reduce errors.)

  • hqlgram2.cpp

    This contains the bulk of the code that is executed by the productions in the grammar. Changes here include new entries added to a case statement to get the text for the new tokens, and a new entry in the simplify() call. This helps reduce the number of valid tokens that could follow when reporting a syntax error.

Looking back over those changes, one reflection is that there are lots of different places that need to be changed. How does a programmer know which functions need to change, and what happens if some are missed? In this example, the locations were found by searching for an activity with a similar syntax e.g., no_soapcall_ds or no_normalize.

It is too easy to miss something, especially for somebody new to the code - although if you do then you will trigger a runtime internal error. It would be much better if the code was refactored so that the bulk of the changes were in one place. (See JIRA https://track.hpccsystems.com/browse/HPCC-13434 that has been added to track improvement of the situation.)

With these changes implemented the examples from the previous pull request now syntax check. The next stage in the process involves thinking through the details of how the activity will be implemented.

Quantile 4 - The engine interface.

The next stage in adding a new activity to the system is to define the interface between the generated code and the engines. The important file for this stage is rtl/include/eclhelper.hpp, which contains the interfaces between the engines and the generated code. These interfaces define the information required by the engines to customize each of the different activities. The changes that define the interface for quantile are found in commit https://github.com/ghalliday/HPCC-Platform/commit/06534d8e9962637fe9a5188d1cc4ab32c3925010.

Adding a quantile activity involves the following changes:

  • ThorActivityKind - TAKquantile

    Each activity that the engines support has an entry in this enumeration. This value is stored in the graph as the _kind attribute of the node.

  • ActivityInterfaceEnum - TAIquantilearg_1

    This enumeration in combination with the selectInterface() member of IHThorArg provides a mechanism for helper interfaces to be extended while preserving backwards compatibility with older workunits. The mechanism is rarely used (but valuable when it is), and adding a new activity only requires a single new entry.

  • IHThorArg

    This is the base interface that all activity interfaces are derived from. This interface does not need to change, but it is worth noting because each activity defines a specialized version of it. The names of the specialised interfaces follow a pattern; in this case the new interface is IHThorQuantileArg.

  • IHThorQuantileArg

    The following is an outline of the new member functions, with comments on their use:

    • getFlags()

      Many of the interfaces have a getFlags() function. It provides a concise way of returning several Boolean options in a single call - provided those options do not change during the execution of the activity. The flags are normally defined with explicit values in an enumeration before the interface. The labels often follow the pattern T<First-letter-of-activity>F<lowercase-name>, i.e. TQFxxx ~= Thor-Quantile-Flag-XXX.

    • getNumDivisions()

      Returns how many parts to split the dataset into.

    • getSkew()

      Corresponds to the SKEW() attribute.

    • queryCompare()

      Returns an implementation of the interface used to compare two rows.

    • createDefault(rowBuilder)

      A function used to create a default row - used if there are no input rows.

    • transform(rowBuilder, _left, _counter)

      The function to create the output record from the input record and the partition number (passed as counter).

    • getScore(_left)

      What weighting should be given to this row?

    • getRange(isAll, tlen, tgt)

      Corresponds to the RANGE attribute.

Note that the different engines all use the same specialised interface - it contains a superset of the functions required by the different targets. Occasionally some of the engines do not need to use some of the functions (e.g., to serialize information between nodes) so the code generator may output empty implementations.

For each interface defined in eclhelper.hpp there is a base implementation class defined in eclhelper_base.hpp. The classes generated for each activity in a query by the code generator are derived from one of these base classes. Therefore we need to create a corresponding new class CThorQuantileArg. It often provides default implementations for some of the helper functions to help reduce the size of the generated code (e.g., getScore returning 1).

Often the process of designing the helper interface is dynamic. As the implementation is created, new options or possibilities for optimizations appear. These require extensions and changes to the helper interface in order to be implemented by the engines. Once the initial interface has been agreed, work on the code generator and the engines can proceeded in parallel. (It is equally possible to design this interface before any work on the parser begins, allowing more work to overlap.)

There are some more details on the contents of thorhelper.hpp in the documentation ecl/eclcc/WORKUNIT.rst within the HPCC repository.

Quantile 5 - The code generator

Adding a new activity to the code generator is (surprisingly!) a relatively simple operation. The process is more complicated if the activity also requires an implementation that generates inline C++, but that only applies to a small subset of very simple activities, e.g., filter, aggregate. Changes to the code generator also tend to be more substantial if you add a new type, but that is also not the case for the quantile activity.

For quantile, the only change required is to add a function that generates an implementation of the helper class. The code for all the different activities follows a very similar pattern - generate input activities, generate the helper for this activity, and link the input activities to this new activity. It is often easiest to copy the boiler-plate code from a similar activity (e.g., sort) and then adapt it. (Yes, some of this code could also be refactored... any volunteers?) There are a large number of helper functions available to help generate transforms and other member functions, which also simplifies the process.

The new code is found in commit https://github.com/ghalliday/HPCC-Platform/commit/47f850d827f1655fd6a78fb9c07f1e911b708175.

Most of the code should be self explanatory, but one item is worth highlighting. The code generator builds up a structure in memory that represents the C++ code that is being generated. The BuildCtx class is used to represent a location within that generated code where new code can be inserted. The instance variable contains several BuildCtx members that are used to represent locations to generate code within the helper class (classctx, nestedctx, createctx and startctx). They are used for different purposes:

  • classctx

    Used to generate any member functions that can be called as soon as the helper object has been created, e.g., getFlags().

  • nestedctx

    Used to generate nested member classes and objects - e.g., comparison classes.

  • startctx

    Any function that may return a value that depends on the context/parent activity. For example if QUANTILE is used inside the TRANSFORM of a PROJECT, the number of partition points may depend on a field in the LEFT row of the PROJECT. Therefore the getNumDivisions() member function needs to be generated inside instance->startctx. These functions can only be called by the engine after onCreate() and onStart() have been called to set up the current context.

  • createctx

    Really, this is a historical artefact from many years ago. It was originally used for functions that could be dependent on a global expression, but not a parent row. Almost all such restrictions have since been removed, and those that remain should probably be replaced with either classctx or startctx.

The only other change is to extend the switch statement in common/thorcommon/thorcommon.cpp to add a text description of the activity.

Quantile 6 - Roxie

With the code generator outputting all the information we need, we can now implement the activity in one of the engines. (As I mentioned previously, in practice this is often done in parallel with adding it to the code generator.) Roxie and hThor are the best engines to start with because most of their activities run on a single node - so the implementations tend to be less complicated. It is also relatively easy to debug them, by compiling to create a stand-alone executable, and then running that executable inside a debugger. The following description walks-through the roxie changes:

The changes have been split into two commits to make the code changes easier to follow. The first commit (https://github.com/ghalliday/HPCC-Platform/commit/30da006df9ae01c9aa784e91129457883e9bb8f3) adds the simplest implementation of the activity:

Code is added to ccdquery to process the new TAKquantile activity kind, and create a factory object of the correct type. The implementation of the factory class is relatively simple - it primarily contains a method for creating an instance of the activity class. Some factories create instances of the helper and cache any information that never changes (in this case the value returned by getFlags(), which is a very marginal optimization).

The classes that implement the existing sort algorithms are extended to return the sorted array in a single call. This allows the quicksort variants to be implemented more efficiently.

The class CRoxieServerQuantileActivity contains the code for implementing the quantile activity. It has the following methods:

  • Constructor

    Extracts any information from the helper that does not vary, and initializes all member variables.

  • start()

    This function is called before the graph is executed. It evaluates any helper methods that might vary from execution to execution (e.g., getRange(), numDivisions()), but which do not depend on the current row.

  • reset()

    Called when a graph has finished executing - after an activity has finished processing all its records. It is used to clean up any variables, and restore the activity ready for processing again (e.g., if it is inside a child query).

  • needsAllocator()

    Returns true if this activity creates new rows.

  • nextInGroup()

    The main function in the activity. This function is called by whichever activity is next in the graph to request a new row from the quantile activity. The functions should be designed so they return the next row as quickly as possible, and delay any processing until it is needed. In this case the input is not read and sorted until the first row is requested.

    Note, the call to the helper.transform() returns the size of the resulting row, and returns zero if the row should be skipped. The call to finaliseRowClear() after a successful row creation is there to indicate that the row can no longer be modified, and ensures that any child rows will be correctly freed when the row is freed.

    The function also contains extra logic to ensure that groups are implemented correctly. The end of a group is marked by returning a single NULL row, the end of the dataset by two contiguous NULL rows. It is important to ensure that a group that has all its output rows skipped doesn't return two NULLs in a row - hence the checks for anyThisGroup.

With those changes in place, the second commit https://github.com/ghalliday/HPCC-Platform/commit/aeaa209092ea1af9660c6908062c1b0b9acff36b adds support for the RANGE, FIRST, and LAST attributes. It also optimizes the cases where the input is already sorted, and the version of QUANTILE which does not include a transform. (If you are looking at the change in github then it is useful to ignore whitespace changes by appending ?w=1 to the URL). The main changes are

  • Extra helper methods called in start() to obtain the range.
  • Optimize the situation where the input is known to be sorted by reading the input rows directly into the "sorted" array.
  • Extra checks to see if this quantile should be included in the output (FIRST,LAST,RANGE,DEDUP)
  • An optimization to link the incoming row if the transform does not modify it, by testing the TQFneedtransform flag.

Quantile 7 - Possible roxie improvements

TBD...

hthor - trivial,sharing code and deprecated.

Discussion, of possible improvements.

Hoares' algorithm.

Ln2(n) < 4k?

SKEW and Hoares

Ordered RANGE. Calc offsets from the quantile (see testing/regress/ecl/xxxxx?)

SCORE

Quantile 8 - Thor

TBD

Basic activity structure

Locally sorting and allowing the inputs to spill.

The partitioning approach

Classes

Skew

Optimizations

`,78)]))}const m=t(s,[["render",n]]);export{u as __pageData,m as default}; diff --git a/assets/devdoc_newActivity.md.Cwh2eRu1.lean.js b/assets/devdoc_newActivity.md.Cwh2eRu1.lean.js new file mode 100644 index 00000000000..f3621115ec6 --- /dev/null +++ b/assets/devdoc_newActivity.md.Cwh2eRu1.lean.js @@ -0,0 +1,11 @@ +import{_ as t,c as i,a3 as a,o}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Quantile 1 - What is it?","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/newActivity.md","filePath":"devdoc/newActivity.md","lastUpdated":1731340314000}'),s={name:"devdoc/newActivity.md"};function n(r,e,l,h,d,c){return o(),i("div",null,e[0]||(e[0]=[a(`

Quantile 1 - What is it?

This series of blog posts started life as a series of walk-throughs and brainstorming sessions at a team offsite. This series will look at adding a new activity to the system. The idea is to give an walk through of the work involved, to highlight the different areas that need changing, and hopefully encourage others to add their own activities. In parallel with the description in this blog there is a series of commits to the github repository that correspond to the different stages in adding the activity. Once the blog is completed, the text will also be checked into the source control tree for future reference.

The new activity is going to be a QUANTILE activity, which can be used to find the records that split a dataset into equal sized blocks. Two common uses are to find the median of a set of data (split into 2) or percentiles (split into 100). It can also be used to split a dataset for distribution across the nodes in a system. One hope is that the classes used to implement quantile in Thor can also be used to improve the performance of the global sort operation.

It may seem fatuous, but the first task in adding any activity to the system is to work out what that activity is going to do! You can approach this in an iterative manner - starting with a minimal set of functionality and adding options as you think of them - or start with a more complete initial design. We have used both approaches in the past to add capabilities to the HPCC system, but on this occasion we will be starting from a more complete design - the conclusion of our initial design discussion:

"What are the inputs, options and capabilities that might be useful in a QUANTILE activity?"

The discussion produced the following items:

  • Which dataset is being processed?

    This is always required and should be the first argument to the activity.

  • How many parts to split the dataset into?

    This is always required, so it should be the next argument to the activity.

  • Which fields are being used to order (and split) the dataset?

    Again this is always required, so the list of fields should follow the number of partitions.

  • Which fields are returned?

    Normally the input row, but often it would be useful for the output to include details of which quantile a row corresponds to. To allow this an optional transform could be passed the input row as LEFT and the quantile number as COUNTER.

  • How about first and last rows in the dataset?

    Sometimes it is also useful to know the first and last rows. Add flags to allow them to be optionally returned.

  • How do you cope with too few input rows (including an empty input)?

    After some discussion we decided that QUANTILE should always return the number of parts requested. If there were fewer items in the input they would be duplicated as appropriate. We should provide a DEDUP flag for the situations when that is not desired. If there is an empty dataset as input then the default (blank) row will be created.

  • Should all rows have the same weighting?

    Generally you want the same weighting for each row. However, if you are using QUANTILE to split your dataset, and the cost of the next operation depends on some feature of the row (e.g., the frequency of the firstname) then you may want to weight the rows differently.

  • What if we are only interested in the 5th and 95th centiles?

    We could optionally allow a set of values to be selected from the results.

There were also some implementation details concluded from the discussions:

  • How accurate should the results be?

    The simplest implementation of QUANTILE (sort and then select the correct rows) will always produce accurate results. However, there may be some implementations that can produce an approximate answer more quickly. Therefore we could add a SKEW attribute to allow early termination.

  • Does the implementation need to be stable?

    In other words, if there are rows with identical values for the ordering fields, but other fields not part of the ordering with different values, does it matter which of those rows are returned? Does the relative order within those matching rows matter?

    The general principle in the HPCC system is that sort operations should be stable, and that where possible activities return consistent, reproducible results. However, that often has a cost - either in performance or memory consumption. The design discussion highlighted the fact that if all the fields from the row are included in the sort order then the relative order does not matter because the duplicate rows will be indistinguishable. (This is also true for sorts, and following the discussion an optimization was added to 5.2 to take advantage of this.) For the QUANTILE activity we will add an ECL flag, but the code generator should also aim to spot this automatically.

  • Returning counts of the numbers in each quantile might be interesting.

    This has little value when the results are exact, but may be more useful when a SKEW is specified to allow an approximate answer, or if a dataset might have a vast numbers of duplicates. It is possibly something to add to a future version of the activity. For an approximate answer, calculating the counts is likely to add an additional cost to the implementation, so the target engine should be informed if this is required.

  • Is the output always sorted by the partition fields?

    If this naturally falls out of the implementations then it would be worth including it in the specification. Initially we will assume not, but will revisit after it has been implemented.

After all the discussions we arrived at the following syntax:

QUANTILE(<dataset>, <number-of-ranges>, { sort-order } [, <transform>(LEFT, COUNTER)]
+        [,FIRST][,LAST][,SKEW(<n>)][,UNSTABLE][,SCORE(<score>)][,RANGE(set)][,DEDUP][,LOCAL]
+
+FIRST - Match the first row in the input dataset (as quantile 0)
+LAST -  Match the last row in the input dataset (as quantile <n>)
+SKEW -  The maximum deviation from the correct results allowed.  Defaults to 0.
+UNSTABLE - Is the order of the original input values unimportant?
+SCORE - What weighting should be applied for each row.  Defaults to 1.
+RANGE - Which quantiles should actually be returned.  (Defaults to ALL).
+DEDUP - Avoid returning a match for an input row more than once.
+

We also summarised a few implementation details:

  • The activity needs to be available in GLOBAL, LOCAL and GROUPED variants.
  • The code generator should derive UNSTABLE if no non-sort fields are returned.
  • Flags to indicate if a score/range is required.
  • Flag to indicate if a transform is required.

Finally, deciding on the name of the activity took almost as long as designing it!

The end result of this process was summarised in a JIRA issue: https://track.hpccsystems.com/browse/HPCC-12267, which contains details of the desired syntax and semantics. It also contains some details of the next blog topic - test cases.

Incidentally, a question that arose from of the design discussion was "What ECL can we use if we want to annotate a dataset with partition points?". Ideally the user needs a join activity which walks through a table of rows, and matches against the first row that contains key values less than or equal to the values in the search row. There are other situations where that operation would also be useful. Our conclusion was that the system does not have a simple way to achieve that, and that it was a deficiency in the current system, so another JIRA was created (see https://track.hpccsystems.com/browse/HPCC-13016). This is often how the design discussions proceed, with discussions in one area leading to new ideas in another. Similarly we concluded it would be useful to distribute rows in a dataset based on a partition (see https://track.hpccsystems.com/browse/HPCC-13260).

Quantile 2 - Test cases

When adding new features to the system, or changing the code generator, the first step is often to write some ECL test cases. They have proved very useful for several reasons:

  • Developing the test cases can help clarify issues, and other details that the implementation needs to take into account. (E.g., what happens if the input dataset is empty?)
  • They provide something concrete to aim towards when implementing the feature.
  • They provide a set of milestones to show progress.
  • They can be used to check the implementation on the different engines.

As part of the design discussion we also started to create a list of useful test cases (they follow below in the order they were discussed). The tests perform varying functions. Some of the tests are checking that the core functionality works correctly, while others check unusual situations and that strange boundary cases are covered. The tests are not exhaustive, but they are a good starting point and new tests can be added as the implementation progresses.

The following is the list of tests that should be created as part of implementing this activity:

  1. Compare with values extracted from a SORT. Useful to check the implementation, but also to ensure we clearly define which results we are expecting.
  2. QUANTILE with a number-of-ranges = 1, 0, and a very large number. Should also test the number of ranges can be dynamic as well as a constant.
  3. Empty dataset as input.
  4. All input entries are duplicates.
  5. Dataset smaller than number of ranges.
  6. Input sorted and reverse sorted.
  7. Normal data with small number of entries.
  8. Duplicates in the input dataset that cause empty ranges.
  9. Random distribution of numbers without duplicates.
  10. Local and grouped cases.
  11. SKEW that fails.
  12. Test scoring functions.
  13. Testing different skews that work on the same dataset.
  14. An example that uses all the keywords.
  15. Examples that do and do not have extra fields not included in the sort order. (Check that the unstable flag is correctly deduced.)
  16. Globally partitioned already (e.g., globally sorted). All partition points on a single node.
  17. Apply quantile to a dataset, and also to the same dataset that has been reordered/distributed. Check the resulting quantiles are the same.
  18. Calculate just the 5 and 95 centiles from a dataset.
  19. Check a non constant number of splits (and also in a child query where it depends on the parent row).
  20. A transform that does something interesting to the sort order. (Check any order is tracked correctly.)
  21. Check the counts are correct for grouped and local operations.
  22. Call in a child query with options that depend on the parent row (e.g., num partitions).
  23. Split points that fall in the middle of two items.
  24. No input rows and DEDUP attribute specified.

Ideally any test cases for features should be included in the runtime regression suite, which is found in the testing/regress directory in the github repository. Tests that check invalid syntax should go in the compiler regression suite (ecl/regress). Commit https://github.com/ghalliday/HPCC-Platform/commit/d75e6b40e3503f851265670a27889d8adc73f645 contains the test cases so far. Note, the test examples in that commit do not yet cover all the cases above. Before the final pull request for the feature is merged the list above should be revisited and the test suite extended to include any missing tests.

In practice it may be easier to write the test cases in parallel with implementing the parser -since that allows you to check their syntax. Some of the examples in the commit were created before work was started on the parser, others during, and some while implementing the feature itself.

Quantile 3 - The parser

The first stage in implementing QUANTILE will be to add it to the parser. This can sometimes highlight issues with the syntax and cause revisions to the design. In this case there were two technical issues integrating the syntax into the grammar. (If you are not interested in shift/reduce conflicts you may want to skip a few paragraphs and jump to the walkthrough of the changes.)

Originally, the optional transform was specified inside an attribute, e.g., something like OUTPUT(transform). However, this was not very consistent with the way that other transforms were implemented, so the syntax was updated so it became an optional transform following the partition field list.

When the syntax was added to the grammar we hit another problem: Currently, a single production (sortList) in the grammar is used for matching sort orders. As well as accepting fields from a dataset the sort order production has been extended to accept any named attribute that can follow a sort order (e.g., LOCAL). This is because (with one token lookahead) it is ambiguous where the sort order finishes and the list of attributes begins. Trying to include transforms in those productions revealed other problems:

  • If a production has a sortList followed by a transform (or attribute) then it introduces a shift/reduce error on ','. To avoid the ambiguity all trailing attributes or values need to be included in the sortList.
  • Including a transform production in the sortList elements causes problems with other transform disambiguation (e.g., DATASET[x] and AGGREGATE).
  • We could require an attribute around the transform e.g., OUTPUT(transform), but that does not really fit in with other activities in the language.
  • We could change the parameter order, e.g., move the transform earlier, but that would make the syntax counter-intuitive.
  • We could require { } around the list - but this is inconsistent with some of the other sort orders.

In order to make some progress I elected to choose the last option and require the sort order to be included in curly braces. There are already a couple of activities - subsort and a form of atmost that similarly require them (and if redesigning ECL from scratch I would be tempted to require them everywhere). The final syntax is something that will need further discussion as part of the review of the pull request though, and may need to be revisited.

Having decided how to solve the ambiguities in the grammar, the following is a walkthrough of the changes that were made as part of commit https://github.com/ghalliday/HPCC-Platform/commit/3d623d1c6cd151a0a5608aa20ae4739a008f6e44:

  • no_quantile in hqlexpr.hpp

    The ECL query is represented by a graph of "expression" nodes - each has a "kind" that comes from the enumeration _node_operator. The first requirement is to add a new enumeration to represent the new activity - in this case we elected to reuse an unused placeholder. (These placeholders correspond to some old operators that are no longer supported. They have not been removed because the other elements in the enumeration need to keep the same values since they are used for calculating derived persistent values e.g., the hashes for persists.)

  • New attribute names in hqlatoms.

    The quantile activity introduces some new attribute names that have not been used before. All names are represented in an atom table, so the code in hqlatoms.hpp/cpp is updated to define the new atoms.

  • Properties of no_quantile

    There are various places that need to be updated to allow the system to know about the properties of the new operator:

    • hqlattr

      This contains code to calculate derived attributes. The first entry in the case statement is currently unused (the function should be removed). The second, inside calcRowInformation(), is used to predict how many rows are generated by this activity. This information is percolated through the graph and is used for optimizations, and input counts can be used to select the best implementation for a particular activity.

    • hqlexpr

      Most changes are relatively simple including the text for the operator, whether it is constant, and the number of dataset arguments it has. One key function is getChildDatasetType() that indicates the kind of dataset arguments the operator has, which in turn controls how LEFT/RIGHT are interpreted. In this case some of the activity arguments (e.g., the number of quantiles) implicitly use fields within the parent dataset, and the transform uses LEFT, so the operator returns childdataset_datasetleft.

    • hqlir

      This entry is used for generating an intermediate representation of the graph. This can be useful for debugging issues. (Running eclcc with the logging options "--logfile xxx" and "--logdetail 999" will include details of the expression tree at each point in the code generation process in the log file. Also defining -ftraceIR will output the graphs in the IR format.)

    • hqlfold

      This is the constant folder. At the moment the only change is to ensure that fields that are assigned constants within the transform are processed correctly. Future work could add code to optimize quantile applied to an empty dataset, or selecting 1 division.

    • hqlmeta

      Similar to the functions in hqlattr that calculate derived attributes, these functions are used to calculate how the rows coming out of an activity are sorted, grouped and distributed. It is vital to only preserve information that is guaranteed to be true - otherwise invalid optimizations might be performed on the rest of the expression tree.

    • reservedwords.cpp

      A new entry indicating which category the keyword belongs to.

Finally we have the changes to the parser to recognise the new syntax:

  • hqllex.l

    This file contains the lexer that breaks the ecl file into tokens. There are two new tokens - QUANTILE and SCORE.

  • Hqlgram.y

    This file contains the grammar that matches the language. There are two productions - one that matches the version of QUANTILE with a transform and one without. (Two productions are used instead of an optional transform to avoid shift/reduce errors.)

  • hqlgram2.cpp

    This contains the bulk of the code that is executed by the productions in the grammar. Changes here include new entries added to a case statement to get the text for the new tokens, and a new entry in the simplify() call. This helps reduce the number of valid tokens that could follow when reporting a syntax error.

Looking back over those changes, one reflection is that there are lots of different places that need to be changed. How does a programmer know which functions need to change, and what happens if some are missed? In this example, the locations were found by searching for an activity with a similar syntax e.g., no_soapcall_ds or no_normalize.

It is too easy to miss something, especially for somebody new to the code - although if you do then you will trigger a runtime internal error. It would be much better if the code was refactored so that the bulk of the changes were in one place. (See JIRA https://track.hpccsystems.com/browse/HPCC-13434 that has been added to track improvement of the situation.)

With these changes implemented the examples from the previous pull request now syntax check. The next stage in the process involves thinking through the details of how the activity will be implemented.

Quantile 4 - The engine interface.

The next stage in adding a new activity to the system is to define the interface between the generated code and the engines. The important file for this stage is rtl/include/eclhelper.hpp, which contains the interfaces between the engines and the generated code. These interfaces define the information required by the engines to customize each of the different activities. The changes that define the interface for quantile are found in commit https://github.com/ghalliday/HPCC-Platform/commit/06534d8e9962637fe9a5188d1cc4ab32c3925010.

Adding a quantile activity involves the following changes:

  • ThorActivityKind - TAKquantile

    Each activity that the engines support has an entry in this enumeration. This value is stored in the graph as the _kind attribute of the node.

  • ActivityInterfaceEnum - TAIquantilearg_1

    This enumeration in combination with the selectInterface() member of IHThorArg provides a mechanism for helper interfaces to be extended while preserving backwards compatibility with older workunits. The mechanism is rarely used (but valuable when it is), and adding a new activity only requires a single new entry.

  • IHThorArg

    This is the base interface that all activity interfaces are derived from. This interface does not need to change, but it is worth noting because each activity defines a specialized version of it. The names of the specialised interfaces follow a pattern; in this case the new interface is IHThorQuantileArg.

  • IHThorQuantileArg

    The following is an outline of the new member functions, with comments on their use:

    • getFlags()

      Many of the interfaces have a getFlags() function. It provides a concise way of returning several Boolean options in a single call - provided those options do not change during the execution of the activity. The flags are normally defined with explicit values in an enumeration before the interface. The labels often follow the pattern T<First-letter-of-activity>F<lowercase-name>, i.e. TQFxxx ~= Thor-Quantile-Flag-XXX.

    • getNumDivisions()

      Returns how many parts to split the dataset into.

    • getSkew()

      Corresponds to the SKEW() attribute.

    • queryCompare()

      Returns an implementation of the interface used to compare two rows.

    • createDefault(rowBuilder)

      A function used to create a default row - used if there are no input rows.

    • transform(rowBuilder, _left, _counter)

      The function to create the output record from the input record and the partition number (passed as counter).

    • getScore(_left)

      What weighting should be given to this row?

    • getRange(isAll, tlen, tgt)

      Corresponds to the RANGE attribute.

Note that the different engines all use the same specialised interface - it contains a superset of the functions required by the different targets. Occasionally some of the engines do not need to use some of the functions (e.g., to serialize information between nodes) so the code generator may output empty implementations.

For each interface defined in eclhelper.hpp there is a base implementation class defined in eclhelper_base.hpp. The classes generated for each activity in a query by the code generator are derived from one of these base classes. Therefore we need to create a corresponding new class CThorQuantileArg. It often provides default implementations for some of the helper functions to help reduce the size of the generated code (e.g., getScore returning 1).

Often the process of designing the helper interface is dynamic. As the implementation is created, new options or possibilities for optimizations appear. These require extensions and changes to the helper interface in order to be implemented by the engines. Once the initial interface has been agreed, work on the code generator and the engines can proceeded in parallel. (It is equally possible to design this interface before any work on the parser begins, allowing more work to overlap.)

There are some more details on the contents of thorhelper.hpp in the documentation ecl/eclcc/WORKUNIT.rst within the HPCC repository.

Quantile 5 - The code generator

Adding a new activity to the code generator is (surprisingly!) a relatively simple operation. The process is more complicated if the activity also requires an implementation that generates inline C++, but that only applies to a small subset of very simple activities, e.g., filter, aggregate. Changes to the code generator also tend to be more substantial if you add a new type, but that is also not the case for the quantile activity.

For quantile, the only change required is to add a function that generates an implementation of the helper class. The code for all the different activities follows a very similar pattern - generate input activities, generate the helper for this activity, and link the input activities to this new activity. It is often easiest to copy the boiler-plate code from a similar activity (e.g., sort) and then adapt it. (Yes, some of this code could also be refactored... any volunteers?) There are a large number of helper functions available to help generate transforms and other member functions, which also simplifies the process.

The new code is found in commit https://github.com/ghalliday/HPCC-Platform/commit/47f850d827f1655fd6a78fb9c07f1e911b708175.

Most of the code should be self explanatory, but one item is worth highlighting. The code generator builds up a structure in memory that represents the C++ code that is being generated. The BuildCtx class is used to represent a location within that generated code where new code can be inserted. The instance variable contains several BuildCtx members that are used to represent locations to generate code within the helper class (classctx, nestedctx, createctx and startctx). They are used for different purposes:

  • classctx

    Used to generate any member functions that can be called as soon as the helper object has been created, e.g., getFlags().

  • nestedctx

    Used to generate nested member classes and objects - e.g., comparison classes.

  • startctx

    Any function that may return a value that depends on the context/parent activity. For example if QUANTILE is used inside the TRANSFORM of a PROJECT, the number of partition points may depend on a field in the LEFT row of the PROJECT. Therefore the getNumDivisions() member function needs to be generated inside instance->startctx. These functions can only be called by the engine after onCreate() and onStart() have been called to set up the current context.

  • createctx

    Really, this is a historical artefact from many years ago. It was originally used for functions that could be dependent on a global expression, but not a parent row. Almost all such restrictions have since been removed, and those that remain should probably be replaced with either classctx or startctx.

The only other change is to extend the switch statement in common/thorcommon/thorcommon.cpp to add a text description of the activity.

Quantile 6 - Roxie

With the code generator outputting all the information we need, we can now implement the activity in one of the engines. (As I mentioned previously, in practice this is often done in parallel with adding it to the code generator.) Roxie and hThor are the best engines to start with because most of their activities run on a single node - so the implementations tend to be less complicated. It is also relatively easy to debug them, by compiling to create a stand-alone executable, and then running that executable inside a debugger. The following description walks-through the roxie changes:

The changes have been split into two commits to make the code changes easier to follow. The first commit (https://github.com/ghalliday/HPCC-Platform/commit/30da006df9ae01c9aa784e91129457883e9bb8f3) adds the simplest implementation of the activity:

Code is added to ccdquery to process the new TAKquantile activity kind, and create a factory object of the correct type. The implementation of the factory class is relatively simple - it primarily contains a method for creating an instance of the activity class. Some factories create instances of the helper and cache any information that never changes (in this case the value returned by getFlags(), which is a very marginal optimization).

The classes that implement the existing sort algorithms are extended to return the sorted array in a single call. This allows the quicksort variants to be implemented more efficiently.

The class CRoxieServerQuantileActivity contains the code for implementing the quantile activity. It has the following methods:

  • Constructor

    Extracts any information from the helper that does not vary, and initializes all member variables.

  • start()

    This function is called before the graph is executed. It evaluates any helper methods that might vary from execution to execution (e.g., getRange(), numDivisions()), but which do not depend on the current row.

  • reset()

    Called when a graph has finished executing - after an activity has finished processing all its records. It is used to clean up any variables, and restore the activity ready for processing again (e.g., if it is inside a child query).

  • needsAllocator()

    Returns true if this activity creates new rows.

  • nextInGroup()

    The main function in the activity. This function is called by whichever activity is next in the graph to request a new row from the quantile activity. The functions should be designed so they return the next row as quickly as possible, and delay any processing until it is needed. In this case the input is not read and sorted until the first row is requested.

    Note, the call to the helper.transform() returns the size of the resulting row, and returns zero if the row should be skipped. The call to finaliseRowClear() after a successful row creation is there to indicate that the row can no longer be modified, and ensures that any child rows will be correctly freed when the row is freed.

    The function also contains extra logic to ensure that groups are implemented correctly. The end of a group is marked by returning a single NULL row, the end of the dataset by two contiguous NULL rows. It is important to ensure that a group that has all its output rows skipped doesn't return two NULLs in a row - hence the checks for anyThisGroup.

With those changes in place, the second commit https://github.com/ghalliday/HPCC-Platform/commit/aeaa209092ea1af9660c6908062c1b0b9acff36b adds support for the RANGE, FIRST, and LAST attributes. It also optimizes the cases where the input is already sorted, and the version of QUANTILE which does not include a transform. (If you are looking at the change in github then it is useful to ignore whitespace changes by appending ?w=1 to the URL). The main changes are

  • Extra helper methods called in start() to obtain the range.
  • Optimize the situation where the input is known to be sorted by reading the input rows directly into the "sorted" array.
  • Extra checks to see if this quantile should be included in the output (FIRST,LAST,RANGE,DEDUP)
  • An optimization to link the incoming row if the transform does not modify it, by testing the TQFneedtransform flag.

Quantile 7 - Possible roxie improvements

TBD...

hthor - trivial,sharing code and deprecated.

Discussion, of possible improvements.

Hoares' algorithm.

Ln2(n) < 4k?

SKEW and Hoares

Ordered RANGE. Calc offsets from the quantile (see testing/regress/ecl/xxxxx?)

SCORE

Quantile 8 - Thor

TBD

Basic activity structure

Locally sorting and allowing the inputs to spill.

The partitioning approach

Classes

Skew

Optimizations

`,78)]))}const m=t(s,[["render",n]]);export{u as __pageData,m as default}; diff --git a/assets/devdoc_roxie.md.C6oOX-36.js b/assets/devdoc_roxie.md.C6oOX-36.js new file mode 100644 index 00000000000..2a6395f74e7 --- /dev/null +++ b/assets/devdoc_roxie.md.C6oOX-36.js @@ -0,0 +1,8 @@ +import{_ as t,c as a,a3 as o,o as i}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Everything you ever wanted to know about Roxie","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/roxie.md","filePath":"devdoc/roxie.md","lastUpdated":1731340314000}'),n={name:"devdoc/roxie.md"};function s(r,e,l,h,d,c){return i(),a("div",null,e[0]||(e[0]=[o(`

Everything you ever wanted to know about Roxie

Why did I create it?

Because I could. Many of the pieces needed for Roxie were already created for use in other systems – ECL language, code generator, index creation in Thor, etc. Indexes could be used by Moxie, but that relied on monolithic single-part indexes, was single-threaded (forked a process per query) and had limited ability to do any queries beyond simple index lookups. ECL had already proved itself as a way to express more complex queries concisely, and the concept of doing the processing next to the data had been proved in hOle and Thor, so Roxie – using the same concept for online queries using indexes – was a natural extension of that, reusing the existing index creation and code generation, but adding a new run-time engine geared towards pre-deployed queries and sending index lookup requests to the node holding the index data.

The code generator creates a graph (DAG) representing the query, with one node per activity and links representing the inputs and dependencies. There is also a helper class for each activity.

Roxie loads this graph for all published queries, creating a factory for each activity and recording how they are linked. When a query is executed, the factories create the activity instances and link them together. All activities without output activities (known as ‘sinks’) are then executed (often on parallel threads), and will typically result in a value being written to a workunit, to the socket that the query was received in, or to a global “context” area where subsequent parts of the query might read it.

Data is pulled through the activity graph, by any activity that wants a row from its input requesting it. Evaluation is therefore lazy, with data only calculated as needed. However, to reduce latency in some cases activities will prepare results ahead of when they are requested – for example an index read activity will send the request to the agent(s) as soon as it is started rather than waiting for the data to be requested by its downstream activity. This may result in wasted work, and in some cases may result in data coming back from an agent after the requesting query has completed after discovering it didn’t need it after all – this results in the dreaded “NO msg collator found – using default” tracing (not an error but may be indicative of a query that could use some tuning).

Before requesting rows from an input, it should be started, and when no more rows are required it should be stopped. It should be reset before destruction or reuse (for example for the next row in a child query).

Balancing the desire to reduce latency with the desire to avoid wasted work can be tricky. Conditional activities (IF etc) will not start their unused inputs, so that queries can be written that do different index reads depending on the input. There is also the concept of a “delayed start” activity – I would need to look at the code to remind myself of how those are used.

Where are the Dragons?

Splitter activities are a bit painful – they may result in arbitrary buffering of the data consumed by one output until another output is ready to request a row. It’s particularly complex when some of the outputs don’t start at all – the splitter needs to keep track of how many of the inputs have been started and stopped (an input that is not going to be used must be stopped, so that splitters know not to keep data for them). Tracking these start/stop/reset calls accurately is very important otherwise you can end up with weird bugs including potential crashes when activities are destroyed. Therefore we report errors if the counts don’t tally properly at the end of a query – but working out where a call was missed is often not trivial. Usually it’s because of an exception thrown from an unexpected place, e.g. midway through starting.

Note that row requests from the activities above a splitter may execute on whichever thread downstream from the splitter happens to need that particular row first.

The splitter code for tracking whether any of the downstream activities still need a row is a bit hairy/inefficient, IIRC. There may be scope to optimize (but I would recommend adding some good unit test cases first!)

How does “I beat you to it” work?

When there are multiple agents fulfilling data on a channel, work is shared among them via a hash of the packet header, which is used to determine which agent should work on that packet. However, if it doesn’t start working on it within a short period (either because the node is down, or because it is too busy on other in-flight requests), then another node may take over. The IBYTI messages are used to indicate that a node has started to work on a packet and therefore there is no need for a secondary to take over.

The priority of agents as determined by the packet hash is also used to determine how to proceed if an IBYTI is received after starting to work on a request. If the IBYTI is from a lower priority buddy (sub-channel) then it is ignored, if it’s from a higher priority one then the processing will be abandoned.

When multicast is enabled, the IBYTI is sent on the same multicast channel as the original packet (and care is needed to ignore ones sent by yourself). Otherwise it is sent to all buddy IPs.

Nodes keep track of how often they have had to step in for a supposedly higher priority node, and reduce their wait time before stepping in each time this happens, so if a node has crashed then the buddy nodes will end up taking over without every packet being delayed.

(QUESTION – does this result in the load on the first node after the failed node getting double the load?)

Newer code for cloud systems (where the topology may change dynamically) send the information about the buddy nodes in the packet header rather than assuming all nodes already have a consistent version of that information. This ensures that all agents are using the same assumptions about buddy nodes and their ordering.

All about index compression

An index is basically a big sorted table of the keyed fields, divided into pages, with an index of the last row from each page used to be able to locate pages quickly. The bottom level pages (‘leaves’) may also contain payload fields that do not form part of the lookup but can be returned with it.

Typical usage within LN Risk tends to lean towards one of two cases:

  • Many keyed fields with a single “ID” field in the payload
  • A single “ID” field in the key with many “PII” fields in the payload.

There may be some other cases of note too though – e.g. an error code lookup file which heavily, used, or Boolean search logic keys using smart-stepping to implement boolean search conditions.

It is necessary to store the index pages on disk compressed – they are very compressible – but decompression can be expensive. For this reason traditionally we have maintained a cache of decompressed pages in addition to the cache of compressed pages that can be found in the Linux page cache. However, it would be much preferred if we could avoid decompressing as much as possible, ideally to the point where no significant cache of the decompressed pages was needed.

Presently we need to decompress to search, so we’ve been looking at options to compress the pages in such a way that searching can be done using the compressed form. The current design being played with here uses a form of DFA to perform searching/matching on the keyed fields – the DFA data is a compact representation of the data in the keyed fields but is also efficient to use as for searching. For the payload part, we are looking at several options (potentially using more than one of them depending on the exact data) including:

  • Do not compress (may be appropriate for ID case, for example)
  • Compress individual rows, using a shared dictionary (perhaps trained on first n rows of the index)
  • Compress blocks of rows (in particular, rows that have the same key value)

A fast (to decompress) compression algorithm that handles small blocks of data efficiently is needed. Zstd may be one possible candidate.

Preliminary work to enable the above changes involved some code restructuring to make it possible to plug in different compression formats more easily, and to vary the compression format per page.

What is the topology server for?

It’s used in the cloud to ensure that all nodes can know the IP addresses of all agents currently processing requests for a given channel. These addresses can change over time due to pod restarts or scaling events. Nodes report to the topology server periodically, and it responds to them with the current topology state. There may be multiple topology servers running (for redundancy purposes). If so all reports should go to all, and it should not matter which one’s answer is used. (QUESTION – how is the send to all done?)

Lazy File IO

All IFileIO objects used to read files from Roxie are instantiated as IRoxieLazyFileIO objects, which means:

  • The underlying file handles can be closed in the background, in order to handle the case where file handles are a limited resource. The maximum (and minimum) number of open files can be configured separately for local versus remote files (sometimes remote connections are a scarcer resource than local, if there are limits at the remote end).

  • The actual file connected to can be switched out in the background, to handle the case where a file read from a remote location becomes unavailable, and to switch to reading from a local location after a background file copy operation completes.

New IBYTI mode

Original IBYTI implementation allocated a thread (from the pool) to each incoming query packet, but some will block for a period to allow an IBYTI to arrive to avoid unnecessary work. It was done this way for historical reasons - mainly that the addition of the delay was after the initial IBYTI implementation, so that in the very earliest versions there was no priority given to any particular subchannel and all would start processing at the same time if they had capacity to do so.

This implementation does not seem particularly smart - in particular it's typing up worker threads even though they are not actually working, and may result in the throughput of the Roxie agent being reduced. For that reason an alternative implementation (controlled by the NEW_IBYTI flag) was created during the cloud transition which tracks what incoming packets are waiting for IBYTI expiry via a separate queue, and they are only allocated to a worker thread once the IBYTI delay times out.

So far the NEW_IBYTI flag has only been set on containerized systems (simply to avoid rocking the boat on the bare-metal systems), but we may turn on in bare metal too going forward (and if so, the old version of the code can be removed sooner or later).

Testing Roxie code

Sometimes when developing/debugging Roxie features, it's simplest to run a standalone executable. Using server mode may be useful if wanting to debug server/agent traffic messaging.

For example, to test IBYTI behaviour on a single node, use

./a.out --server --port=9999 --traceLevel=1 --logFullQueries=1 --expert.addDummyNode --roxieMulticastEnabled=0 --traceRoxiePackets=1
+

Having first compiled a suitable bit of ECL into a.out. I have found a snippet like this quite handy:

rtl := SERVICE
+ unsigned4 sleep(unsigned4 _delay) : eclrtl,action,library='eclrtl',entrypoint='rtlSleep';
+END;
+
+d := dataset([{rtl.sleep(5000)}], {unsigned a});
+allnodes(d)+d;
+

Cache prewarm

Roxie (optionally) maintains a list of the most recently accessed file pages (in a circular buffer), and flushes this information periodically to text files that will persist from one run of Roxie to the next. On startup, these files are processed and the relevant pages preloaded into the linux page cache to ensure that the "hot" pages are already available and maximum performance is available immediately once the Roxie is brought online, rather than requiring a replay of a "typical" query set to heat the cache as used to be done. In particular this should allow a node to be "warm" before being added to the cluster when autoscaling.

There are some questions outstanding about how this operates that may require empirical testing to answer. Firstly, how does this interact with volumes mounted via k8s pvc's, and in particular with cloud billing systems that charge per read. Will the reads that are done to warm the cache be done in large chunks, or will they happen one linux page at a time? The code at the Roxie level operates by memory-mapping the file then touching a byte within each linux page that we want to be "warm", but does the linux paging subsystem fetch larger blocks? Do huge pages play a part here?

Secondly, the prewarm is actually done by a child process (ccdcache), but the parent process is blocked while it happens. It would probably make sense to at allow at least some of the other startup operations of the parent process to proceed in parallel. There are two reasons why the cache prewarm is done using a child process. Firstly is to allow there to be a standalone way to prewarm prior to launching a Roxie, which might be useful for automation in some bare-metal systems. Secondly, because there is a possibility of segfaults resulting from the prewarm if the file has changed size since the cache warming was done, it is easier to contain, capture, and recover from such faults in a child process than it would be inside Roxie. However, it would probably be possible to avoid these segfaults (by checking more carefully against file size before trying to warm a page, for example) and then link the code into Roxie while still keeping the code common with the standalone executable version.

Thirdly, need to check that the prewarm is complete before adding a new agent to the topology. This is especially relevant if we make any change to do the prewarm asynchronously.

Fourthly, there are potential race conditions when reading/writing the file containing cache information, since this file may be written by any agent operating on the same channel, at any time.

Fifthly, how is the amount of information tracked decided? It should be at least related to the amount of memory available to the linux page cache, but that's not a completely trivial thing to calculate. Should we restrict to the most recent N when outputting, where N is calculated from, for example /proc/meminfo's Active(file) value? Unfortunately on containerized sytems that reflects the host, but perhaps /sys/fs/cgroup/memory.stat can be used instead?

When deciding how much to track, we can pick an upper limit from the pod's memory limit. This could be read from /sys/fs/cgroup/memory.max though we currently read from the config file instead. We should probably (a) subtract the roxiemem size from that and (b) think about a value that will work on bare-metal and fusion too. However, because we don't dedup the entries in the circular buffer used for tracking hot pages until the info is flushed, the appropriate size is not really the same as the memory size.

We track all reads by page, and before writing also add all pages in the jhtree cache with info about the node type. Note that a hit in the jhtree page cache won't be noted as a read OTHER than via this last-minute add.

Blacklisting sockets

This isn't really specific to Roxie, but was originally added for federated Roxie systems...

When a Roxie query (or hthor/thor) makes a SOAPCALL, there is an option to specify a list of target gateway IPs, and failover to the next in the list if the first does not respond in a timely fashion. In order to avoid this "timely fashion" check adding an overhead to every query made when a listed gateway is unavailable, we maintain a "blacklist" of nodes that have been seen to fail, and do not attempt to connect to them. There is a "deblacklister" thread that checks periodically whether it is now possible to connect to a previously-blacklisted gateway, and removes it from the list if so.

There are a number of potential questions and issues with this code:

  1. It would appear that a blacklist is applied even when there is only one gateway listed. In this case, the blacklist may be doing more harm than good? I'm not sure that is true - it is still causing rapid failures in cases that are never going to work...
  2. Even when there is only one gateway listed, a blacklist MIGHT still be useful (you don't really want EVERY query to block trying to connect, if the gateway is down - may prefer a fast failure). Also applies when there are multiple records, all being passed to a gateway, and with an ONFAIL.
  3. Is the blacklist shared between all queries? I'm pretty sure it is NOT shared across Roxie nodes... Looks like it is a global object, shared between all queries and activities. However, connections that were started in parallel will all be reported as failed rather than blacklisted, which can make it look like it is maintained per-activity.
  4. Is it only a failed connect that leads to blacklisting, or does a slow/error response also cause a gateway endpoint to be blacklisted? It's only a failed connect.
  5. When deblacklisting, can we check any condition other than "Successfully connected"? If not, blacklisting for any reason other than "Did not connect" feels like a recipe for problems. We only check for a connection (and correspondingly only blacklist for a failed connection).
  6. Are we ever using the functionality where there are more than one gateway listed? Most of the time a load-balancer is a preferable solution...
  7. The "deblacklister" thread seems to add an escalating delay between attempts. Is this delay ever reset? Is it configurable? Is it appropriate?
  8. There's a thread (in the blacklister's pool) for each blacklisted endpoint. These threads will never go away if the endpoint does not recover...
  9. There's a delay of up to 10 seconds in terminating caused by the deblacklister's connect having to timeout before it notices that we are stopping. Can we close the socket as well as interrupting the semaphore?
  10. Does the "reconnect" attempt from the deblacklister cause any pain for the server it is connecting to? Lots of connect attempts without any data could look like a DoS attack...
  11. Retries/timeout seems to translate to Owned<ISocketConnectWait> scw = nonBlockingConnect(ep, timeoutMS == WAIT_FOREVER ? 60000 : timeoutMS*(retries+1)); I am not sure that is correct (a single attempt to connect with a long timeout doesn't feel like it is the same as multiple attempts with shorter timeouts, for example if there is a load balancer in the mix).
  12. Perhaps an option to not use blacklister would solve the immediate issue?
  13. The blacklister uses an array of endpoints - If there were a lot blacklisted, a hash table would be better
  14. Hints to control behaviour of deblacklister would behave unpredictably if multiple activities connected to the same endpoint with different hints unless we make the blacklist lookup match the hint values too.
  15. Deblacklister should use nonBlockingConnect too.

Should the scope of the blacklist be different? Possible scopes are:

  1. Shared across all queries/activities (current behaviour)
  2. Specific to an activity, but shared across queries (i.e. owned by the activity factory)
  3. Specific to all activities in a deployed query (i.e. owned by the query factory)
  4. Specific to a particular activity instance (i.e. owned by the activity object)
  5. Specific to a particular query instance (i.e. owned by the query object)

Options 2 and 4 above would allow all aspects of the blacklisting behaviour to be specified by options on the SOAPCALL. We could control whether or not the blacklister is to be used at all via a SOAPCALL option with any of the above...

perftrace options

The HPCC Platform includes a rudimentary performance tracing feature using periodic stack capture to generate flame graphs. Roxie supports this in 3 ways:

  1. If expert/@profileStartup is set in roxie config, a flame graph is generated for operations during Roxie startup phase.
  2. If @perf is set on an incoming query, a flame graph is generated for the lifetime of that query's execution, and returned along with the query results
  3. If expert/perftrace is set in roxie config, one-shot roxie queries (e.g. eclagent mode) generate a flame graph (currently just to a text file).

The perf trace operates as follows:

  1. A child process is launched that runs the doperf script. This samples the current stack(s) every 0.2s (configurable) to a series of text files.
  2. When tracing is done, these text files are "folded" via a perl script that notes every unique stack and how many times it was seen, one line per unique stack
  3. This folded stack list is filtered to suppress some stacks that are not very interesting
  4. The filtered folded stack list is passed to another perl script that generates an svg file.

The basic info captured at step 1 (or maybe 2) could also be analysed to give other insights, such as:

  1. A list of "time in function, time in children of function".
  2. An expanded list of callers to __GI___lll_lock_wait and __GI___lll_lock_wake, to help spot contended critsecs.

Unfortunately some info present in the original stack text files is lost in the folded summary - in particular related to the TID that the stack is on. Can we spot lifetimes of threads and/or should we treat "stacks" on different threads as different? Thread pools might render this difficult though. There is an option in stack-collapse-elfutils.pl to include the TID when considering whether stacks match, so perhaps we should just (optionally) use that.

Some notes on LocalAgent mode

In localAgent mode, the global queueManager object (normally a RoxieUdpSocketQueueManager) is replaced by a RoxieLocalQueueManager. Outbound packets are added directly to target queue, inbound are packed into DataBuffers.

There is also "local optimizations" mode where any index operation reading a one-part file (does the same apply to one-part disk files?) just reads it directly on the server (regardless of localAgent setting). Typically still injected into receiver code though as otherwise handling exception cases, limits etc would all be duplicated/messy. Rows created in localOptimization mode are created directly in the caller's row manager, and are injected in serialized format.

Why are inbound not created directly in the desired destination's allocator and then marked as serialized? Some lifespan issues... are they insurmountable? We do pack into dataBuffers rather than MemoryBuffers, which avoids a need to copy the data before the receiver can use it. Large rows get split and will require copying again, but we could set dataBufferSize to be bigger in localAgent mode to mitigate this somewhat.

What is the lifespan issue? In-flight queries may be abandoned when a server-side query fails, times out, or no longer needs the data. Using DataBuffer does not have this issue as they are attached to the query's memory manager/allocation once read. Or we could bypass the agent queue altogether, but rather more refactoring needed for that (might almost be easier to extent the "local optimization" mode to use multiple threads at that point)

abortPending, replyPending, and abortPendingData methods are unimplemented, which may lead to some inefficiencies?

Some notes on UDP packet sending mechanism

Requests from server to agents are send via UDP (and have a size limit of 64k as a result). Historically they were sent using multicast to go to all agents on a channel at the same time, but since most cloud providers do not support multicast, there has long been an option to avoid multicast and send explicitly to the agent IPs. In bare metal systems these IPs are known via the topology file, and do not change. In cloud systems the topology server provides the IPs of all agents for a channel.

In cloud systems, the list of IPs that a message was sent to is included in the message header, so that the IBYTI messages can be sent without requiring that all agents/servers have the same topology information at any given moment (they will stay in sync because of topology server, but may be temporarily out of sync when nodes are added/removed, until next time topology info is retrieved). This is controled by the SUBCHANNELS_IN_HEADER define.

Packets back from agents to server go via the udplib message-passing code. This can best be described by looking at the sending and receiving sides separately.

When sending, results are split into individual packets (DataBuffers), each designed to be under 1 MTU in size. Traditionally this meant they were 1k, but they can be set larger (8k is good). They do have to be a power of 2 because of how they are allocated from the roxiemem heap. The sender maintains a set of UdpReceiverEntry objects, one for each server that it is conversing with. Each UdpReceiverEntry maintains multiple queues of data packets waiting to be sent, one queue for each priority. The UdpReceiverEntry maintains a count of how many packets are contained across all its queues in packetsQueued, so that it knows if there is data to send.

The priority levels are: 0: Out Of Band 1: Fast lane 2: Standard

This is designed to allow control information to be sent without getting blocked by data, and high priority queries to avoid being blocked by data going to lower priority ones. The mechanism for deciding what packet to send next is a little odd though - rather than sending all higher-priorty packets before any lower-priority ones, it round robins across the queues sending up to N^2 from queue 0 then up to N from queue 1 then 1 from queue 2, where N is set by the UdpOutQsPriority option, or 1 if not set. This may be a mistake - probably any from queue 0 should be sent first, before round-robining the other queues in this fashion.

UdpReceiverEntry objects are also responsible for maintaining a list of packets that have been sent but receiver has not yet indicated that they have arrived.

If an agent has data ready for a given receiver, it will send a requestToSend to that receiver, and wait for a permitToSend response. Sequence numbers are used to handle situations where these messages get lost. A permitToSend that does not contain the expected sequence number is ignored.

`,85)]))}const m=t(n,[["render",s]]);export{u as __pageData,m as default}; diff --git a/assets/devdoc_roxie.md.C6oOX-36.lean.js b/assets/devdoc_roxie.md.C6oOX-36.lean.js new file mode 100644 index 00000000000..2a6395f74e7 --- /dev/null +++ b/assets/devdoc_roxie.md.C6oOX-36.lean.js @@ -0,0 +1,8 @@ +import{_ as t,c as a,a3 as o,o as i}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Everything you ever wanted to know about Roxie","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/roxie.md","filePath":"devdoc/roxie.md","lastUpdated":1731340314000}'),n={name:"devdoc/roxie.md"};function s(r,e,l,h,d,c){return i(),a("div",null,e[0]||(e[0]=[o(`

Everything you ever wanted to know about Roxie

Why did I create it?

Because I could. Many of the pieces needed for Roxie were already created for use in other systems – ECL language, code generator, index creation in Thor, etc. Indexes could be used by Moxie, but that relied on monolithic single-part indexes, was single-threaded (forked a process per query) and had limited ability to do any queries beyond simple index lookups. ECL had already proved itself as a way to express more complex queries concisely, and the concept of doing the processing next to the data had been proved in hOle and Thor, so Roxie – using the same concept for online queries using indexes – was a natural extension of that, reusing the existing index creation and code generation, but adding a new run-time engine geared towards pre-deployed queries and sending index lookup requests to the node holding the index data.

The code generator creates a graph (DAG) representing the query, with one node per activity and links representing the inputs and dependencies. There is also a helper class for each activity.

Roxie loads this graph for all published queries, creating a factory for each activity and recording how they are linked. When a query is executed, the factories create the activity instances and link them together. All activities without output activities (known as ‘sinks’) are then executed (often on parallel threads), and will typically result in a value being written to a workunit, to the socket that the query was received in, or to a global “context” area where subsequent parts of the query might read it.

Data is pulled through the activity graph, by any activity that wants a row from its input requesting it. Evaluation is therefore lazy, with data only calculated as needed. However, to reduce latency in some cases activities will prepare results ahead of when they are requested – for example an index read activity will send the request to the agent(s) as soon as it is started rather than waiting for the data to be requested by its downstream activity. This may result in wasted work, and in some cases may result in data coming back from an agent after the requesting query has completed after discovering it didn’t need it after all – this results in the dreaded “NO msg collator found – using default” tracing (not an error but may be indicative of a query that could use some tuning).

Before requesting rows from an input, it should be started, and when no more rows are required it should be stopped. It should be reset before destruction or reuse (for example for the next row in a child query).

Balancing the desire to reduce latency with the desire to avoid wasted work can be tricky. Conditional activities (IF etc) will not start their unused inputs, so that queries can be written that do different index reads depending on the input. There is also the concept of a “delayed start” activity – I would need to look at the code to remind myself of how those are used.

Where are the Dragons?

Splitter activities are a bit painful – they may result in arbitrary buffering of the data consumed by one output until another output is ready to request a row. It’s particularly complex when some of the outputs don’t start at all – the splitter needs to keep track of how many of the inputs have been started and stopped (an input that is not going to be used must be stopped, so that splitters know not to keep data for them). Tracking these start/stop/reset calls accurately is very important otherwise you can end up with weird bugs including potential crashes when activities are destroyed. Therefore we report errors if the counts don’t tally properly at the end of a query – but working out where a call was missed is often not trivial. Usually it’s because of an exception thrown from an unexpected place, e.g. midway through starting.

Note that row requests from the activities above a splitter may execute on whichever thread downstream from the splitter happens to need that particular row first.

The splitter code for tracking whether any of the downstream activities still need a row is a bit hairy/inefficient, IIRC. There may be scope to optimize (but I would recommend adding some good unit test cases first!)

How does “I beat you to it” work?

When there are multiple agents fulfilling data on a channel, work is shared among them via a hash of the packet header, which is used to determine which agent should work on that packet. However, if it doesn’t start working on it within a short period (either because the node is down, or because it is too busy on other in-flight requests), then another node may take over. The IBYTI messages are used to indicate that a node has started to work on a packet and therefore there is no need for a secondary to take over.

The priority of agents as determined by the packet hash is also used to determine how to proceed if an IBYTI is received after starting to work on a request. If the IBYTI is from a lower priority buddy (sub-channel) then it is ignored, if it’s from a higher priority one then the processing will be abandoned.

When multicast is enabled, the IBYTI is sent on the same multicast channel as the original packet (and care is needed to ignore ones sent by yourself). Otherwise it is sent to all buddy IPs.

Nodes keep track of how often they have had to step in for a supposedly higher priority node, and reduce their wait time before stepping in each time this happens, so if a node has crashed then the buddy nodes will end up taking over without every packet being delayed.

(QUESTION – does this result in the load on the first node after the failed node getting double the load?)

Newer code for cloud systems (where the topology may change dynamically) send the information about the buddy nodes in the packet header rather than assuming all nodes already have a consistent version of that information. This ensures that all agents are using the same assumptions about buddy nodes and their ordering.

All about index compression

An index is basically a big sorted table of the keyed fields, divided into pages, with an index of the last row from each page used to be able to locate pages quickly. The bottom level pages (‘leaves’) may also contain payload fields that do not form part of the lookup but can be returned with it.

Typical usage within LN Risk tends to lean towards one of two cases:

  • Many keyed fields with a single “ID” field in the payload
  • A single “ID” field in the key with many “PII” fields in the payload.

There may be some other cases of note too though – e.g. an error code lookup file which heavily, used, or Boolean search logic keys using smart-stepping to implement boolean search conditions.

It is necessary to store the index pages on disk compressed – they are very compressible – but decompression can be expensive. For this reason traditionally we have maintained a cache of decompressed pages in addition to the cache of compressed pages that can be found in the Linux page cache. However, it would be much preferred if we could avoid decompressing as much as possible, ideally to the point where no significant cache of the decompressed pages was needed.

Presently we need to decompress to search, so we’ve been looking at options to compress the pages in such a way that searching can be done using the compressed form. The current design being played with here uses a form of DFA to perform searching/matching on the keyed fields – the DFA data is a compact representation of the data in the keyed fields but is also efficient to use as for searching. For the payload part, we are looking at several options (potentially using more than one of them depending on the exact data) including:

  • Do not compress (may be appropriate for ID case, for example)
  • Compress individual rows, using a shared dictionary (perhaps trained on first n rows of the index)
  • Compress blocks of rows (in particular, rows that have the same key value)

A fast (to decompress) compression algorithm that handles small blocks of data efficiently is needed. Zstd may be one possible candidate.

Preliminary work to enable the above changes involved some code restructuring to make it possible to plug in different compression formats more easily, and to vary the compression format per page.

What is the topology server for?

It’s used in the cloud to ensure that all nodes can know the IP addresses of all agents currently processing requests for a given channel. These addresses can change over time due to pod restarts or scaling events. Nodes report to the topology server periodically, and it responds to them with the current topology state. There may be multiple topology servers running (for redundancy purposes). If so all reports should go to all, and it should not matter which one’s answer is used. (QUESTION – how is the send to all done?)

Lazy File IO

All IFileIO objects used to read files from Roxie are instantiated as IRoxieLazyFileIO objects, which means:

  • The underlying file handles can be closed in the background, in order to handle the case where file handles are a limited resource. The maximum (and minimum) number of open files can be configured separately for local versus remote files (sometimes remote connections are a scarcer resource than local, if there are limits at the remote end).

  • The actual file connected to can be switched out in the background, to handle the case where a file read from a remote location becomes unavailable, and to switch to reading from a local location after a background file copy operation completes.

New IBYTI mode

Original IBYTI implementation allocated a thread (from the pool) to each incoming query packet, but some will block for a period to allow an IBYTI to arrive to avoid unnecessary work. It was done this way for historical reasons - mainly that the addition of the delay was after the initial IBYTI implementation, so that in the very earliest versions there was no priority given to any particular subchannel and all would start processing at the same time if they had capacity to do so.

This implementation does not seem particularly smart - in particular it's typing up worker threads even though they are not actually working, and may result in the throughput of the Roxie agent being reduced. For that reason an alternative implementation (controlled by the NEW_IBYTI flag) was created during the cloud transition which tracks what incoming packets are waiting for IBYTI expiry via a separate queue, and they are only allocated to a worker thread once the IBYTI delay times out.

So far the NEW_IBYTI flag has only been set on containerized systems (simply to avoid rocking the boat on the bare-metal systems), but we may turn on in bare metal too going forward (and if so, the old version of the code can be removed sooner or later).

Testing Roxie code

Sometimes when developing/debugging Roxie features, it's simplest to run a standalone executable. Using server mode may be useful if wanting to debug server/agent traffic messaging.

For example, to test IBYTI behaviour on a single node, use

./a.out --server --port=9999 --traceLevel=1 --logFullQueries=1 --expert.addDummyNode --roxieMulticastEnabled=0 --traceRoxiePackets=1
+

Having first compiled a suitable bit of ECL into a.out. I have found a snippet like this quite handy:

rtl := SERVICE
+ unsigned4 sleep(unsigned4 _delay) : eclrtl,action,library='eclrtl',entrypoint='rtlSleep';
+END;
+
+d := dataset([{rtl.sleep(5000)}], {unsigned a});
+allnodes(d)+d;
+

Cache prewarm

Roxie (optionally) maintains a list of the most recently accessed file pages (in a circular buffer), and flushes this information periodically to text files that will persist from one run of Roxie to the next. On startup, these files are processed and the relevant pages preloaded into the linux page cache to ensure that the "hot" pages are already available and maximum performance is available immediately once the Roxie is brought online, rather than requiring a replay of a "typical" query set to heat the cache as used to be done. In particular this should allow a node to be "warm" before being added to the cluster when autoscaling.

There are some questions outstanding about how this operates that may require empirical testing to answer. Firstly, how does this interact with volumes mounted via k8s pvc's, and in particular with cloud billing systems that charge per read. Will the reads that are done to warm the cache be done in large chunks, or will they happen one linux page at a time? The code at the Roxie level operates by memory-mapping the file then touching a byte within each linux page that we want to be "warm", but does the linux paging subsystem fetch larger blocks? Do huge pages play a part here?

Secondly, the prewarm is actually done by a child process (ccdcache), but the parent process is blocked while it happens. It would probably make sense to at allow at least some of the other startup operations of the parent process to proceed in parallel. There are two reasons why the cache prewarm is done using a child process. Firstly is to allow there to be a standalone way to prewarm prior to launching a Roxie, which might be useful for automation in some bare-metal systems. Secondly, because there is a possibility of segfaults resulting from the prewarm if the file has changed size since the cache warming was done, it is easier to contain, capture, and recover from such faults in a child process than it would be inside Roxie. However, it would probably be possible to avoid these segfaults (by checking more carefully against file size before trying to warm a page, for example) and then link the code into Roxie while still keeping the code common with the standalone executable version.

Thirdly, need to check that the prewarm is complete before adding a new agent to the topology. This is especially relevant if we make any change to do the prewarm asynchronously.

Fourthly, there are potential race conditions when reading/writing the file containing cache information, since this file may be written by any agent operating on the same channel, at any time.

Fifthly, how is the amount of information tracked decided? It should be at least related to the amount of memory available to the linux page cache, but that's not a completely trivial thing to calculate. Should we restrict to the most recent N when outputting, where N is calculated from, for example /proc/meminfo's Active(file) value? Unfortunately on containerized sytems that reflects the host, but perhaps /sys/fs/cgroup/memory.stat can be used instead?

When deciding how much to track, we can pick an upper limit from the pod's memory limit. This could be read from /sys/fs/cgroup/memory.max though we currently read from the config file instead. We should probably (a) subtract the roxiemem size from that and (b) think about a value that will work on bare-metal and fusion too. However, because we don't dedup the entries in the circular buffer used for tracking hot pages until the info is flushed, the appropriate size is not really the same as the memory size.

We track all reads by page, and before writing also add all pages in the jhtree cache with info about the node type. Note that a hit in the jhtree page cache won't be noted as a read OTHER than via this last-minute add.

Blacklisting sockets

This isn't really specific to Roxie, but was originally added for federated Roxie systems...

When a Roxie query (or hthor/thor) makes a SOAPCALL, there is an option to specify a list of target gateway IPs, and failover to the next in the list if the first does not respond in a timely fashion. In order to avoid this "timely fashion" check adding an overhead to every query made when a listed gateway is unavailable, we maintain a "blacklist" of nodes that have been seen to fail, and do not attempt to connect to them. There is a "deblacklister" thread that checks periodically whether it is now possible to connect to a previously-blacklisted gateway, and removes it from the list if so.

There are a number of potential questions and issues with this code:

  1. It would appear that a blacklist is applied even when there is only one gateway listed. In this case, the blacklist may be doing more harm than good? I'm not sure that is true - it is still causing rapid failures in cases that are never going to work...
  2. Even when there is only one gateway listed, a blacklist MIGHT still be useful (you don't really want EVERY query to block trying to connect, if the gateway is down - may prefer a fast failure). Also applies when there are multiple records, all being passed to a gateway, and with an ONFAIL.
  3. Is the blacklist shared between all queries? I'm pretty sure it is NOT shared across Roxie nodes... Looks like it is a global object, shared between all queries and activities. However, connections that were started in parallel will all be reported as failed rather than blacklisted, which can make it look like it is maintained per-activity.
  4. Is it only a failed connect that leads to blacklisting, or does a slow/error response also cause a gateway endpoint to be blacklisted? It's only a failed connect.
  5. When deblacklisting, can we check any condition other than "Successfully connected"? If not, blacklisting for any reason other than "Did not connect" feels like a recipe for problems. We only check for a connection (and correspondingly only blacklist for a failed connection).
  6. Are we ever using the functionality where there are more than one gateway listed? Most of the time a load-balancer is a preferable solution...
  7. The "deblacklister" thread seems to add an escalating delay between attempts. Is this delay ever reset? Is it configurable? Is it appropriate?
  8. There's a thread (in the blacklister's pool) for each blacklisted endpoint. These threads will never go away if the endpoint does not recover...
  9. There's a delay of up to 10 seconds in terminating caused by the deblacklister's connect having to timeout before it notices that we are stopping. Can we close the socket as well as interrupting the semaphore?
  10. Does the "reconnect" attempt from the deblacklister cause any pain for the server it is connecting to? Lots of connect attempts without any data could look like a DoS attack...
  11. Retries/timeout seems to translate to Owned<ISocketConnectWait> scw = nonBlockingConnect(ep, timeoutMS == WAIT_FOREVER ? 60000 : timeoutMS*(retries+1)); I am not sure that is correct (a single attempt to connect with a long timeout doesn't feel like it is the same as multiple attempts with shorter timeouts, for example if there is a load balancer in the mix).
  12. Perhaps an option to not use blacklister would solve the immediate issue?
  13. The blacklister uses an array of endpoints - If there were a lot blacklisted, a hash table would be better
  14. Hints to control behaviour of deblacklister would behave unpredictably if multiple activities connected to the same endpoint with different hints unless we make the blacklist lookup match the hint values too.
  15. Deblacklister should use nonBlockingConnect too.

Should the scope of the blacklist be different? Possible scopes are:

  1. Shared across all queries/activities (current behaviour)
  2. Specific to an activity, but shared across queries (i.e. owned by the activity factory)
  3. Specific to all activities in a deployed query (i.e. owned by the query factory)
  4. Specific to a particular activity instance (i.e. owned by the activity object)
  5. Specific to a particular query instance (i.e. owned by the query object)

Options 2 and 4 above would allow all aspects of the blacklisting behaviour to be specified by options on the SOAPCALL. We could control whether or not the blacklister is to be used at all via a SOAPCALL option with any of the above...

perftrace options

The HPCC Platform includes a rudimentary performance tracing feature using periodic stack capture to generate flame graphs. Roxie supports this in 3 ways:

  1. If expert/@profileStartup is set in roxie config, a flame graph is generated for operations during Roxie startup phase.
  2. If @perf is set on an incoming query, a flame graph is generated for the lifetime of that query's execution, and returned along with the query results
  3. If expert/perftrace is set in roxie config, one-shot roxie queries (e.g. eclagent mode) generate a flame graph (currently just to a text file).

The perf trace operates as follows:

  1. A child process is launched that runs the doperf script. This samples the current stack(s) every 0.2s (configurable) to a series of text files.
  2. When tracing is done, these text files are "folded" via a perl script that notes every unique stack and how many times it was seen, one line per unique stack
  3. This folded stack list is filtered to suppress some stacks that are not very interesting
  4. The filtered folded stack list is passed to another perl script that generates an svg file.

The basic info captured at step 1 (or maybe 2) could also be analysed to give other insights, such as:

  1. A list of "time in function, time in children of function".
  2. An expanded list of callers to __GI___lll_lock_wait and __GI___lll_lock_wake, to help spot contended critsecs.

Unfortunately some info present in the original stack text files is lost in the folded summary - in particular related to the TID that the stack is on. Can we spot lifetimes of threads and/or should we treat "stacks" on different threads as different? Thread pools might render this difficult though. There is an option in stack-collapse-elfutils.pl to include the TID when considering whether stacks match, so perhaps we should just (optionally) use that.

Some notes on LocalAgent mode

In localAgent mode, the global queueManager object (normally a RoxieUdpSocketQueueManager) is replaced by a RoxieLocalQueueManager. Outbound packets are added directly to target queue, inbound are packed into DataBuffers.

There is also "local optimizations" mode where any index operation reading a one-part file (does the same apply to one-part disk files?) just reads it directly on the server (regardless of localAgent setting). Typically still injected into receiver code though as otherwise handling exception cases, limits etc would all be duplicated/messy. Rows created in localOptimization mode are created directly in the caller's row manager, and are injected in serialized format.

Why are inbound not created directly in the desired destination's allocator and then marked as serialized? Some lifespan issues... are they insurmountable? We do pack into dataBuffers rather than MemoryBuffers, which avoids a need to copy the data before the receiver can use it. Large rows get split and will require copying again, but we could set dataBufferSize to be bigger in localAgent mode to mitigate this somewhat.

What is the lifespan issue? In-flight queries may be abandoned when a server-side query fails, times out, or no longer needs the data. Using DataBuffer does not have this issue as they are attached to the query's memory manager/allocation once read. Or we could bypass the agent queue altogether, but rather more refactoring needed for that (might almost be easier to extent the "local optimization" mode to use multiple threads at that point)

abortPending, replyPending, and abortPendingData methods are unimplemented, which may lead to some inefficiencies?

Some notes on UDP packet sending mechanism

Requests from server to agents are send via UDP (and have a size limit of 64k as a result). Historically they were sent using multicast to go to all agents on a channel at the same time, but since most cloud providers do not support multicast, there has long been an option to avoid multicast and send explicitly to the agent IPs. In bare metal systems these IPs are known via the topology file, and do not change. In cloud systems the topology server provides the IPs of all agents for a channel.

In cloud systems, the list of IPs that a message was sent to is included in the message header, so that the IBYTI messages can be sent without requiring that all agents/servers have the same topology information at any given moment (they will stay in sync because of topology server, but may be temporarily out of sync when nodes are added/removed, until next time topology info is retrieved). This is controled by the SUBCHANNELS_IN_HEADER define.

Packets back from agents to server go via the udplib message-passing code. This can best be described by looking at the sending and receiving sides separately.

When sending, results are split into individual packets (DataBuffers), each designed to be under 1 MTU in size. Traditionally this meant they were 1k, but they can be set larger (8k is good). They do have to be a power of 2 because of how they are allocated from the roxiemem heap. The sender maintains a set of UdpReceiverEntry objects, one for each server that it is conversing with. Each UdpReceiverEntry maintains multiple queues of data packets waiting to be sent, one queue for each priority. The UdpReceiverEntry maintains a count of how many packets are contained across all its queues in packetsQueued, so that it knows if there is data to send.

The priority levels are: 0: Out Of Band 1: Fast lane 2: Standard

This is designed to allow control information to be sent without getting blocked by data, and high priority queries to avoid being blocked by data going to lower priority ones. The mechanism for deciding what packet to send next is a little odd though - rather than sending all higher-priorty packets before any lower-priority ones, it round robins across the queues sending up to N^2 from queue 0 then up to N from queue 1 then 1 from queue 2, where N is set by the UdpOutQsPriority option, or 1 if not set. This may be a mistake - probably any from queue 0 should be sent first, before round-robining the other queues in this fashion.

UdpReceiverEntry objects are also responsible for maintaining a list of packets that have been sent but receiver has not yet indicated that they have arrived.

If an agent has data ready for a given receiver, it will send a requestToSend to that receiver, and wait for a permitToSend response. Sequence numbers are used to handle situations where these messages get lost. A permitToSend that does not contain the expected sequence number is ignored.

`,85)]))}const m=t(n,[["render",s]]);export{u as __pageData,m as default}; diff --git a/assets/devdoc_userdoc_AzureTipsTricks.md.BUnTMakG.js b/assets/devdoc_userdoc_AzureTipsTricks.md.BUnTMakG.js new file mode 100644 index 00000000000..c2403ac2837 --- /dev/null +++ b/assets/devdoc_userdoc_AzureTipsTricks.md.BUnTMakG.js @@ -0,0 +1 @@ +import{_ as e,c as r,o as s}from"./chunks/framework.DkhCEVKm.js";const _=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/AzureTipsTricks.md","filePath":"devdoc/userdoc/AzureTipsTricks.md","lastUpdated":1731340314000}'),t={name:"devdoc/userdoc/AzureTipsTricks.md"};function c(o,a,d,i,p,n){return s(),r("div")}const m=e(t,[["render",c]]);export{_ as __pageData,m as default}; diff --git a/assets/devdoc_userdoc_AzureTipsTricks.md.BUnTMakG.lean.js b/assets/devdoc_userdoc_AzureTipsTricks.md.BUnTMakG.lean.js new file mode 100644 index 00000000000..c2403ac2837 --- /dev/null +++ b/assets/devdoc_userdoc_AzureTipsTricks.md.BUnTMakG.lean.js @@ -0,0 +1 @@ +import{_ as e,c as r,o as s}from"./chunks/framework.DkhCEVKm.js";const _=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/AzureTipsTricks.md","filePath":"devdoc/userdoc/AzureTipsTricks.md","lastUpdated":1731340314000}'),t={name:"devdoc/userdoc/AzureTipsTricks.md"};function c(o,a,d,i,p,n){return s(),r("div")}const m=e(t,[["render",c]]);export{_ as __pageData,m as default}; diff --git a/assets/devdoc_userdoc_Blogs.md.D5ZHWDBF.js b/assets/devdoc_userdoc_Blogs.md.D5ZHWDBF.js new file mode 100644 index 00000000000..9b3bf6dd497 --- /dev/null +++ b/assets/devdoc_userdoc_Blogs.md.D5ZHWDBF.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as t}from"./chunks/framework.DkhCEVKm.js";const i=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/Blogs.md","filePath":"devdoc/userdoc/Blogs.md","lastUpdated":1731340314000}'),s={name:"devdoc/userdoc/Blogs.md"};function a(c,d,r,n,l,p){return t(),o("div")}const m=e(s,[["render",a]]);export{i as __pageData,m as default}; diff --git a/assets/devdoc_userdoc_Blogs.md.D5ZHWDBF.lean.js b/assets/devdoc_userdoc_Blogs.md.D5ZHWDBF.lean.js new file mode 100644 index 00000000000..9b3bf6dd497 --- /dev/null +++ b/assets/devdoc_userdoc_Blogs.md.D5ZHWDBF.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as t}from"./chunks/framework.DkhCEVKm.js";const i=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/Blogs.md","filePath":"devdoc/userdoc/Blogs.md","lastUpdated":1731340314000}'),s={name:"devdoc/userdoc/Blogs.md"};function a(c,d,r,n,l,p){return t(),o("div")}const m=e(s,[["render",a]]);export{i as __pageData,m as default}; diff --git a/assets/devdoc_userdoc_README.md.6WX0TGeB.js b/assets/devdoc_userdoc_README.md.6WX0TGeB.js new file mode 100644 index 00000000000..4a77359f86d --- /dev/null +++ b/assets/devdoc_userdoc_README.md.6WX0TGeB.js @@ -0,0 +1 @@ +import{_ as t,c as o,a3 as r,o as a}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"User Documentation","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/README.md","filePath":"devdoc/userdoc/README.md","lastUpdated":1731340314000}'),i={name:"devdoc/userdoc/README.md"};function s(n,e,l,c,u,d){return a(),o("div",null,e[0]||(e[0]=[r('

User Documentation

Documentation in this directory is targeted to end-users of the HPCC Systems Platform. This is less-formal documentation intended to be produced and released more quickly than the published HPCC documentation. See HPCC documentation if you would like to contribute to our official docs.

Directory structure under devdoc

INFO

  • userdoc/troubleshoot: Information related to troubleshooting particular components
  • userdoc/azure: Useful information about Azure Cloud portal and cli
  • userdoc/roxie: Useful information for running roxie
  • userdoc/thor: COMING SOON: Useful information for running thor
  • userdoc/blogs: COMING SOON: Location and instructions for all Blogs

General documentation

HPCC Website documentation

',8)]))}const f=t(i,[["render",s]]);export{m as __pageData,f as default}; diff --git a/assets/devdoc_userdoc_README.md.6WX0TGeB.lean.js b/assets/devdoc_userdoc_README.md.6WX0TGeB.lean.js new file mode 100644 index 00000000000..4a77359f86d --- /dev/null +++ b/assets/devdoc_userdoc_README.md.6WX0TGeB.lean.js @@ -0,0 +1 @@ +import{_ as t,c as o,a3 as r,o as a}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"User Documentation","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/README.md","filePath":"devdoc/userdoc/README.md","lastUpdated":1731340314000}'),i={name:"devdoc/userdoc/README.md"};function s(n,e,l,c,u,d){return a(),o("div",null,e[0]||(e[0]=[r('

User Documentation

Documentation in this directory is targeted to end-users of the HPCC Systems Platform. This is less-formal documentation intended to be produced and released more quickly than the published HPCC documentation. See HPCC documentation if you would like to contribute to our official docs.

Directory structure under devdoc

INFO

  • userdoc/troubleshoot: Information related to troubleshooting particular components
  • userdoc/azure: Useful information about Azure Cloud portal and cli
  • userdoc/roxie: Useful information for running roxie
  • userdoc/thor: COMING SOON: Useful information for running thor
  • userdoc/blogs: COMING SOON: Location and instructions for all Blogs

General documentation

HPCC Website documentation

',8)]))}const f=t(i,[["render",s]]);export{m as __pageData,f as default}; diff --git a/assets/devdoc_userdoc_WikiGuidelines.md.BcZTSYE9.js b/assets/devdoc_userdoc_WikiGuidelines.md.BcZTSYE9.js new file mode 100644 index 00000000000..5078df0b683 --- /dev/null +++ b/assets/devdoc_userdoc_WikiGuidelines.md.BcZTSYE9.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as i}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/WikiGuidelines.md","filePath":"devdoc/userdoc/WikiGuidelines.md","lastUpdated":1731340314000}'),d={name:"devdoc/userdoc/WikiGuidelines.md"};function s(o,a,c,r,n,l){return i(),t("div")}const _=e(d,[["render",s]]);export{u as __pageData,_ as default}; diff --git a/assets/devdoc_userdoc_WikiGuidelines.md.BcZTSYE9.lean.js b/assets/devdoc_userdoc_WikiGuidelines.md.BcZTSYE9.lean.js new file mode 100644 index 00000000000..5078df0b683 --- /dev/null +++ b/assets/devdoc_userdoc_WikiGuidelines.md.BcZTSYE9.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as i}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/WikiGuidelines.md","filePath":"devdoc/userdoc/WikiGuidelines.md","lastUpdated":1731340314000}'),d={name:"devdoc/userdoc/WikiGuidelines.md"};function s(o,a,c,r,n,l){return i(),t("div")}const _=e(d,[["render",s]]);export{u as __pageData,_ as default}; diff --git a/assets/devdoc_userdoc_azure_TipsAndTricks.md.2OyOG7Og.js b/assets/devdoc_userdoc_azure_TipsAndTricks.md.2OyOG7Og.js new file mode 100644 index 00000000000..6d8e796e2af --- /dev/null +++ b/assets/devdoc_userdoc_azure_TipsAndTricks.md.2OyOG7Og.js @@ -0,0 +1,54 @@ +import{_ as n,c as a,a3 as p,o as e}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Azure Portal FAQs","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/azure/TipsAndTricks.md","filePath":"devdoc/userdoc/azure/TipsAndTricks.md","lastUpdated":1731340314000}'),l={name:"devdoc/userdoc/azure/TipsAndTricks.md"};function t(o,s,i,c,r,d){return e(),a("div",null,s[0]||(s[0]=[p(`

Azure Portal FAQs

  1. How do I see who created a resource group?

Answer:

1. go to the resource group service page (https://portal.azure.com/#view/HubsExtension/BrowseResourceGroups)
+2. "Add filter"
+3. Filter on "Admin"
+4. Set the Value to "ALL" or select the names you are interested in (the names in the list are the only ones that have the Admin tag set)  This works for all other fields that you can filter on
  1. How do I create a dashboard in Azure?

Answer:

Login to the Azure portal.
+
+Click on the \`hamburger icon\`, located at the top left corner of the page.
+
+Click on \`Dashboard\` to go to your dashboards.
+
+Click on \`Create\`, located at the top left corner.
+
+Click on the \`Custom\` tile.
+
+Edit the input box to name your dashboard.
+
+Click on \`Resource groups\` in the tile gallery.
+
+Click \`Add\`.
+
+Click and drag the \`lower right corner\` of the tile to resize it to your likings.
+
+Click \`Save\` to save your settings.
+
+You should now be taken to your new dashboard.
+
+Click on your new dashboard tile.
+
+Click on \`Add filter\`, located at the top center of the page.
+
+Click on the \`Filter\` input box to reveal the tags.
+
+Select the \`Admin\` tag.
+
+Click on the \`Value\` input box.
+
+Click on \`Select all\` to unselect all.
+
+Select your name.
+
+Click on \`Apply\`.
+
+Next, click on \`Manage view\`, located at the top left of the page.
+
+Select \`Save view\`.
+
+Enter a name for the view in the input box.
+
+Click \`Save\`
+
+ 
+
+ 
+
+Click on \`Manage View\`, located at the top left of the page
`,7)]))}const g=n(l,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/devdoc_userdoc_azure_TipsAndTricks.md.2OyOG7Og.lean.js b/assets/devdoc_userdoc_azure_TipsAndTricks.md.2OyOG7Og.lean.js new file mode 100644 index 00000000000..6d8e796e2af --- /dev/null +++ b/assets/devdoc_userdoc_azure_TipsAndTricks.md.2OyOG7Og.lean.js @@ -0,0 +1,54 @@ +import{_ as n,c as a,a3 as p,o as e}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Azure Portal FAQs","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/azure/TipsAndTricks.md","filePath":"devdoc/userdoc/azure/TipsAndTricks.md","lastUpdated":1731340314000}'),l={name:"devdoc/userdoc/azure/TipsAndTricks.md"};function t(o,s,i,c,r,d){return e(),a("div",null,s[0]||(s[0]=[p(`

Azure Portal FAQs

  1. How do I see who created a resource group?

Answer:

1. go to the resource group service page (https://portal.azure.com/#view/HubsExtension/BrowseResourceGroups)
+2. "Add filter"
+3. Filter on "Admin"
+4. Set the Value to "ALL" or select the names you are interested in (the names in the list are the only ones that have the Admin tag set)  This works for all other fields that you can filter on
  1. How do I create a dashboard in Azure?

Answer:

Login to the Azure portal.
+
+Click on the \`hamburger icon\`, located at the top left corner of the page.
+
+Click on \`Dashboard\` to go to your dashboards.
+
+Click on \`Create\`, located at the top left corner.
+
+Click on the \`Custom\` tile.
+
+Edit the input box to name your dashboard.
+
+Click on \`Resource groups\` in the tile gallery.
+
+Click \`Add\`.
+
+Click and drag the \`lower right corner\` of the tile to resize it to your likings.
+
+Click \`Save\` to save your settings.
+
+You should now be taken to your new dashboard.
+
+Click on your new dashboard tile.
+
+Click on \`Add filter\`, located at the top center of the page.
+
+Click on the \`Filter\` input box to reveal the tags.
+
+Select the \`Admin\` tag.
+
+Click on the \`Value\` input box.
+
+Click on \`Select all\` to unselect all.
+
+Select your name.
+
+Click on \`Apply\`.
+
+Next, click on \`Manage view\`, located at the top left of the page.
+
+Select \`Save view\`.
+
+Enter a name for the view in the input box.
+
+Click \`Save\`
+
+ 
+
+ 
+
+Click on \`Manage View\`, located at the top left of the page
`,7)]))}const g=n(l,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/devdoc_userdoc_copilot_CopilotPromptTips.md._YhzUQ6a.js b/assets/devdoc_userdoc_copilot_CopilotPromptTips.md._YhzUQ6a.js new file mode 100644 index 00000000000..61b8eed4679 --- /dev/null +++ b/assets/devdoc_userdoc_copilot_CopilotPromptTips.md._YhzUQ6a.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as a}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Copilot Prompt Tips","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/copilot/CopilotPromptTips.md","filePath":"devdoc/userdoc/copilot/CopilotPromptTips.md","lastUpdated":1731340314000}'),r={name:"devdoc/userdoc/copilot/CopilotPromptTips.md"};function s(n,e,l,p,c,u){return a(),i("div",null,e[0]||(e[0]=[o('

Copilot Prompt Tips

Unlock the full potential of GitHub Copilot with these carefully curated prompts designed to streamline your workflow and enhance productivity.

Whether you're summarizing complex texts, brainstorming innovative ideas, or creating detailed guides, these prompts will help you get the most out of Copilot's capabilities.

Dive in and discover how these simple yet powerful prompts can save you time and effort in your daily tasks.

Generic Prompts

Here are a few simple prompts that can save time:

  • Provide a brief summary of the text below. Show the key points in a bullet list.

  • List [N (number)] ways to [accomplish a specific goal or solve a problem]? Include a short description for each approach in a bullet list.

    • Example: List 10 ways to reduce redundancy in text. Include a short description for each approach in a bullet list.
  • What are the key differences/similarities between [concept A] and [concept B]? Present the information in a table format.

    • Example: What are the key differences/similarities between a quicksort ad a bubble sort? Present the information in a table format.
  • Explain [complex topic] in simple terms. Use analogies or examples to help make it easier to understand.

    • Example: Explain generative AI in simple terms. Use analogies or examples to help make it easier to understand.
  • Brainstorm [N (number)] ideas for [a specific project or topic]? Include a short description for each idea in a bullet list.

    • Example: Brainstorm 7 ideas for an instructional video for new programmers? Include a short description for each idea in a bullet list.
  • Create a template for [a specific type of document, such as a business email, proposal, etc.]. Include the key elements to include in a bullet list.

    • Example: Create a template for product version release announcement. Include the key elements to include in a bullet list.
  • Write a step-by-step guide on how to [specific task or procedure]. Number the steps to improve clarity.

    • Example: Write a step-by-step guide on how to make a PB & J sandwich. Number the steps to improve clarity.

Specific Prompts

These prompts are more focused and are meant to accomplish a specific purpose. They are included here to spark your imagination.

If you think of others that could be included here to share with the world, please send your ideas to docfeedback@hpccsystems.com.

  • Write comments for this ECL code in javadoc format

  • Create an ECL File Definition, record structure, and inline dataset with [N (number)] of records with the following fields [list of fields]

    • Example: Create an ECL file definition with a record structure and an inline dataset with 20 records for a dataset of animals with the following fields: ReferenceNumber, AnimalName, ClassName, FamilyName, Color, Size, and LifeExpectancy.
  • Write ECL code to classify records into [categories].

    • Example: Write ECL code to classify customer feedback into neutral, negative, or positive categories.

How to Avoid AI Hallucinations with Good Prompts

AI hallucinations refer to instances where artificial intelligence systems generate information or responses that are incorrect, misleading, or entirely fabricated.

Hallucinations can occur due to various reasons, such as limitations in the training data, inherent biases, or the AI's attempt to provide an answer even when it lacks sufficient context or knowledge.

Understanding and mitigating AI hallucinations is crucial for ensuring the reliability and accuracy of AI-driven applications.

Creating effective prompts is essential to minimize AI hallucinations. Here are some tips to help you craft prompts that lead to accurate and reliable responses:

  • Be Specific and Clear: Ambiguous prompts can lead to incorrect or irrelevant answers. Clearly define the task and provide specific instructions.

    • Example: Instead of asking "What is AI?", ask "Explain the concept of artificial intelligence and its primary applications in healthcare."
  • Provide Context: Give the AI enough background information to understand the task. This helps in generating more accurate responses.

    • Example: "Given the following text about climate change, summarize the key points in a bullet list."
  • Use Constraints: Limit the scope of the response by specifying constraints such as word count, format, or specific details to include.

    • Example: "List 5 benefits of renewable energy in a bullet list, each point not exceeding 20 words."
  • Ask for Evidence or Sources: Encourage the AI to provide evidence or cite sources for the information it generates.

    • Example: "Explain the impact of social media on mental health and provide references to recent studies."
  • Iterative Refinement: Start with a broad prompt and refine it based on the initial responses to get more accurate results.

    • Example: Begin with "Describe the process of photosynthesis." If the response is too vague, refine it to "Describe the process of photosynthesis in plants, including the role of chlorophyll and sunlight."

By following these guidelines, you can reduce the likelihood of AI hallucinations and ensure that the responses generated are accurate and useful.

',18)]))}const m=t(r,[["render",s]]);export{h as __pageData,m as default}; diff --git a/assets/devdoc_userdoc_copilot_CopilotPromptTips.md._YhzUQ6a.lean.js b/assets/devdoc_userdoc_copilot_CopilotPromptTips.md._YhzUQ6a.lean.js new file mode 100644 index 00000000000..61b8eed4679 --- /dev/null +++ b/assets/devdoc_userdoc_copilot_CopilotPromptTips.md._YhzUQ6a.lean.js @@ -0,0 +1 @@ +import{_ as t,c as i,a3 as o,o as a}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Copilot Prompt Tips","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/copilot/CopilotPromptTips.md","filePath":"devdoc/userdoc/copilot/CopilotPromptTips.md","lastUpdated":1731340314000}'),r={name:"devdoc/userdoc/copilot/CopilotPromptTips.md"};function s(n,e,l,p,c,u){return a(),i("div",null,e[0]||(e[0]=[o('

Copilot Prompt Tips

Unlock the full potential of GitHub Copilot with these carefully curated prompts designed to streamline your workflow and enhance productivity.

Whether you're summarizing complex texts, brainstorming innovative ideas, or creating detailed guides, these prompts will help you get the most out of Copilot's capabilities.

Dive in and discover how these simple yet powerful prompts can save you time and effort in your daily tasks.

Generic Prompts

Here are a few simple prompts that can save time:

  • Provide a brief summary of the text below. Show the key points in a bullet list.

  • List [N (number)] ways to [accomplish a specific goal or solve a problem]? Include a short description for each approach in a bullet list.

    • Example: List 10 ways to reduce redundancy in text. Include a short description for each approach in a bullet list.
  • What are the key differences/similarities between [concept A] and [concept B]? Present the information in a table format.

    • Example: What are the key differences/similarities between a quicksort ad a bubble sort? Present the information in a table format.
  • Explain [complex topic] in simple terms. Use analogies or examples to help make it easier to understand.

    • Example: Explain generative AI in simple terms. Use analogies or examples to help make it easier to understand.
  • Brainstorm [N (number)] ideas for [a specific project or topic]? Include a short description for each idea in a bullet list.

    • Example: Brainstorm 7 ideas for an instructional video for new programmers? Include a short description for each idea in a bullet list.
  • Create a template for [a specific type of document, such as a business email, proposal, etc.]. Include the key elements to include in a bullet list.

    • Example: Create a template for product version release announcement. Include the key elements to include in a bullet list.
  • Write a step-by-step guide on how to [specific task or procedure]. Number the steps to improve clarity.

    • Example: Write a step-by-step guide on how to make a PB & J sandwich. Number the steps to improve clarity.

Specific Prompts

These prompts are more focused and are meant to accomplish a specific purpose. They are included here to spark your imagination.

If you think of others that could be included here to share with the world, please send your ideas to docfeedback@hpccsystems.com.

  • Write comments for this ECL code in javadoc format

  • Create an ECL File Definition, record structure, and inline dataset with [N (number)] of records with the following fields [list of fields]

    • Example: Create an ECL file definition with a record structure and an inline dataset with 20 records for a dataset of animals with the following fields: ReferenceNumber, AnimalName, ClassName, FamilyName, Color, Size, and LifeExpectancy.
  • Write ECL code to classify records into [categories].

    • Example: Write ECL code to classify customer feedback into neutral, negative, or positive categories.

How to Avoid AI Hallucinations with Good Prompts

AI hallucinations refer to instances where artificial intelligence systems generate information or responses that are incorrect, misleading, or entirely fabricated.

Hallucinations can occur due to various reasons, such as limitations in the training data, inherent biases, or the AI's attempt to provide an answer even when it lacks sufficient context or knowledge.

Understanding and mitigating AI hallucinations is crucial for ensuring the reliability and accuracy of AI-driven applications.

Creating effective prompts is essential to minimize AI hallucinations. Here are some tips to help you craft prompts that lead to accurate and reliable responses:

  • Be Specific and Clear: Ambiguous prompts can lead to incorrect or irrelevant answers. Clearly define the task and provide specific instructions.

    • Example: Instead of asking "What is AI?", ask "Explain the concept of artificial intelligence and its primary applications in healthcare."
  • Provide Context: Give the AI enough background information to understand the task. This helps in generating more accurate responses.

    • Example: "Given the following text about climate change, summarize the key points in a bullet list."
  • Use Constraints: Limit the scope of the response by specifying constraints such as word count, format, or specific details to include.

    • Example: "List 5 benefits of renewable energy in a bullet list, each point not exceeding 20 words."
  • Ask for Evidence or Sources: Encourage the AI to provide evidence or cite sources for the information it generates.

    • Example: "Explain the impact of social media on mental health and provide references to recent studies."
  • Iterative Refinement: Start with a broad prompt and refine it based on the initial responses to get more accurate results.

    • Example: Begin with "Describe the process of photosynthesis." If the response is too vague, refine it to "Describe the process of photosynthesis in plants, including the role of chlorophyll and sunlight."

By following these guidelines, you can reduce the likelihood of AI hallucinations and ensure that the responses generated are accurate and useful.

',18)]))}const m=t(r,[["render",s]]);export{h as __pageData,m as default}; diff --git a/assets/devdoc_userdoc_roxie_FAQ.md.Cbz7RPiY.js b/assets/devdoc_userdoc_roxie_FAQ.md.Cbz7RPiY.js new file mode 100644 index 00000000000..6d996e5cfe3 --- /dev/null +++ b/assets/devdoc_userdoc_roxie_FAQ.md.Cbz7RPiY.js @@ -0,0 +1,24 @@ +import{_ as a,c as e,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const g=JSON.parse('{"title":"ROXIE FAQs","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/roxie/FAQ.md","filePath":"devdoc/userdoc/roxie/FAQ.md","lastUpdated":1731340314000}'),o={name:"devdoc/userdoc/roxie/FAQ.md"};function p(i,s,l,r,c,d){return n(),e("div",null,s[0]||(s[0]=[t(`

ROXIE FAQs

  1. How I can compile a query on a containerized or cloud-based system?

Answer:

Same way as bare metal. Command line, or with the IDE, or from ECL Watch. Just point to the HPCC Systems instance to compile.
+For Example:
+ecl deploy <target> <file>
  1. How do I copy queries from an on-prem cluster to Azure?

Answer:

The copy query command – use the Azure host name or IP address for the target.
+For example:
+ecl queries copy <source_query_path> <target_queryset>
  1. How can I get the IP address for the Azure target cluster?

Answer:

Use the "kubectl get svc" command. Use the external IP address listed for ECL Watch.
+kubectl get svc
  1. Do we have to have use the DNSName or do we need to use the IP address?

Answer:

If you can reach ECL Watch with the DNS Name then it should also work for the command line.
  1. How can I find the ECL Watch or Dali hostname?

Answer:

If you did not set up the containerized instance, then you need to ask your Systems Administrator or whomever set it up..
  1. How do I publish a package file?

Answer:

Same way as bare metal.
+To add a new package file: ecl packagemap add or
+To copy exisitng package file : ecl packagemap copy
  1. How do I check the logs?

Answer:

kubectl log <podname>
+in addition you can use -f (follow) option to tail the logs. Optionally you can also issue the <namespace> parameter.
+For example:
+kbectl log roxie-agent-1-3b12a587b –namespace MyNameSpace
+Optionally, you may have implemented a log-processing solution such as the Elastic Stack (elastic4hpcclogs).
  1. How do I get the data on to Azure?

Answer:

Use the copy query command and copy or add the Packagemap.
+With data copy start in the logs…copy from remote location specified if data doesn’t exist on the local system.
+The remote location is the remote Dali (use the --daliip=<daliIP> parameter to specify the remote Dali)
+You can also use ECL Watch.
  1. How can I start a cloud cluster? (akin to the old Virtual Box image)?

Answer:

Can use Docker Desktop, or Azure or any cloud provider and install the HPCC Systems Cloud native helm
+charts
  1. How can I show the ECL queries that are published to a given Roxie?

Answer:

Can use WUListQueries
+For example:
+https://[eclwatch]:18010/WsWorkunits/WUListQueries.json?ver_=1.86&ClusterName=roxie&CheckAllNodes=0
  1. I set up persistent storage on my containerized HPCC Systems, and now it won't start. Why?

Answer:

One possible reason may be that all of the required storage directories are not present. The directories for ~/
+hpccdata/dalistorage, hpcc-data, debug, queries, sasha, and dropzone are all required to exist or your cluster may not start.
  1. Are there any new methods available to work with queries?

Answer:

Yes. There is a new method available ServiceQuery.
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&
+For example Roxie Queries:
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&Type=roxie
+or WsECL (eclqueries)
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&Type=eclqueries
`,37)]))}const u=a(o,[["render",p]]);export{g as __pageData,u as default}; diff --git a/assets/devdoc_userdoc_roxie_FAQ.md.Cbz7RPiY.lean.js b/assets/devdoc_userdoc_roxie_FAQ.md.Cbz7RPiY.lean.js new file mode 100644 index 00000000000..6d996e5cfe3 --- /dev/null +++ b/assets/devdoc_userdoc_roxie_FAQ.md.Cbz7RPiY.lean.js @@ -0,0 +1,24 @@ +import{_ as a,c as e,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const g=JSON.parse('{"title":"ROXIE FAQs","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/roxie/FAQ.md","filePath":"devdoc/userdoc/roxie/FAQ.md","lastUpdated":1731340314000}'),o={name:"devdoc/userdoc/roxie/FAQ.md"};function p(i,s,l,r,c,d){return n(),e("div",null,s[0]||(s[0]=[t(`

ROXIE FAQs

  1. How I can compile a query on a containerized or cloud-based system?

Answer:

Same way as bare metal. Command line, or with the IDE, or from ECL Watch. Just point to the HPCC Systems instance to compile.
+For Example:
+ecl deploy <target> <file>
  1. How do I copy queries from an on-prem cluster to Azure?

Answer:

The copy query command – use the Azure host name or IP address for the target.
+For example:
+ecl queries copy <source_query_path> <target_queryset>
  1. How can I get the IP address for the Azure target cluster?

Answer:

Use the "kubectl get svc" command. Use the external IP address listed for ECL Watch.
+kubectl get svc
  1. Do we have to have use the DNSName or do we need to use the IP address?

Answer:

If you can reach ECL Watch with the DNS Name then it should also work for the command line.
  1. How can I find the ECL Watch or Dali hostname?

Answer:

If you did not set up the containerized instance, then you need to ask your Systems Administrator or whomever set it up..
  1. How do I publish a package file?

Answer:

Same way as bare metal.
+To add a new package file: ecl packagemap add or
+To copy exisitng package file : ecl packagemap copy
  1. How do I check the logs?

Answer:

kubectl log <podname>
+in addition you can use -f (follow) option to tail the logs. Optionally you can also issue the <namespace> parameter.
+For example:
+kbectl log roxie-agent-1-3b12a587b –namespace MyNameSpace
+Optionally, you may have implemented a log-processing solution such as the Elastic Stack (elastic4hpcclogs).
  1. How do I get the data on to Azure?

Answer:

Use the copy query command and copy or add the Packagemap.
+With data copy start in the logs…copy from remote location specified if data doesn’t exist on the local system.
+The remote location is the remote Dali (use the --daliip=<daliIP> parameter to specify the remote Dali)
+You can also use ECL Watch.
  1. How can I start a cloud cluster? (akin to the old Virtual Box image)?

Answer:

Can use Docker Desktop, or Azure or any cloud provider and install the HPCC Systems Cloud native helm
+charts
  1. How can I show the ECL queries that are published to a given Roxie?

Answer:

Can use WUListQueries
+For example:
+https://[eclwatch]:18010/WsWorkunits/WUListQueries.json?ver_=1.86&ClusterName=roxie&CheckAllNodes=0
  1. I set up persistent storage on my containerized HPCC Systems, and now it won't start. Why?

Answer:

One possible reason may be that all of the required storage directories are not present. The directories for ~/
+hpccdata/dalistorage, hpcc-data, debug, queries, sasha, and dropzone are all required to exist or your cluster may not start.
  1. Are there any new methods available to work with queries?

Answer:

Yes. There is a new method available ServiceQuery.
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&
+For example Roxie Queries:
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&Type=roxie
+or WsECL (eclqueries)
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&Type=eclqueries
`,37)]))}const u=a(o,[["render",p]]);export{g as __pageData,u as default}; diff --git a/assets/devdoc_userdoc_troubleshoot_ClientsToolIssues.md.Buqpsc4y.js b/assets/devdoc_userdoc_troubleshoot_ClientsToolIssues.md.Buqpsc4y.js new file mode 100644 index 00000000000..af4aefe1e69 --- /dev/null +++ b/assets/devdoc_userdoc_troubleshoot_ClientsToolIssues.md.Buqpsc4y.js @@ -0,0 +1 @@ +import{_ as t,c as o,a3 as s,o as l}from"./chunks/framework.DkhCEVKm.js";const r="/HPCC-Platform/assets/ECLIDE-WindowsProtectionError.ZJeAxFtk.png",a="/HPCC-Platform/assets/ECLIDE-AppProperties.CzKpIibe.png",C=JSON.parse('{"title":"How to resolve issues installing the ECL IDE / Client tools","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/troubleshoot/ClientsToolIssues.md","filePath":"devdoc/userdoc/troubleshoot/ClientsToolIssues.md","lastUpdated":1731340314000}'),i={name:"devdoc/userdoc/troubleshoot/ClientsToolIssues.md"};function n(c,e,d,h,u,p){return l(),o("div",null,e[0]||(e[0]=[s('

How to resolve issues installing the ECL IDE / Client tools

Problem

Some users may experience an issue when trying to install the ECL IDE / client tools version 8.10 and later.
If you get an error saying something like, Windows Defender SmartScreen prevented access and there is no option other than "Don't Run":

  • Go to the folder where you downloaded the file.
  • Once there go to the app file properties click unblock

alt-text

alt-text

',6)]))}const f=t(i,[["render",n]]);export{C as __pageData,f as default}; diff --git a/assets/devdoc_userdoc_troubleshoot_ClientsToolIssues.md.Buqpsc4y.lean.js b/assets/devdoc_userdoc_troubleshoot_ClientsToolIssues.md.Buqpsc4y.lean.js new file mode 100644 index 00000000000..af4aefe1e69 --- /dev/null +++ b/assets/devdoc_userdoc_troubleshoot_ClientsToolIssues.md.Buqpsc4y.lean.js @@ -0,0 +1 @@ +import{_ as t,c as o,a3 as s,o as l}from"./chunks/framework.DkhCEVKm.js";const r="/HPCC-Platform/assets/ECLIDE-WindowsProtectionError.ZJeAxFtk.png",a="/HPCC-Platform/assets/ECLIDE-AppProperties.CzKpIibe.png",C=JSON.parse('{"title":"How to resolve issues installing the ECL IDE / Client tools","description":"","frontmatter":{},"headers":[],"relativePath":"devdoc/userdoc/troubleshoot/ClientsToolIssues.md","filePath":"devdoc/userdoc/troubleshoot/ClientsToolIssues.md","lastUpdated":1731340314000}'),i={name:"devdoc/userdoc/troubleshoot/ClientsToolIssues.md"};function n(c,e,d,h,u,p){return l(),o("div",null,e[0]||(e[0]=[s('

How to resolve issues installing the ECL IDE / Client tools

Problem

Some users may experience an issue when trying to install the ECL IDE / client tools version 8.10 and later.
If you get an error saying something like, Windows Defender SmartScreen prevented access and there is no option other than "Don't Run":

  • Go to the folder where you downloaded the file.
  • Once there go to the app file properties click unblock

alt-text

alt-text

',6)]))}const f=t(i,[["render",n]]);export{C as __pageData,f as default}; diff --git a/assets/ecl_ecl-bundle_DOCUMENTATION.md.rUiAyrKM.js b/assets/ecl_ecl-bundle_DOCUMENTATION.md.rUiAyrKM.js new file mode 100644 index 00000000000..b47e2e24d43 --- /dev/null +++ b/assets/ecl_ecl-bundle_DOCUMENTATION.md.rUiAyrKM.js @@ -0,0 +1 @@ +import{_ as t,c as l,a3 as a,o as n}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"Ecl-bundle source documentation","description":"","frontmatter":{},"headers":[],"relativePath":"ecl/ecl-bundle/DOCUMENTATION.md","filePath":"ecl/ecl-bundle/DOCUMENTATION.md","lastUpdated":1731340314000}'),i={name:"ecl/ecl-bundle/DOCUMENTATION.md"};function o(r,e,s,d,c,u){return n(),l("div",null,e[0]||(e[0]=[a('

Ecl-bundle source documentation

Introduction

Purpose

The ecl-bundle executable (normally executed from the ecl executable by specifying 'ecl bundle XXX' is designed to manipulate ecl bundle files

  • these are self-contained parcels of ECL code, packaged into a compressed file like a zip or .tar.gz file, that can be downloaded, installed, removed etc from an ECL installation.

Design

The metadata for ECL bundles is described using an exported module called Bundle within the bundle's source tree - typically, this means that a file called Bundle.ecl will be added to the highest level of the bundle's directory tree. In order to extract the information from the Bundle module, eclcc is run in 'evaluate' mode (using the -Me option) which will parse the bundle module and output the required fields to stdout.

ecl-bundle also executes eclcc (using the --showpaths option) to determine where bundle files are to be located.

Directory structure

In order to make versioning easier, bundle files are not copied directly into the bundles directory. A bundle called "MyBundle" that announces itself as version "x.y.z" will be installed to the directory

$BUNDLEDIR/_versions/MyBundle/x.y.z

A "redirect" file called MyBundle.ecl is then created in $BUNDLEDIR, which redirects any IMPORT MyBundle statement to actually import the currently active version of the bundle in _versions/MyBundle/x.y.z

By rewriting this redirect file, it is possible to switch to using a different version of a bundle without having to uninstall and reinstall.

In a future release, we hope to make it possible to specify that bundle A requires version X of bundle B, while bundle C requires version Y of bundle B. That will require the redirect files to be 'local' to a bundle (and will require that bundle B uses a redirect file to ensure it picks up the local copy of B when making internal calls).

Key classes

An IBundleInfo represents a specific copy of a bundle, and is created by explicitly parsing a snippet of ECL that imports it, with the ECL include path set to include only the specified bundle.

An IBundleInfoSet represents all the installed versions of a particular named bundle.

An IBundleCollection represents all the bundles on the system.

Every individual subcommand is represented by a class derived (directly or indirectly) from EclCmdCommon. These classes are responsible for command-line parsing, usage text output, and (most importantly) execution of the desired outcomes.

',19)]))}const b=t(i,[["render",o]]);export{p as __pageData,b as default}; diff --git a/assets/ecl_ecl-bundle_DOCUMENTATION.md.rUiAyrKM.lean.js b/assets/ecl_ecl-bundle_DOCUMENTATION.md.rUiAyrKM.lean.js new file mode 100644 index 00000000000..b47e2e24d43 --- /dev/null +++ b/assets/ecl_ecl-bundle_DOCUMENTATION.md.rUiAyrKM.lean.js @@ -0,0 +1 @@ +import{_ as t,c as l,a3 as a,o as n}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"Ecl-bundle source documentation","description":"","frontmatter":{},"headers":[],"relativePath":"ecl/ecl-bundle/DOCUMENTATION.md","filePath":"ecl/ecl-bundle/DOCUMENTATION.md","lastUpdated":1731340314000}'),i={name:"ecl/ecl-bundle/DOCUMENTATION.md"};function o(r,e,s,d,c,u){return n(),l("div",null,e[0]||(e[0]=[a('

Ecl-bundle source documentation

Introduction

Purpose

The ecl-bundle executable (normally executed from the ecl executable by specifying 'ecl bundle XXX' is designed to manipulate ecl bundle files

  • these are self-contained parcels of ECL code, packaged into a compressed file like a zip or .tar.gz file, that can be downloaded, installed, removed etc from an ECL installation.

Design

The metadata for ECL bundles is described using an exported module called Bundle within the bundle's source tree - typically, this means that a file called Bundle.ecl will be added to the highest level of the bundle's directory tree. In order to extract the information from the Bundle module, eclcc is run in 'evaluate' mode (using the -Me option) which will parse the bundle module and output the required fields to stdout.

ecl-bundle also executes eclcc (using the --showpaths option) to determine where bundle files are to be located.

Directory structure

In order to make versioning easier, bundle files are not copied directly into the bundles directory. A bundle called "MyBundle" that announces itself as version "x.y.z" will be installed to the directory

$BUNDLEDIR/_versions/MyBundle/x.y.z

A "redirect" file called MyBundle.ecl is then created in $BUNDLEDIR, which redirects any IMPORT MyBundle statement to actually import the currently active version of the bundle in _versions/MyBundle/x.y.z

By rewriting this redirect file, it is possible to switch to using a different version of a bundle without having to uninstall and reinstall.

In a future release, we hope to make it possible to specify that bundle A requires version X of bundle B, while bundle C requires version Y of bundle B. That will require the redirect files to be 'local' to a bundle (and will require that bundle B uses a redirect file to ensure it picks up the local copy of B when making internal calls).

Key classes

An IBundleInfo represents a specific copy of a bundle, and is created by explicitly parsing a snippet of ECL that imports it, with the ECL include path set to include only the specified bundle.

An IBundleInfoSet represents all the installed versions of a particular named bundle.

An IBundleCollection represents all the bundles on the system.

Every individual subcommand is represented by a class derived (directly or indirectly) from EclCmdCommon. These classes are responsible for command-line parsing, usage text output, and (most importantly) execution of the desired outcomes.

',19)]))}const b=t(i,[["render",o]]);export{p as __pageData,b as default}; diff --git a/assets/ecllibrary_StyleGuide.md.CjMQ9yWq.js b/assets/ecllibrary_StyleGuide.md.CjMQ9yWq.js new file mode 100644 index 00000000000..4b6c2a2a4b4 --- /dev/null +++ b/assets/ecllibrary_StyleGuide.md.CjMQ9yWq.js @@ -0,0 +1,20 @@ +import{_ as i,c as a,a3 as e,o as n}from"./chunks/framework.DkhCEVKm.js";const o=JSON.parse('{"title":"ECL standard library style guide","description":"","frontmatter":{},"headers":[],"relativePath":"ecllibrary/StyleGuide.md","filePath":"ecllibrary/StyleGuide.md","lastUpdated":1731340314000}'),l={name:"ecllibrary/StyleGuide.md"};function t(h,s,p,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

ECL standard library style guide

The ECL code in the standard library should follow the following style guidelines:

  • All ECL keywords in upper case
  • ECL reserved types in upper case
  • Public attributes in camel case with leading upper case
  • Private attributes in lower case with underscore as a separator
  • Field names in lower case with underscore as a separator
  • Standard indent is 2 spaces (no tabs)
  • Maximum line length of 120 characters
  • Compound statements have contents indented, and END is aligned with the opening statement
  • Field names are not indented to make them line up within a record structure
  • Parameters are indented as necessary
  • Use javadoc style comments on all functions/attributes (see Writing Javadoc Comments)

For example:

ecl
my_record := RECORD
+    INTEGER4 id;
+    STRING firstname{MAXLENGTH(40)};
+    STRING lastname{MAXLENGTH(50)};
+END;
+
+/**
+  * Returns a dataset of people to treat with caution matching a particular lastname.  The
+  * names are maintained in a global database of undesirables.
+  *
+  * @param  search_lastname    A lastname used as a filter
+  * @return                    The list of people
+  * @see                       NoFlyList
+  * @see                       MorePeopleToAvoid
+  */
+
+EXPORT DodgyCharacters(STRING search_lastname) := FUNCTION
+    raw_ds := DATASET(my_record, 'undesirables', THOR);
+    RETURN raw_ds(last_name = search_lastname);
+END;

Some additional rules for attributes in the library:

  • Services should be SHARED and EXPORTed via intermediate attributes
  • All attributes must have at least one matching test. If you're not on the test list you're not coming in.
`,7)]))}const c=i(l,[["render",t]]);export{o as __pageData,c as default}; diff --git a/assets/ecllibrary_StyleGuide.md.CjMQ9yWq.lean.js b/assets/ecllibrary_StyleGuide.md.CjMQ9yWq.lean.js new file mode 100644 index 00000000000..4b6c2a2a4b4 --- /dev/null +++ b/assets/ecllibrary_StyleGuide.md.CjMQ9yWq.lean.js @@ -0,0 +1,20 @@ +import{_ as i,c as a,a3 as e,o as n}from"./chunks/framework.DkhCEVKm.js";const o=JSON.parse('{"title":"ECL standard library style guide","description":"","frontmatter":{},"headers":[],"relativePath":"ecllibrary/StyleGuide.md","filePath":"ecllibrary/StyleGuide.md","lastUpdated":1731340314000}'),l={name:"ecllibrary/StyleGuide.md"};function t(h,s,p,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

ECL standard library style guide

The ECL code in the standard library should follow the following style guidelines:

  • All ECL keywords in upper case
  • ECL reserved types in upper case
  • Public attributes in camel case with leading upper case
  • Private attributes in lower case with underscore as a separator
  • Field names in lower case with underscore as a separator
  • Standard indent is 2 spaces (no tabs)
  • Maximum line length of 120 characters
  • Compound statements have contents indented, and END is aligned with the opening statement
  • Field names are not indented to make them line up within a record structure
  • Parameters are indented as necessary
  • Use javadoc style comments on all functions/attributes (see Writing Javadoc Comments)

For example:

ecl
my_record := RECORD
+    INTEGER4 id;
+    STRING firstname{MAXLENGTH(40)};
+    STRING lastname{MAXLENGTH(50)};
+END;
+
+/**
+  * Returns a dataset of people to treat with caution matching a particular lastname.  The
+  * names are maintained in a global database of undesirables.
+  *
+  * @param  search_lastname    A lastname used as a filter
+  * @return                    The list of people
+  * @see                       NoFlyList
+  * @see                       MorePeopleToAvoid
+  */
+
+EXPORT DodgyCharacters(STRING search_lastname) := FUNCTION
+    raw_ds := DATASET(my_record, 'undesirables', THOR);
+    RETURN raw_ds(last_name = search_lastname);
+END;

Some additional rules for attributes in the library:

  • Services should be SHARED and EXPORTed via intermediate attributes
  • All attributes must have at least one matching test. If you're not on the test list you're not coming in.
`,7)]))}const c=i(l,[["render",t]]);export{o as __pageData,c as default}; diff --git a/assets/index.md.C4lg4w25.js b/assets/index.md.C4lg4w25.js new file mode 100644 index 00000000000..4c2e4c6fe6c --- /dev/null +++ b/assets/index.md.C4lg4w25.js @@ -0,0 +1 @@ +import{_ as t,c as e,o as s}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"HPCC-Platform","text":"Developers Hub","tagline":"Notes and documentation for developers of the HPCC-Platform","image":{"light":{"src":"/devdoc/hpccsystems.png","alt":"HPCC Systems","link":"https://hpccsystems.com"},"dark":{"src":"/devdoc/hpccsystemsdark.png","alt":"HPCC Systems","link":"https://hpccsystems.com"}},"actions":[{"theme":"brand","text":"Get Started","link":"/devdoc/README"},{"theme":"alt","text":"View on GitHub","link":"https://github.com/hpcc-systems/HPCC-Platform"}]},"features":[{"title":"Clone and Build","details":"Building the HPCC Platform from source code, deploying to a test environment and submitting pull requests.","link":"/devdoc/Development"},{"title":"Issues","details":"Report and track issues relating to the HPCC Platform.","link":"https://track.hpccsystems.com"},{"title":"Discussions","details":"Discuss the HPCC Platform with other platform developers.","link":"https://github.com/hpcc-systems/HPCC-Platform/discussions"},{"title":"HPCC Systems Homepage","details":"Not a HPCC-Platform developer? Please visit the HPCC Systems homepage for end user information and support.","link":"https://hpccsystems.com"}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":1731340314000}'),o={name:"index.md"};function a(i,n,r,c,l,m){return s(),e("div")}const h=t(o,[["render",a]]);export{p as __pageData,h as default}; diff --git a/assets/index.md.C4lg4w25.lean.js b/assets/index.md.C4lg4w25.lean.js new file mode 100644 index 00000000000..4c2e4c6fe6c --- /dev/null +++ b/assets/index.md.C4lg4w25.lean.js @@ -0,0 +1 @@ +import{_ as t,c as e,o as s}from"./chunks/framework.DkhCEVKm.js";const p=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"HPCC-Platform","text":"Developers Hub","tagline":"Notes and documentation for developers of the HPCC-Platform","image":{"light":{"src":"/devdoc/hpccsystems.png","alt":"HPCC Systems","link":"https://hpccsystems.com"},"dark":{"src":"/devdoc/hpccsystemsdark.png","alt":"HPCC Systems","link":"https://hpccsystems.com"}},"actions":[{"theme":"brand","text":"Get Started","link":"/devdoc/README"},{"theme":"alt","text":"View on GitHub","link":"https://github.com/hpcc-systems/HPCC-Platform"}]},"features":[{"title":"Clone and Build","details":"Building the HPCC Platform from source code, deploying to a test environment and submitting pull requests.","link":"/devdoc/Development"},{"title":"Issues","details":"Report and track issues relating to the HPCC Platform.","link":"https://track.hpccsystems.com"},{"title":"Discussions","details":"Discuss the HPCC Platform with other platform developers.","link":"https://github.com/hpcc-systems/HPCC-Platform/discussions"},{"title":"HPCC Systems Homepage","details":"Not a HPCC-Platform developer? Please visit the HPCC Systems homepage for end user information and support.","link":"https://hpccsystems.com"}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":1731340314000}'),o={name:"index.md"};function a(i,n,r,c,l,m){return s(),e("div")}const h=t(o,[["render",a]]);export{p as __pageData,h as default}; diff --git a/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 b/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 new file mode 100644 index 00000000000..b6b603d5969 Binary files /dev/null and b/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 differ diff --git a/assets/inter-italic-cyrillic.By2_1cv3.woff2 b/assets/inter-italic-cyrillic.By2_1cv3.woff2 new file mode 100644 index 00000000000..def40a4f658 Binary files /dev/null and b/assets/inter-italic-cyrillic.By2_1cv3.woff2 differ diff --git a/assets/inter-italic-greek-ext.1u6EdAuj.woff2 b/assets/inter-italic-greek-ext.1u6EdAuj.woff2 new file mode 100644 index 00000000000..e070c3d3097 Binary files /dev/null and b/assets/inter-italic-greek-ext.1u6EdAuj.woff2 differ diff --git a/assets/inter-italic-greek.DJ8dCoTZ.woff2 b/assets/inter-italic-greek.DJ8dCoTZ.woff2 new file mode 100644 index 00000000000..a3c16ca40b2 Binary files /dev/null and b/assets/inter-italic-greek.DJ8dCoTZ.woff2 differ diff --git a/assets/inter-italic-latin-ext.CN1xVJS-.woff2 b/assets/inter-italic-latin-ext.CN1xVJS-.woff2 new file mode 100644 index 00000000000..2210a899eda Binary files /dev/null and b/assets/inter-italic-latin-ext.CN1xVJS-.woff2 differ diff --git a/assets/inter-italic-latin.C2AdPX0b.woff2 b/assets/inter-italic-latin.C2AdPX0b.woff2 new file mode 100644 index 00000000000..790d62dc7ba Binary files /dev/null and b/assets/inter-italic-latin.C2AdPX0b.woff2 differ diff --git a/assets/inter-italic-vietnamese.BSbpV94h.woff2 b/assets/inter-italic-vietnamese.BSbpV94h.woff2 new file mode 100644 index 00000000000..1eec0775a64 Binary files /dev/null and b/assets/inter-italic-vietnamese.BSbpV94h.woff2 differ diff --git a/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 b/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 new file mode 100644 index 00000000000..2cfe61536e3 Binary files /dev/null and b/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 differ diff --git a/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 b/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 new file mode 100644 index 00000000000..e3886dd141e Binary files /dev/null and b/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 differ diff --git a/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 b/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 new file mode 100644 index 00000000000..36d67487dcf Binary files /dev/null and b/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 differ diff --git a/assets/inter-roman-greek.BBVDIX6e.woff2 b/assets/inter-roman-greek.BBVDIX6e.woff2 new file mode 100644 index 00000000000..2bed1e85e8b Binary files /dev/null and b/assets/inter-roman-greek.BBVDIX6e.woff2 differ diff --git a/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 b/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 new file mode 100644 index 00000000000..9a8d1e2b5ef Binary files /dev/null and b/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 differ diff --git a/assets/inter-roman-latin.Di8DUHzh.woff2 b/assets/inter-roman-latin.Di8DUHzh.woff2 new file mode 100644 index 00000000000..07d3c53aef1 Binary files /dev/null and b/assets/inter-roman-latin.Di8DUHzh.woff2 differ diff --git a/assets/inter-roman-vietnamese.BjW4sHH5.woff2 b/assets/inter-roman-vietnamese.BjW4sHH5.woff2 new file mode 100644 index 00000000000..57bdc22ae88 Binary files /dev/null and b/assets/inter-roman-vietnamese.BjW4sHH5.woff2 differ diff --git a/assets/repository-tag-tab.DSfut5r1.png b/assets/repository-tag-tab.DSfut5r1.png new file mode 100644 index 00000000000..16bdb37acbc Binary files /dev/null and b/assets/repository-tag-tab.DSfut5r1.png differ diff --git a/assets/style.D_DvoJT5.css b/assets/style.D_DvoJT5.css new file mode 100644 index 00000000000..7ad0f490b93 --- /dev/null +++ b/assets/style.D_DvoJT5.css @@ -0,0 +1 @@ +@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-roman-cyrillic.C5lxZ8CY.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-roman-greek-ext.CqjqNYQ-.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-roman-greek.BBVDIX6e.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-roman-vietnamese.BjW4sHH5.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-roman-latin-ext.4ZJIpNVo.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-roman-latin.Di8DUHzh.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-italic-cyrillic-ext.r48I6akx.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-italic-cyrillic.By2_1cv3.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-italic-greek-ext.1u6EdAuj.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-italic-greek.DJ8dCoTZ.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-italic-vietnamese.BSbpV94h.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-italic-latin-ext.CN1xVJS-.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/HPCC-Platform/assets/inter-italic-latin.C2AdPX0b.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Punctuation SC;font-weight:400;src:local("PingFang SC Regular"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:500;src:local("PingFang SC Medium"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:600;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:700;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}:root{--vp-c-white: #ffffff;--vp-c-black: #000000;--vp-c-neutral: var(--vp-c-black);--vp-c-neutral-inverse: var(--vp-c-white)}.dark{--vp-c-neutral: var(--vp-c-white);--vp-c-neutral-inverse: var(--vp-c-black)}:root{--vp-c-gray-1: #dddde3;--vp-c-gray-2: #e4e4e9;--vp-c-gray-3: #ebebef;--vp-c-gray-soft: rgba(142, 150, 170, .14);--vp-c-indigo-1: #3451b2;--vp-c-indigo-2: #3a5ccc;--vp-c-indigo-3: #5672cd;--vp-c-indigo-soft: rgba(100, 108, 255, .14);--vp-c-purple-1: #6f42c1;--vp-c-purple-2: #7e4cc9;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .14);--vp-c-green-1: #18794e;--vp-c-green-2: #299764;--vp-c-green-3: #30a46c;--vp-c-green-soft: rgba(16, 185, 129, .14);--vp-c-yellow-1: #915930;--vp-c-yellow-2: #946300;--vp-c-yellow-3: #9f6a00;--vp-c-yellow-soft: rgba(234, 179, 8, .14);--vp-c-red-1: #b8272c;--vp-c-red-2: #d5393e;--vp-c-red-3: #e0575b;--vp-c-red-soft: rgba(244, 63, 94, .14);--vp-c-sponsor: #db2777}.dark{--vp-c-gray-1: #515c67;--vp-c-gray-2: #414853;--vp-c-gray-3: #32363f;--vp-c-gray-soft: rgba(101, 117, 133, .16);--vp-c-indigo-1: #a8b1ff;--vp-c-indigo-2: #5c73e7;--vp-c-indigo-3: #3e63dd;--vp-c-indigo-soft: rgba(100, 108, 255, .16);--vp-c-purple-1: #c8abfa;--vp-c-purple-2: #a879e6;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .16);--vp-c-green-1: #3dd68c;--vp-c-green-2: #30a46c;--vp-c-green-3: #298459;--vp-c-green-soft: rgba(16, 185, 129, .16);--vp-c-yellow-1: #f9b44e;--vp-c-yellow-2: #da8b17;--vp-c-yellow-3: #a46a0a;--vp-c-yellow-soft: rgba(234, 179, 8, .16);--vp-c-red-1: #f66f81;--vp-c-red-2: #f14158;--vp-c-red-3: #b62a3c;--vp-c-red-soft: rgba(244, 63, 94, .16)}:root{--vp-c-bg: #ffffff;--vp-c-bg-alt: #f6f6f7;--vp-c-bg-elv: #ffffff;--vp-c-bg-soft: #f6f6f7}.dark{--vp-c-bg: #1b1b1f;--vp-c-bg-alt: #161618;--vp-c-bg-elv: #202127;--vp-c-bg-soft: #202127}:root{--vp-c-border: #c2c2c4;--vp-c-divider: #e2e2e3;--vp-c-gutter: #e2e2e3}.dark{--vp-c-border: #3c3f44;--vp-c-divider: #2e2e32;--vp-c-gutter: #000000}:root{--vp-c-text-1: rgba(60, 60, 67);--vp-c-text-2: rgba(60, 60, 67, .78);--vp-c-text-3: rgba(60, 60, 67, .56)}.dark{--vp-c-text-1: rgba(255, 255, 245, .86);--vp-c-text-2: rgba(235, 235, 245, .6);--vp-c-text-3: rgba(235, 235, 245, .38)}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: var(--vp-c-indigo-1);--vp-c-brand-2: var(--vp-c-indigo-2);--vp-c-brand-3: var(--vp-c-indigo-3);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-brand: var(--vp-c-brand-1);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-note-1: var(--vp-c-brand-1);--vp-c-note-2: var(--vp-c-brand-2);--vp-c-note-3: var(--vp-c-brand-3);--vp-c-note-soft: var(--vp-c-brand-soft);--vp-c-success-1: var(--vp-c-green-1);--vp-c-success-2: var(--vp-c-green-2);--vp-c-success-3: var(--vp-c-green-3);--vp-c-success-soft: var(--vp-c-green-soft);--vp-c-important-1: var(--vp-c-purple-1);--vp-c-important-2: var(--vp-c-purple-2);--vp-c-important-3: var(--vp-c-purple-3);--vp-c-important-soft: var(--vp-c-purple-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft);--vp-c-caution-1: var(--vp-c-red-1);--vp-c-caution-2: var(--vp-c-red-2);--vp-c-caution-3: var(--vp-c-red-3);--vp-c-caution-soft: var(--vp-c-red-soft)}:root{--vp-font-family-base: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--vp-font-family-mono: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;font-optical-sizing:auto}:root:where(:lang(zh)){--vp-font-family-base: "Punctuation SC", "Inter", ui-sans-serif, system-ui, "PingFang SC", "Noto Sans CJK SC", "Noto Sans SC", "Heiti SC", "Microsoft YaHei", "DengXian", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}:root{--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16)}:root{--vp-z-index-footer: 10;--vp-z-index-local-nav: 20;--vp-z-index-nav: 30;--vp-z-index-layout-top: 40;--vp-z-index-backdrop: 50;--vp-z-index-sidebar: 60}@media (min-width: 960px){:root{--vp-z-index-sidebar: 25}}:root{--vp-layout-max-width: 1440px}:root{--vp-header-anchor-symbol: "#"}:root{--vp-code-line-height: 1.7;--vp-code-font-size: .875em;--vp-code-color: var(--vp-c-brand-1);--vp-code-link-color: var(--vp-c-brand-1);--vp-code-link-hover-color: var(--vp-c-brand-2);--vp-code-bg: var(--vp-c-default-soft);--vp-code-block-color: var(--vp-c-text-2);--vp-code-block-bg: var(--vp-c-bg-alt);--vp-code-block-divider-color: var(--vp-c-gutter);--vp-code-lang-color: var(--vp-c-text-3);--vp-code-line-highlight-color: var(--vp-c-default-soft);--vp-code-line-number-color: var(--vp-c-text-3);--vp-code-line-diff-add-color: var(--vp-c-success-soft);--vp-code-line-diff-add-symbol-color: var(--vp-c-success-1);--vp-code-line-diff-remove-color: var(--vp-c-danger-soft);--vp-code-line-diff-remove-symbol-color: var(--vp-c-danger-1);--vp-code-line-warning-color: var(--vp-c-warning-soft);--vp-code-line-error-color: var(--vp-c-danger-soft);--vp-code-copy-code-border-color: var(--vp-c-divider);--vp-code-copy-code-bg: var(--vp-c-bg-soft);--vp-code-copy-code-hover-border-color: var(--vp-c-divider);--vp-code-copy-code-hover-bg: var(--vp-c-bg);--vp-code-copy-code-active-text: var(--vp-c-text-2);--vp-code-copy-copied-text-content: "Copied";--vp-code-tab-divider: var(--vp-code-block-divider-color);--vp-code-tab-text-color: var(--vp-c-text-2);--vp-code-tab-bg: var(--vp-code-block-bg);--vp-code-tab-hover-text-color: var(--vp-c-text-1);--vp-code-tab-active-text-color: var(--vp-c-text-1);--vp-code-tab-active-bar-color: var(--vp-c-brand-1)}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1);--vp-button-alt-border: transparent;--vp-button-alt-text: var(--vp-c-text-1);--vp-button-alt-bg: var(--vp-c-default-3);--vp-button-alt-hover-border: transparent;--vp-button-alt-hover-text: var(--vp-c-text-1);--vp-button-alt-hover-bg: var(--vp-c-default-2);--vp-button-alt-active-border: transparent;--vp-button-alt-active-text: var(--vp-c-text-1);--vp-button-alt-active-bg: var(--vp-c-default-1);--vp-button-sponsor-border: var(--vp-c-text-2);--vp-button-sponsor-text: var(--vp-c-text-2);--vp-button-sponsor-bg: transparent;--vp-button-sponsor-hover-border: var(--vp-c-sponsor);--vp-button-sponsor-hover-text: var(--vp-c-sponsor);--vp-button-sponsor-hover-bg: transparent;--vp-button-sponsor-active-border: var(--vp-c-sponsor);--vp-button-sponsor-active-text: var(--vp-c-sponsor);--vp-button-sponsor-active-bg: transparent}:root{--vp-custom-block-font-size: 14px;--vp-custom-block-code-font-size: 13px;--vp-custom-block-info-border: transparent;--vp-custom-block-info-text: var(--vp-c-text-1);--vp-custom-block-info-bg: var(--vp-c-default-soft);--vp-custom-block-info-code-bg: var(--vp-c-default-soft);--vp-custom-block-note-border: transparent;--vp-custom-block-note-text: var(--vp-c-text-1);--vp-custom-block-note-bg: var(--vp-c-default-soft);--vp-custom-block-note-code-bg: var(--vp-c-default-soft);--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-tip-soft);--vp-custom-block-tip-code-bg: var(--vp-c-tip-soft);--vp-custom-block-important-border: transparent;--vp-custom-block-important-text: var(--vp-c-text-1);--vp-custom-block-important-bg: var(--vp-c-important-soft);--vp-custom-block-important-code-bg: var(--vp-c-important-soft);--vp-custom-block-warning-border: transparent;--vp-custom-block-warning-text: var(--vp-c-text-1);--vp-custom-block-warning-bg: var(--vp-c-warning-soft);--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);--vp-custom-block-danger-border: transparent;--vp-custom-block-danger-text: var(--vp-c-text-1);--vp-custom-block-danger-bg: var(--vp-c-danger-soft);--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);--vp-custom-block-caution-border: transparent;--vp-custom-block-caution-text: var(--vp-c-text-1);--vp-custom-block-caution-bg: var(--vp-c-caution-soft);--vp-custom-block-caution-code-bg: var(--vp-c-caution-soft);--vp-custom-block-details-border: var(--vp-custom-block-info-border);--vp-custom-block-details-text: var(--vp-custom-block-info-text);--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg)}:root{--vp-input-border-color: var(--vp-c-border);--vp-input-bg-color: var(--vp-c-bg-alt);--vp-input-switch-bg-color: var(--vp-c-default-soft)}:root{--vp-nav-height: 64px;--vp-nav-bg-color: var(--vp-c-bg);--vp-nav-screen-bg-color: var(--vp-c-bg);--vp-nav-logo-height: 24px}.hide-nav{--vp-nav-height: 0px}.hide-nav .VPSidebar{--vp-nav-height: 22px}:root{--vp-local-nav-bg-color: var(--vp-c-bg)}:root{--vp-sidebar-width: 272px;--vp-sidebar-bg-color: var(--vp-c-bg-alt)}:root{--vp-backdrop-bg-color: rgba(0, 0, 0, .6)}:root{--vp-home-hero-name-color: var(--vp-c-brand-1);--vp-home-hero-name-background: transparent;--vp-home-hero-image-background-image: none;--vp-home-hero-image-filter: none}:root{--vp-badge-info-border: transparent;--vp-badge-info-text: var(--vp-c-text-2);--vp-badge-info-bg: var(--vp-c-default-soft);--vp-badge-tip-border: transparent;--vp-badge-tip-text: var(--vp-c-tip-1);--vp-badge-tip-bg: var(--vp-c-tip-soft);--vp-badge-warning-border: transparent;--vp-badge-warning-text: var(--vp-c-warning-1);--vp-badge-warning-bg: var(--vp-c-warning-soft);--vp-badge-danger-border: transparent;--vp-badge-danger-text: var(--vp-c-danger-1);--vp-badge-danger-bg: var(--vp-c-danger-soft)}:root{--vp-carbon-ads-text-color: var(--vp-c-text-1);--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1)}:root{--vp-local-search-bg: var(--vp-c-bg);--vp-local-search-result-bg: var(--vp-c-bg);--vp-local-search-result-border: var(--vp-c-divider);--vp-local-search-result-selected-bg: var(--vp-c-bg);--vp-local-search-result-selected-border: var(--vp-c-brand-1);--vp-local-search-highlight-bg: var(--vp-c-brand-1);--vp-local-search-highlight-text: var(--vp-c-neutral-inverse)}@media (prefers-reduced-motion: reduce){*,:before,:after{animation-delay:-1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important;background-attachment:initial!important;scroll-behavior:auto!important;transition-duration:0s!important;transition-delay:0s!important}}*,:before,:after{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}html.dark{color-scheme:dark}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:24px;font-family:var(--vp-font-family-base);font-size:16px;font-weight:400;color:var(--vp-c-text-1);background-color:var(--vp-c-bg);font-synthesis:style;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:24px;font-size:16px;font-weight:400}p{margin:0}strong,b{font-weight:600}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}a{color:inherit;text-decoration:inherit}ol,ul{list-style:none;margin:0;padding:0}blockquote{margin:0}pre,code,kbd,samp{font-family:var(--vp-font-family-mono)}img,svg,video,canvas,audio,iframe,embed,object{display:block}figure{margin:0}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{border:0;padding:0;line-height:inherit;color:inherit}button{padding:0;font-family:inherit;background-color:transparent;background-image:none}button:enabled,[role=button]:enabled{cursor:pointer}button:focus,button:focus-visible{outline:1px dotted;outline:4px auto -webkit-focus-ring-color}button:focus:not(:focus-visible){outline:none!important}input:focus,textarea:focus,select:focus{outline:none}table{border-collapse:collapse}input{background-color:transparent}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--vp-c-text-3)}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--vp-c-text-3)}input::placeholder,textarea::placeholder{color:var(--vp-c-text-3)}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}textarea{resize:vertical}select{-webkit-appearance:none}fieldset{margin:0;padding:0}h1,h2,h3,h4,h5,h6,li,p{overflow-wrap:break-word}vite-error-overlay{z-index:9999}mjx-container{overflow-x:auto}mjx-container>svg{display:inline-block;margin:auto}[class^=vpi-],[class*=" vpi-"],.vp-icon{width:1em;height:1em}[class^=vpi-].bg,[class*=" vpi-"].bg,.vp-icon.bg{background-size:100% 100%;background-color:transparent}[class^=vpi-]:not(.bg),[class*=" vpi-"]:not(.bg),.vp-icon:not(.bg){-webkit-mask:var(--icon) no-repeat;mask:var(--icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit}.vpi-align-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M21 6H3M15 12H3M17 18H3'/%3E%3C/svg%3E")}.vpi-arrow-right,.vpi-arrow-down,.vpi-arrow-left,.vpi-arrow-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5l7 7-7 7'/%3E%3C/svg%3E")}.vpi-chevron-right,.vpi-chevron-down,.vpi-chevron-left,.vpi-chevron-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E")}.vpi-chevron-down,.vpi-arrow-down{transform:rotate(90deg)}.vpi-chevron-left,.vpi-arrow-left{transform:rotate(180deg)}.vpi-chevron-up,.vpi-arrow-up{transform:rotate(-90deg)}.vpi-square-pen{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/%3E%3Cpath d='M18.375 2.625a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4Z'/%3E%3C/svg%3E")}.vpi-plus{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5v14'/%3E%3C/svg%3E")}.vpi-sun{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41'/%3E%3C/svg%3E")}.vpi-moon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z'/%3E%3C/svg%3E")}.vpi-more-horizontal{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3Ccircle cx='19' cy='12' r='1'/%3E%3Ccircle cx='5' cy='12' r='1'/%3E%3C/svg%3E")}.vpi-languages{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m5 8 6 6M4 14l6-6 2-3M2 5h12M7 2h1M22 22l-5-10-5 10M14 18h6'/%3E%3C/svg%3E")}.vpi-heart{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E")}.vpi-search{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E")}.vpi-layout-list{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7M14 9h7M14 15h7M14 20h7'/%3E%3C/svg%3E")}.vpi-delete{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H9l-7 7 7 7h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2ZM18 9l-6 6M12 9l6 6'/%3E%3C/svg%3E")}.vpi-corner-down-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 10-5 5 5 5'/%3E%3Cpath d='M20 4v7a4 4 0 0 1-4 4H4'/%3E%3C/svg%3E")}:root{--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E");--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E")}.vpi-social-discord{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z'/%3E%3C/svg%3E")}.vpi-social-facebook{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z'/%3E%3C/svg%3E")}.vpi-social-github{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")}.vpi-social-instagram{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.03.084c-1.277.06-2.149.264-2.91.563a5.874 5.874 0 0 0-2.124 1.388 5.878 5.878 0 0 0-1.38 2.127C.321 4.926.12 5.8.064 7.076.008 8.354-.005 8.764.001 12.023c.007 3.259.021 3.667.083 4.947.061 1.277.264 2.149.563 2.911.308.789.72 1.457 1.388 2.123a5.872 5.872 0 0 0 2.129 1.38c.763.295 1.636.496 2.913.552 1.278.056 1.689.069 4.947.063 3.257-.007 3.668-.021 4.947-.082 1.28-.06 2.147-.265 2.91-.563a5.881 5.881 0 0 0 2.123-1.388 5.881 5.881 0 0 0 1.38-2.129c.295-.763.496-1.636.551-2.912.056-1.28.07-1.69.063-4.948-.006-3.258-.02-3.667-.081-4.947-.06-1.28-.264-2.148-.564-2.911a5.892 5.892 0 0 0-1.387-2.123 5.857 5.857 0 0 0-2.128-1.38C19.074.322 18.202.12 16.924.066 15.647.009 15.236-.006 11.977 0 8.718.008 8.31.021 7.03.084m.14 21.693c-1.17-.05-1.805-.245-2.228-.408a3.736 3.736 0 0 1-1.382-.895 3.695 3.695 0 0 1-.9-1.378c-.165-.423-.363-1.058-.417-2.228-.06-1.264-.072-1.644-.08-4.848-.006-3.204.006-3.583.061-4.848.05-1.169.246-1.805.408-2.228.216-.561.477-.96.895-1.382a3.705 3.705 0 0 1 1.379-.9c.423-.165 1.057-.361 2.227-.417 1.265-.06 1.644-.072 4.848-.08 3.203-.006 3.583.006 4.85.062 1.168.05 1.804.244 2.227.408.56.216.96.475 1.382.895.421.42.681.817.9 1.378.165.422.362 1.056.417 2.227.06 1.265.074 1.645.08 4.848.005 3.203-.006 3.583-.061 4.848-.051 1.17-.245 1.805-.408 2.23-.216.56-.477.96-.896 1.38a3.705 3.705 0 0 1-1.378.9c-.422.165-1.058.362-2.226.418-1.266.06-1.645.072-4.85.079-3.204.007-3.582-.006-4.848-.06m9.783-16.192a1.44 1.44 0 1 0 1.437-1.442 1.44 1.44 0 0 0-1.437 1.442M5.839 12.012a6.161 6.161 0 1 0 12.323-.024 6.162 6.162 0 0 0-12.323.024M8 12.008A4 4 0 1 1 12.008 16 4 4 0 0 1 8 12.008'/%3E%3C/svg%3E")}.vpi-social-linkedin{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z'/%3E%3C/svg%3E")}.vpi-social-mastodon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z'/%3E%3C/svg%3E")}.vpi-social-npm{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z'/%3E%3C/svg%3E")}.vpi-social-slack{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.122 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.268 0a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zm-2.523 10.122a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zm0-1.268a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z'/%3E%3C/svg%3E")}.vpi-social-twitter,.vpi-social-x{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z'/%3E%3C/svg%3E")}.vpi-social-youtube{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z'/%3E%3C/svg%3E")}.visually-hidden{position:absolute;width:1px;height:1px;white-space:nowrap;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.custom-block{border:1px solid transparent;border-radius:8px;padding:16px 16px 8px;line-height:24px;font-size:var(--vp-custom-block-font-size);color:var(--vp-c-text-2)}.custom-block.info{border-color:var(--vp-custom-block-info-border);color:var(--vp-custom-block-info-text);background-color:var(--vp-custom-block-info-bg)}.custom-block.info a,.custom-block.info code{color:var(--vp-c-brand-1)}.custom-block.info a:hover,.custom-block.info a:hover>code{color:var(--vp-c-brand-2)}.custom-block.info code{background-color:var(--vp-custom-block-info-code-bg)}.custom-block.note{border-color:var(--vp-custom-block-note-border);color:var(--vp-custom-block-note-text);background-color:var(--vp-custom-block-note-bg)}.custom-block.note a,.custom-block.note code{color:var(--vp-c-brand-1)}.custom-block.note a:hover,.custom-block.note a:hover>code{color:var(--vp-c-brand-2)}.custom-block.note code{background-color:var(--vp-custom-block-note-code-bg)}.custom-block.tip{border-color:var(--vp-custom-block-tip-border);color:var(--vp-custom-block-tip-text);background-color:var(--vp-custom-block-tip-bg)}.custom-block.tip a,.custom-block.tip code{color:var(--vp-c-tip-1)}.custom-block.tip a:hover,.custom-block.tip a:hover>code{color:var(--vp-c-tip-2)}.custom-block.tip code{background-color:var(--vp-custom-block-tip-code-bg)}.custom-block.important{border-color:var(--vp-custom-block-important-border);color:var(--vp-custom-block-important-text);background-color:var(--vp-custom-block-important-bg)}.custom-block.important a,.custom-block.important code{color:var(--vp-c-important-1)}.custom-block.important a:hover,.custom-block.important a:hover>code{color:var(--vp-c-important-2)}.custom-block.important code{background-color:var(--vp-custom-block-important-code-bg)}.custom-block.warning{border-color:var(--vp-custom-block-warning-border);color:var(--vp-custom-block-warning-text);background-color:var(--vp-custom-block-warning-bg)}.custom-block.warning a,.custom-block.warning code{color:var(--vp-c-warning-1)}.custom-block.warning a:hover,.custom-block.warning a:hover>code{color:var(--vp-c-warning-2)}.custom-block.warning code{background-color:var(--vp-custom-block-warning-code-bg)}.custom-block.danger{border-color:var(--vp-custom-block-danger-border);color:var(--vp-custom-block-danger-text);background-color:var(--vp-custom-block-danger-bg)}.custom-block.danger a,.custom-block.danger code{color:var(--vp-c-danger-1)}.custom-block.danger a:hover,.custom-block.danger a:hover>code{color:var(--vp-c-danger-2)}.custom-block.danger code{background-color:var(--vp-custom-block-danger-code-bg)}.custom-block.caution{border-color:var(--vp-custom-block-caution-border);color:var(--vp-custom-block-caution-text);background-color:var(--vp-custom-block-caution-bg)}.custom-block.caution a,.custom-block.caution code{color:var(--vp-c-caution-1)}.custom-block.caution a:hover,.custom-block.caution a:hover>code{color:var(--vp-c-caution-2)}.custom-block.caution code{background-color:var(--vp-custom-block-caution-code-bg)}.custom-block.details{border-color:var(--vp-custom-block-details-border);color:var(--vp-custom-block-details-text);background-color:var(--vp-custom-block-details-bg)}.custom-block.details a{color:var(--vp-c-brand-1)}.custom-block.details a:hover,.custom-block.details a:hover>code{color:var(--vp-c-brand-2)}.custom-block.details code{background-color:var(--vp-custom-block-details-code-bg)}.custom-block-title{font-weight:600}.custom-block p+p{margin:8px 0}.custom-block.details summary{margin:0 0 8px;font-weight:700;cursor:pointer;-webkit-user-select:none;user-select:none}.custom-block.details summary+p{margin:8px 0}.custom-block a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;transition:opacity .25s}.custom-block a:hover{opacity:.75}.custom-block code{font-size:var(--vp-custom-block-code-font-size)}.custom-block.custom-block th,.custom-block.custom-block blockquote>p{font-size:var(--vp-custom-block-font-size);color:inherit}.dark .vp-code span{color:var(--shiki-dark, inherit)}html:not(.dark) .vp-code span{color:var(--shiki-light, inherit)}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;background-color:var(--vp-code-tab-bg);overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider)}@media (min-width: 640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-code-tab-text-color);white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:var(--vp-code-tab-hover-text-color)}.vp-code-group input:checked+label{color:var(--vp-code-tab-active-text-color)}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}.vp-doc h1,.vp-doc h2,.vp-doc h3,.vp-doc h4,.vp-doc h5,.vp-doc h6{position:relative;font-weight:600;outline:none}.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:28px}.vp-doc h2{margin:48px 0 16px;border-top:1px solid var(--vp-c-divider);padding-top:24px;letter-spacing:-.02em;line-height:32px;font-size:24px}.vp-doc h3{margin:32px 0 0;letter-spacing:-.01em;line-height:28px;font-size:20px}.vp-doc h4{margin:24px 0 0;letter-spacing:-.01em;line-height:24px;font-size:18px}.vp-doc .header-anchor{position:absolute;top:0;left:0;margin-left:-.87em;font-weight:500;-webkit-user-select:none;user-select:none;opacity:0;text-decoration:none;transition:color .25s,opacity .25s}.vp-doc .header-anchor:before{content:var(--vp-header-anchor-symbol)}.vp-doc h1:hover .header-anchor,.vp-doc h1 .header-anchor:focus,.vp-doc h2:hover .header-anchor,.vp-doc h2 .header-anchor:focus,.vp-doc h3:hover .header-anchor,.vp-doc h3 .header-anchor:focus,.vp-doc h4:hover .header-anchor,.vp-doc h4 .header-anchor:focus,.vp-doc h5:hover .header-anchor,.vp-doc h5 .header-anchor:focus,.vp-doc h6:hover .header-anchor,.vp-doc h6 .header-anchor:focus{opacity:1}@media (min-width: 768px){.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:32px}}.vp-doc h2 .header-anchor{top:24px}.vp-doc p,.vp-doc summary{margin:16px 0}.vp-doc p{line-height:28px}.vp-doc blockquote{margin:16px 0;border-left:2px solid var(--vp-c-divider);padding-left:16px;transition:border-color .5s;color:var(--vp-c-text-2)}.vp-doc blockquote>p{margin:0;font-size:16px;transition:color .5s}.vp-doc a{font-weight:500;color:var(--vp-c-brand-1);text-decoration:underline;text-underline-offset:2px;transition:color .25s,opacity .25s}.vp-doc a:hover{color:var(--vp-c-brand-2)}.vp-doc strong{font-weight:600}.vp-doc ul,.vp-doc ol{padding-left:1.25rem;margin:16px 0}.vp-doc ul{list-style:disc}.vp-doc ol{list-style:decimal}.vp-doc li+li{margin-top:8px}.vp-doc li>ol,.vp-doc li>ul{margin:8px 0 0}.vp-doc table{display:block;border-collapse:collapse;margin:20px 0;overflow-x:auto}.vp-doc tr{background-color:var(--vp-c-bg);border-top:1px solid var(--vp-c-divider);transition:background-color .5s}.vp-doc tr:nth-child(2n){background-color:var(--vp-c-bg-soft)}.vp-doc th,.vp-doc td{border:1px solid var(--vp-c-divider);padding:8px 16px}.vp-doc th{text-align:left;font-size:14px;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-doc td{font-size:14px}.vp-doc hr{margin:16px 0;border:none;border-top:1px solid var(--vp-c-divider)}.vp-doc .custom-block{margin:16px 0}.vp-doc .custom-block p{margin:8px 0;line-height:24px}.vp-doc .custom-block p:first-child{margin:0}.vp-doc .custom-block div[class*=language-]{margin:8px 0;border-radius:8px}.vp-doc .custom-block div[class*=language-] code{font-weight:400;background-color:transparent}.vp-doc .custom-block .vp-code-group .tabs{margin:0;border-radius:8px 8px 0 0}.vp-doc :not(pre,h1,h2,h3,h4,h5,h6)>code{font-size:var(--vp-code-font-size);color:var(--vp-code-color)}.vp-doc :not(pre)>code{border-radius:4px;padding:3px 6px;background-color:var(--vp-code-bg);transition:color .25s,background-color .5s}.vp-doc a>code{color:var(--vp-code-link-color)}.vp-doc a:hover>code{color:var(--vp-code-link-hover-color)}.vp-doc h1>code,.vp-doc h2>code,.vp-doc h3>code,.vp-doc h4>code{font-size:.9em}.vp-doc div[class*=language-],.vp-block{position:relative;margin:16px -24px;background-color:var(--vp-code-block-bg);overflow-x:auto;transition:background-color .5s}@media (min-width: 640px){.vp-doc div[class*=language-],.vp-block{border-radius:8px;margin:16px 0}}@media (max-width: 639px){.vp-doc li div[class*=language-]{border-radius:8px 0 0 8px}}.vp-doc div[class*=language-]+div[class*=language-],.vp-doc div[class$=-api]+div[class*=language-],.vp-doc div[class*=language-]+div[class$=-api]>div[class*=language-]{margin-top:-8px}.vp-doc [class*=language-] pre,.vp-doc [class*=language-] code{direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.vp-doc [class*=language-] pre{position:relative;z-index:1;margin:0;padding:20px 0;background:transparent;overflow-x:auto}.vp-doc [class*=language-] code{display:block;padding:0 24px;width:fit-content;min-width:100%;line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-block-color);transition:color .5s}.vp-doc [class*=language-] code .highlighted{background-color:var(--vp-code-line-highlight-color);transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .highlighted.error{background-color:var(--vp-code-line-error-color)}.vp-doc [class*=language-] code .highlighted.warning{background-color:var(--vp-code-line-warning-color)}.vp-doc [class*=language-] code .diff{transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .diff:before{position:absolute;left:10px}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){filter:blur(.095rem);opacity:.4;transition:filter .35s,opacity .35s}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){opacity:.7;transition:filter .35s,opacity .35s}.vp-doc [class*=language-]:hover .has-focused-lines .line:not(.has-focus){filter:blur(0);opacity:1}.vp-doc [class*=language-] code .diff.remove{background-color:var(--vp-code-line-diff-remove-color);opacity:.7}.vp-doc [class*=language-] code .diff.remove:before{content:"-";color:var(--vp-code-line-diff-remove-symbol-color)}.vp-doc [class*=language-] code .diff.add{background-color:var(--vp-code-line-diff-add-color)}.vp-doc [class*=language-] code .diff.add:before{content:"+";color:var(--vp-code-line-diff-add-symbol-color)}.vp-doc div[class*=language-].line-numbers-mode{padding-left:32px}.vp-doc .line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid var(--vp-code-block-divider-color);padding-top:20px;width:32px;text-align:center;font-family:var(--vp-font-family-mono);line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-line-number-color);transition:border-color .5s,color .5s}.vp-doc [class*=language-]>button.copy{direction:ltr;position:absolute;top:12px;right:12px;z-index:3;border:1px solid var(--vp-code-copy-code-border-color);border-radius:4px;width:40px;height:40px;background-color:var(--vp-code-copy-code-bg);opacity:0;cursor:pointer;background-image:var(--vp-icon-copy);background-position:50%;background-size:20px;background-repeat:no-repeat;transition:border-color .25s,background-color .25s,opacity .25s}.vp-doc [class*=language-]:hover>button.copy,.vp-doc [class*=language-]>button.copy:focus{opacity:1}.vp-doc [class*=language-]>button.copy:hover,.vp-doc [class*=language-]>button.copy.copied{border-color:var(--vp-code-copy-code-hover-border-color);background-color:var(--vp-code-copy-code-hover-bg)}.vp-doc [class*=language-]>button.copy.copied,.vp-doc [class*=language-]>button.copy:hover.copied{border-radius:0 4px 4px 0;background-color:var(--vp-code-copy-code-hover-bg);background-image:var(--vp-icon-copied)}.vp-doc [class*=language-]>button.copy.copied:before,.vp-doc [class*=language-]>button.copy:hover.copied:before{position:relative;top:-1px;transform:translate(calc(-100% - 1px));display:flex;justify-content:center;align-items:center;border:1px solid var(--vp-code-copy-code-hover-border-color);border-right:0;border-radius:4px 0 0 4px;padding:0 10px;width:fit-content;height:40px;text-align:center;font-size:12px;font-weight:500;color:var(--vp-code-copy-code-active-text);background-color:var(--vp-code-copy-code-hover-bg);white-space:nowrap;content:var(--vp-code-copy-copied-text-content)}.vp-doc [class*=language-]>span.lang{position:absolute;top:2px;right:8px;z-index:2;font-size:12px;font-weight:500;color:var(--vp-code-lang-color);transition:color .4s,opacity .4s}.vp-doc [class*=language-]:hover>button.copy+span.lang,.vp-doc [class*=language-]>button.copy:focus+span.lang{opacity:0}.vp-doc .VPTeamMembers{margin-top:24px}.vp-doc .VPTeamMembers.small.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}.vp-doc .VPTeamMembers.small.count-2 .container,.vp-doc .VPTeamMembers.small.count-3 .container{max-width:100%!important}.vp-doc .VPTeamMembers.medium.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}:is(.vp-external-link-icon,.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(.no-icon):after{display:inline-block;margin-top:-1px;margin-left:4px;width:11px;height:11px;background:currentColor;color:var(--vp-c-text-3);flex-shrink:0;--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");-webkit-mask-image:var(--icon);mask-image:var(--icon)}.vp-external-link-icon:after{content:""}.external-link-icon-enabled :is(.vp-doc a[href*="://"],.vp-doc a[target=_blank]):after{content:"";color:currentColor}.vp-sponsor{border-radius:16px;overflow:hidden}.vp-sponsor.aside{border-radius:12px}.vp-sponsor-section+.vp-sponsor-section{margin-top:4px}.vp-sponsor-tier{margin:0 0 4px!important;text-align:center;letter-spacing:1px!important;line-height:24px;width:100%;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-sponsor.normal .vp-sponsor-tier{padding:13px 0 11px;font-size:14px}.vp-sponsor.aside .vp-sponsor-tier{padding:9px 0 7px;font-size:12px}.vp-sponsor-grid+.vp-sponsor-tier{margin-top:4px}.vp-sponsor-grid{display:flex;flex-wrap:wrap;gap:4px}.vp-sponsor-grid.xmini .vp-sponsor-grid-link{height:64px}.vp-sponsor-grid.xmini .vp-sponsor-grid-image{max-width:64px;max-height:22px}.vp-sponsor-grid.mini .vp-sponsor-grid-link{height:72px}.vp-sponsor-grid.mini .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.small .vp-sponsor-grid-link{height:96px}.vp-sponsor-grid.small .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.medium .vp-sponsor-grid-link{height:112px}.vp-sponsor-grid.medium .vp-sponsor-grid-image{max-width:120px;max-height:36px}.vp-sponsor-grid.big .vp-sponsor-grid-link{height:184px}.vp-sponsor-grid.big .vp-sponsor-grid-image{max-width:192px;max-height:56px}.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item{width:calc((100% - 4px)/2)}.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item{width:calc((100% - 4px * 2) / 3)}.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item{width:calc((100% - 12px)/4)}.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item{width:calc((100% - 16px)/5)}.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item{width:calc((100% - 4px * 5) / 6)}.vp-sponsor-grid-item{flex-shrink:0;width:100%;background-color:var(--vp-c-bg-soft);transition:background-color .25s}.vp-sponsor-grid-item:hover{background-color:var(--vp-c-default-soft)}.vp-sponsor-grid-item:hover .vp-sponsor-grid-image{filter:grayscale(0) invert(0)}.vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.dark .vp-sponsor-grid-item:hover{background-color:var(--vp-c-white)}.dark .vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.vp-sponsor-grid-link{display:flex}.vp-sponsor-grid-box{display:flex;justify-content:center;align-items:center;width:100%}.vp-sponsor-grid-image{max-width:100%;filter:grayscale(1);transition:filter .25s}.dark .vp-sponsor-grid-image{filter:grayscale(1) invert(1)}.VPBadge{display:inline-block;margin-left:2px;border:1px solid transparent;border-radius:12px;padding:0 10px;line-height:22px;font-size:12px;font-weight:500;transform:translateY(-2px)}.VPBadge.small{padding:0 6px;line-height:18px;font-size:10px;transform:translateY(-8px)}.VPDocFooter .VPBadge{display:none}.vp-doc h1>.VPBadge{margin-top:4px;vertical-align:top}.vp-doc h2>.VPBadge{margin-top:3px;padding:0 8px;vertical-align:top}.vp-doc h3>.VPBadge{vertical-align:middle}.vp-doc h4>.VPBadge,.vp-doc h5>.VPBadge,.vp-doc h6>.VPBadge{vertical-align:middle;line-height:18px}.VPBadge.info{border-color:var(--vp-badge-info-border);color:var(--vp-badge-info-text);background-color:var(--vp-badge-info-bg)}.VPBadge.tip{border-color:var(--vp-badge-tip-border);color:var(--vp-badge-tip-text);background-color:var(--vp-badge-tip-bg)}.VPBadge.warning{border-color:var(--vp-badge-warning-border);color:var(--vp-badge-warning-text);background-color:var(--vp-badge-warning-bg)}.VPBadge.danger{border-color:var(--vp-badge-danger-border);color:var(--vp-badge-danger-text);background-color:var(--vp-badge-danger-bg)}.VPBackdrop[data-v-54a304ca]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:var(--vp-z-index-backdrop);background:var(--vp-backdrop-bg-color);transition:opacity .5s}.VPBackdrop.fade-enter-from[data-v-54a304ca],.VPBackdrop.fade-leave-to[data-v-54a304ca]{opacity:0}.VPBackdrop.fade-leave-active[data-v-54a304ca]{transition-duration:.25s}@media (min-width: 1280px){.VPBackdrop[data-v-54a304ca]{display:none}}.NotFound[data-v-6ff51ddd]{padding:64px 24px 96px;text-align:center}@media (min-width: 768px){.NotFound[data-v-6ff51ddd]{padding:96px 32px 168px}}.code[data-v-6ff51ddd]{line-height:64px;font-size:64px;font-weight:600}.title[data-v-6ff51ddd]{padding-top:12px;letter-spacing:2px;line-height:20px;font-size:20px;font-weight:700}.divider[data-v-6ff51ddd]{margin:24px auto 18px;width:64px;height:1px;background-color:var(--vp-c-divider)}.quote[data-v-6ff51ddd]{margin:0 auto;max-width:256px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.action[data-v-6ff51ddd]{padding-top:20px}.link[data-v-6ff51ddd]{display:inline-block;border:1px solid var(--vp-c-brand-1);border-radius:16px;padding:3px 16px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:border-color .25s,color .25s}.link[data-v-6ff51ddd]:hover{border-color:var(--vp-c-brand-2);color:var(--vp-c-brand-2)}.root[data-v-53c99d69]{position:relative;z-index:1}.nested[data-v-53c99d69]{padding-right:16px;padding-left:16px}.outline-link[data-v-53c99d69]{display:block;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .5s}.outline-link[data-v-53c99d69]:hover,.outline-link.active[data-v-53c99d69]{color:var(--vp-c-text-1);transition:color .25s}.outline-link.nested[data-v-53c99d69]{padding-left:13px}.VPDocAsideOutline[data-v-f610f197]{display:none}.VPDocAsideOutline.has-outline[data-v-f610f197]{display:block}.content[data-v-f610f197]{position:relative;border-left:1px solid var(--vp-c-divider);padding-left:16px;font-size:13px;font-weight:500}.outline-marker[data-v-f610f197]{position:absolute;top:32px;left:-1px;z-index:0;opacity:0;width:2px;border-radius:2px;height:18px;background-color:var(--vp-c-brand-1);transition:top .25s cubic-bezier(0,1,.5,1),background-color .5s,opacity .25s}.outline-title[data-v-f610f197]{line-height:32px;font-size:14px;font-weight:600}.VPDocAside[data-v-cb998dce]{display:flex;flex-direction:column;flex-grow:1}.spacer[data-v-cb998dce]{flex-grow:1}.VPDocAside[data-v-cb998dce] .spacer+.VPDocAsideSponsors,.VPDocAside[data-v-cb998dce] .spacer+.VPDocAsideCarbonAds{margin-top:24px}.VPDocAside[data-v-cb998dce] .VPDocAsideSponsors+.VPDocAsideCarbonAds{margin-top:16px}.VPLastUpdated[data-v-1bb0c8a8]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 640px){.VPLastUpdated[data-v-1bb0c8a8]{line-height:32px;font-size:14px;font-weight:500}}.VPDocFooter[data-v-1bcd8184]{margin-top:64px}.edit-info[data-v-1bcd8184]{padding-bottom:18px}@media (min-width: 640px){.edit-info[data-v-1bcd8184]{display:flex;justify-content:space-between;align-items:center;padding-bottom:14px}}.edit-link-button[data-v-1bcd8184]{display:flex;align-items:center;border:0;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.edit-link-button[data-v-1bcd8184]:hover{color:var(--vp-c-brand-2)}.edit-link-icon[data-v-1bcd8184]{margin-right:8px}.prev-next[data-v-1bcd8184]{border-top:1px solid var(--vp-c-divider);padding-top:24px;display:grid;grid-row-gap:8px}@media (min-width: 640px){.prev-next[data-v-1bcd8184]{grid-template-columns:repeat(2,1fr);grid-column-gap:16px}}.pager-link[data-v-1bcd8184]{display:block;border:1px solid var(--vp-c-divider);border-radius:8px;padding:11px 16px 13px;width:100%;height:100%;transition:border-color .25s}.pager-link[data-v-1bcd8184]:hover{border-color:var(--vp-c-brand-1)}.pager-link.next[data-v-1bcd8184]{margin-left:auto;text-align:right}.desc[data-v-1bcd8184]{display:block;line-height:20px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.title[data-v-1bcd8184]{display:block;line-height:20px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.VPDoc[data-v-e6f2a212]{padding:32px 24px 96px;width:100%}@media (min-width: 768px){.VPDoc[data-v-e6f2a212]{padding:48px 32px 128px}}@media (min-width: 960px){.VPDoc[data-v-e6f2a212]{padding:48px 32px 0}.VPDoc:not(.has-sidebar) .container[data-v-e6f2a212]{display:flex;justify-content:center;max-width:992px}.VPDoc:not(.has-sidebar) .content[data-v-e6f2a212]{max-width:752px}}@media (min-width: 1280px){.VPDoc .container[data-v-e6f2a212]{display:flex;justify-content:center}.VPDoc .aside[data-v-e6f2a212]{display:block}}@media (min-width: 1440px){.VPDoc:not(.has-sidebar) .content[data-v-e6f2a212]{max-width:784px}.VPDoc:not(.has-sidebar) .container[data-v-e6f2a212]{max-width:1104px}}.container[data-v-e6f2a212]{margin:0 auto;width:100%}.aside[data-v-e6f2a212]{position:relative;display:none;order:2;flex-grow:1;padding-left:32px;width:100%;max-width:256px}.left-aside[data-v-e6f2a212]{order:1;padding-left:unset;padding-right:32px}.aside-container[data-v-e6f2a212]{position:fixed;top:0;padding-top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);width:224px;height:100vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none}.aside-container[data-v-e6f2a212]::-webkit-scrollbar{display:none}.aside-curtain[data-v-e6f2a212]{position:fixed;bottom:0;z-index:10;width:224px;height:32px;background:linear-gradient(transparent,var(--vp-c-bg) 70%)}.aside-content[data-v-e6f2a212]{display:flex;flex-direction:column;min-height:calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));padding-bottom:32px}.content[data-v-e6f2a212]{position:relative;margin:0 auto;width:100%}@media (min-width: 960px){.content[data-v-e6f2a212]{padding:0 32px 128px}}@media (min-width: 1280px){.content[data-v-e6f2a212]{order:1;margin:0;min-width:640px}}.content-container[data-v-e6f2a212]{margin:0 auto}.VPDoc.has-aside .content-container[data-v-e6f2a212]{max-width:688px}.VPButton[data-v-93dc4167]{display:inline-block;border:1px solid transparent;text-align:center;font-weight:600;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s}.VPButton[data-v-93dc4167]:active{transition:color .1s,border-color .1s,background-color .1s}.VPButton.medium[data-v-93dc4167]{border-radius:20px;padding:0 20px;line-height:38px;font-size:14px}.VPButton.big[data-v-93dc4167]{border-radius:24px;padding:0 24px;line-height:46px;font-size:16px}.VPButton.brand[data-v-93dc4167]{border-color:var(--vp-button-brand-border);color:var(--vp-button-brand-text);background-color:var(--vp-button-brand-bg)}.VPButton.brand[data-v-93dc4167]:hover{border-color:var(--vp-button-brand-hover-border);color:var(--vp-button-brand-hover-text);background-color:var(--vp-button-brand-hover-bg)}.VPButton.brand[data-v-93dc4167]:active{border-color:var(--vp-button-brand-active-border);color:var(--vp-button-brand-active-text);background-color:var(--vp-button-brand-active-bg)}.VPButton.alt[data-v-93dc4167]{border-color:var(--vp-button-alt-border);color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg)}.VPButton.alt[data-v-93dc4167]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.VPButton.alt[data-v-93dc4167]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.VPButton.sponsor[data-v-93dc4167]{border-color:var(--vp-button-sponsor-border);color:var(--vp-button-sponsor-text);background-color:var(--vp-button-sponsor-bg)}.VPButton.sponsor[data-v-93dc4167]:hover{border-color:var(--vp-button-sponsor-hover-border);color:var(--vp-button-sponsor-hover-text);background-color:var(--vp-button-sponsor-hover-bg)}.VPButton.sponsor[data-v-93dc4167]:active{border-color:var(--vp-button-sponsor-active-border);color:var(--vp-button-sponsor-active-text);background-color:var(--vp-button-sponsor-active-bg)}html:not(.dark) .VPImage.dark[data-v-ab19afbb]{display:none}.dark .VPImage.light[data-v-ab19afbb]{display:none}.VPHero[data-v-b10c5094]{margin-top:calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px}@media (min-width: 640px){.VPHero[data-v-b10c5094]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px}}@media (min-width: 960px){.VPHero[data-v-b10c5094]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px}}.container[data-v-b10c5094]{display:flex;flex-direction:column;margin:0 auto;max-width:1152px}@media (min-width: 960px){.container[data-v-b10c5094]{flex-direction:row}}.main[data-v-b10c5094]{position:relative;z-index:10;order:2;flex-grow:1;flex-shrink:0}.VPHero.has-image .container[data-v-b10c5094]{text-align:center}@media (min-width: 960px){.VPHero.has-image .container[data-v-b10c5094]{text-align:left}}@media (min-width: 960px){.main[data-v-b10c5094]{order:1;width:calc((100% / 3) * 2)}.VPHero.has-image .main[data-v-b10c5094]{max-width:592px}}.name[data-v-b10c5094],.text[data-v-b10c5094]{max-width:392px;letter-spacing:-.4px;line-height:40px;font-size:32px;font-weight:700;white-space:pre-wrap}.VPHero.has-image .name[data-v-b10c5094],.VPHero.has-image .text[data-v-b10c5094]{margin:0 auto}.name[data-v-b10c5094]{color:var(--vp-home-hero-name-color)}.clip[data-v-b10c5094]{background:var(--vp-home-hero-name-background);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:var(--vp-home-hero-name-color)}@media (min-width: 640px){.name[data-v-b10c5094],.text[data-v-b10c5094]{max-width:576px;line-height:56px;font-size:48px}}@media (min-width: 960px){.name[data-v-b10c5094],.text[data-v-b10c5094]{line-height:64px;font-size:56px}.VPHero.has-image .name[data-v-b10c5094],.VPHero.has-image .text[data-v-b10c5094]{margin:0}}.tagline[data-v-b10c5094]{padding-top:8px;max-width:392px;line-height:28px;font-size:18px;font-weight:500;white-space:pre-wrap;color:var(--vp-c-text-2)}.VPHero.has-image .tagline[data-v-b10c5094]{margin:0 auto}@media (min-width: 640px){.tagline[data-v-b10c5094]{padding-top:12px;max-width:576px;line-height:32px;font-size:20px}}@media (min-width: 960px){.tagline[data-v-b10c5094]{line-height:36px;font-size:24px}.VPHero.has-image .tagline[data-v-b10c5094]{margin:0}}.actions[data-v-b10c5094]{display:flex;flex-wrap:wrap;margin:-6px;padding-top:24px}.VPHero.has-image .actions[data-v-b10c5094]{justify-content:center}@media (min-width: 640px){.actions[data-v-b10c5094]{padding-top:32px}}@media (min-width: 960px){.VPHero.has-image .actions[data-v-b10c5094]{justify-content:flex-start}}.action[data-v-b10c5094]{flex-shrink:0;padding:6px}.image[data-v-b10c5094]{order:1;margin:-76px -24px -48px}@media (min-width: 640px){.image[data-v-b10c5094]{margin:-108px -24px -48px}}@media (min-width: 960px){.image[data-v-b10c5094]{flex-grow:1;order:2;margin:0;min-height:100%}}.image-container[data-v-b10c5094]{position:relative;margin:0 auto;width:320px;height:320px}@media (min-width: 640px){.image-container[data-v-b10c5094]{width:392px;height:392px}}@media (min-width: 960px){.image-container[data-v-b10c5094]{display:flex;justify-content:center;align-items:center;width:100%;height:100%;transform:translate(-32px,-32px)}}.image-bg[data-v-b10c5094]{position:absolute;top:50%;left:50%;border-radius:50%;width:192px;height:192px;background-image:var(--vp-home-hero-image-background-image);filter:var(--vp-home-hero-image-filter);transform:translate(-50%,-50%)}@media (min-width: 640px){.image-bg[data-v-b10c5094]{width:256px;height:256px}}@media (min-width: 960px){.image-bg[data-v-b10c5094]{width:320px;height:320px}}[data-v-b10c5094] .image-src{position:absolute;top:50%;left:50%;max-width:192px;max-height:192px;transform:translate(-50%,-50%)}@media (min-width: 640px){[data-v-b10c5094] .image-src{max-width:256px;max-height:256px}}@media (min-width: 960px){[data-v-b10c5094] .image-src{max-width:320px;max-height:320px}}.VPFeature[data-v-bd37d1a2]{display:block;border:1px solid var(--vp-c-bg-soft);border-radius:12px;height:100%;background-color:var(--vp-c-bg-soft);transition:border-color .25s,background-color .25s}.VPFeature.link[data-v-bd37d1a2]:hover{border-color:var(--vp-c-brand-1)}.box[data-v-bd37d1a2]{display:flex;flex-direction:column;padding:24px;height:100%}.box[data-v-bd37d1a2]>.VPImage{margin-bottom:20px}.icon[data-v-bd37d1a2]{display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-radius:6px;background-color:var(--vp-c-default-soft);width:48px;height:48px;font-size:24px;transition:background-color .25s}.title[data-v-bd37d1a2]{line-height:24px;font-size:16px;font-weight:600}.details[data-v-bd37d1a2]{flex-grow:1;padding-top:8px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.link-text[data-v-bd37d1a2]{padding-top:8px}.link-text-value[data-v-bd37d1a2]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.link-text-icon[data-v-bd37d1a2]{margin-left:6px}.VPFeatures[data-v-b1eea84a]{position:relative;padding:0 24px}@media (min-width: 640px){.VPFeatures[data-v-b1eea84a]{padding:0 48px}}@media (min-width: 960px){.VPFeatures[data-v-b1eea84a]{padding:0 64px}}.container[data-v-b1eea84a]{margin:0 auto;max-width:1152px}.items[data-v-b1eea84a]{display:flex;flex-wrap:wrap;margin:-8px}.item[data-v-b1eea84a]{padding:8px;width:100%}@media (min-width: 640px){.item.grid-2[data-v-b1eea84a],.item.grid-4[data-v-b1eea84a],.item.grid-6[data-v-b1eea84a]{width:50%}}@media (min-width: 768px){.item.grid-2[data-v-b1eea84a],.item.grid-4[data-v-b1eea84a]{width:50%}.item.grid-3[data-v-b1eea84a],.item.grid-6[data-v-b1eea84a]{width:calc(100% / 3)}}@media (min-width: 960px){.item.grid-4[data-v-b1eea84a]{width:25%}}.container[data-v-c141a4bd]{margin:auto;width:100%;max-width:1280px;padding:0 24px}@media (min-width: 640px){.container[data-v-c141a4bd]{padding:0 48px}}@media (min-width: 960px){.container[data-v-c141a4bd]{width:100%;padding:0 64px}}.vp-doc[data-v-c141a4bd] .VPHomeSponsors,.vp-doc[data-v-c141a4bd] .VPTeamPage{margin-left:var(--vp-offset, calc(50% - 50vw) );margin-right:var(--vp-offset, calc(50% - 50vw) )}.vp-doc[data-v-c141a4bd] .VPHomeSponsors h2{border-top:none;letter-spacing:normal}.vp-doc[data-v-c141a4bd] .VPHomeSponsors a,.vp-doc[data-v-c141a4bd] .VPTeamPage a{text-decoration:none}.VPHome[data-v-07b1ad08]{margin-bottom:96px}@media (min-width: 768px){.VPHome[data-v-07b1ad08]{margin-bottom:128px}}.VPContent[data-v-9a6c75ad]{flex-grow:1;flex-shrink:0;margin:var(--vp-layout-top-height, 0px) auto 0;width:100%}.VPContent.is-home[data-v-9a6c75ad]{width:100%;max-width:100%}.VPContent.has-sidebar[data-v-9a6c75ad]{margin:0}@media (min-width: 960px){.VPContent[data-v-9a6c75ad]{padding-top:var(--vp-nav-height)}.VPContent.has-sidebar[data-v-9a6c75ad]{margin:var(--vp-layout-top-height, 0px) 0 0;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPContent.has-sidebar[data-v-9a6c75ad]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.VPFooter[data-v-566314d4]{position:relative;z-index:var(--vp-z-index-footer);border-top:1px solid var(--vp-c-gutter);padding:32px 24px;background-color:var(--vp-c-bg)}.VPFooter.has-sidebar[data-v-566314d4]{display:none}.VPFooter[data-v-566314d4] a{text-decoration-line:underline;text-underline-offset:2px;transition:color .25s}.VPFooter[data-v-566314d4] a:hover{color:var(--vp-c-text-1)}@media (min-width: 768px){.VPFooter[data-v-566314d4]{padding:32px}}.container[data-v-566314d4]{margin:0 auto;max-width:var(--vp-layout-max-width);text-align:center}.message[data-v-566314d4],.copyright[data-v-566314d4]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.VPLocalNavOutlineDropdown[data-v-883964e0]{padding:12px 20px 11px}@media (min-width: 960px){.VPLocalNavOutlineDropdown[data-v-883964e0]{padding:12px 36px 11px}}.VPLocalNavOutlineDropdown button[data-v-883964e0]{display:block;font-size:12px;font-weight:500;line-height:24px;color:var(--vp-c-text-2);transition:color .5s;position:relative}.VPLocalNavOutlineDropdown button[data-v-883964e0]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPLocalNavOutlineDropdown button.open[data-v-883964e0]{color:var(--vp-c-text-1)}.icon[data-v-883964e0]{display:inline-block;vertical-align:middle;margin-left:2px;font-size:14px;transform:rotate(0);transition:transform .25s}@media (min-width: 960px){.VPLocalNavOutlineDropdown button[data-v-883964e0]{font-size:14px}.icon[data-v-883964e0]{font-size:16px}}.open>.icon[data-v-883964e0]{transform:rotate(90deg)}.items[data-v-883964e0]{position:absolute;top:40px;right:16px;left:16px;display:grid;gap:1px;border:1px solid var(--vp-c-border);border-radius:8px;background-color:var(--vp-c-gutter);max-height:calc(var(--vp-vh, 100vh) - 86px);overflow:hidden auto;box-shadow:var(--vp-shadow-3)}@media (min-width: 960px){.items[data-v-883964e0]{right:auto;left:calc(var(--vp-sidebar-width) + 32px);width:320px}}.header[data-v-883964e0]{background-color:var(--vp-c-bg-soft)}.top-link[data-v-883964e0]{display:block;padding:0 16px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.outline[data-v-883964e0]{padding:8px 0;background-color:var(--vp-c-bg-soft)}.flyout-enter-active[data-v-883964e0]{transition:all .2s ease-out}.flyout-leave-active[data-v-883964e0]{transition:all .15s ease-in}.flyout-enter-from[data-v-883964e0],.flyout-leave-to[data-v-883964e0]{opacity:0;transform:translateY(-16px)}.VPLocalNav[data-v-2488c25a]{position:sticky;top:0;left:0;z-index:var(--vp-z-index-local-nav);border-bottom:1px solid var(--vp-c-gutter);padding-top:var(--vp-layout-top-height, 0px);width:100%;background-color:var(--vp-local-nav-bg-color)}.VPLocalNav.fixed[data-v-2488c25a]{position:fixed}@media (min-width: 960px){.VPLocalNav[data-v-2488c25a]{top:var(--vp-nav-height)}.VPLocalNav.has-sidebar[data-v-2488c25a]{padding-left:var(--vp-sidebar-width)}.VPLocalNav.empty[data-v-2488c25a]{display:none}}@media (min-width: 1280px){.VPLocalNav[data-v-2488c25a]{display:none}}@media (min-width: 1440px){.VPLocalNav.has-sidebar[data-v-2488c25a]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.container[data-v-2488c25a]{display:flex;justify-content:space-between;align-items:center}.menu[data-v-2488c25a]{display:flex;align-items:center;padding:12px 24px 11px;line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.menu[data-v-2488c25a]:hover{color:var(--vp-c-text-1);transition:color .25s}@media (min-width: 768px){.menu[data-v-2488c25a]{padding:0 32px}}@media (min-width: 960px){.menu[data-v-2488c25a]{display:none}}.menu-icon[data-v-2488c25a]{margin-right:8px;font-size:14px}.VPOutlineDropdown[data-v-2488c25a]{padding:12px 24px 11px}@media (min-width: 768px){.VPOutlineDropdown[data-v-2488c25a]{padding:12px 32px 11px}}.VPSwitch[data-v-b4ccac88]{position:relative;border-radius:11px;display:block;width:40px;height:22px;flex-shrink:0;border:1px solid var(--vp-input-border-color);background-color:var(--vp-input-switch-bg-color);transition:border-color .25s!important}.VPSwitch[data-v-b4ccac88]:hover{border-color:var(--vp-c-brand-1)}.check[data-v-b4ccac88]{position:absolute;top:1px;left:1px;width:18px;height:18px;border-radius:50%;background-color:var(--vp-c-neutral-inverse);box-shadow:var(--vp-shadow-1);transition:transform .25s!important}.icon[data-v-b4ccac88]{position:relative;display:block;width:18px;height:18px;border-radius:50%;overflow:hidden}.icon[data-v-b4ccac88] [class^=vpi-]{position:absolute;top:3px;left:3px;width:12px;height:12px;color:var(--vp-c-text-2)}.dark .icon[data-v-b4ccac88] [class^=vpi-]{color:var(--vp-c-text-1);transition:opacity .25s!important}.sun[data-v-be9742d9]{opacity:1}.moon[data-v-be9742d9],.dark .sun[data-v-be9742d9]{opacity:0}.dark .moon[data-v-be9742d9]{opacity:1}.dark .VPSwitchAppearance[data-v-be9742d9] .check{transform:translate(18px)}.VPNavBarAppearance[data-v-3f90c1a5]{display:none}@media (min-width: 1280px){.VPNavBarAppearance[data-v-3f90c1a5]{display:flex;align-items:center}}.VPMenuGroup+.VPMenuLink[data-v-7eeeb2dc]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.link[data-v-7eeeb2dc]{display:block;border-radius:6px;padding:0 12px;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);white-space:nowrap;transition:background-color .25s,color .25s}.link[data-v-7eeeb2dc]:hover{color:var(--vp-c-brand-1);background-color:var(--vp-c-default-soft)}.link.active[data-v-7eeeb2dc]{color:var(--vp-c-brand-1)}.VPMenuGroup[data-v-a6b0397c]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.VPMenuGroup[data-v-a6b0397c]:first-child{margin-top:0;border-top:0;padding-top:0}.VPMenuGroup+.VPMenuGroup[data-v-a6b0397c]{margin-top:12px;border-top:1px solid var(--vp-c-divider)}.title[data-v-a6b0397c]{padding:0 12px;line-height:32px;font-size:14px;font-weight:600;color:var(--vp-c-text-2);white-space:nowrap;transition:color .25s}.VPMenu[data-v-20ed86d6]{border-radius:12px;padding:12px;min-width:128px;border:1px solid var(--vp-c-divider);background-color:var(--vp-c-bg-elv);box-shadow:var(--vp-shadow-3);transition:background-color .5s;max-height:calc(100vh - var(--vp-nav-height));overflow-y:auto}.VPMenu[data-v-20ed86d6] .group{margin:0 -12px;padding:0 12px 12px}.VPMenu[data-v-20ed86d6] .group+.group{border-top:1px solid var(--vp-c-divider);padding:11px 12px 12px}.VPMenu[data-v-20ed86d6] .group:last-child{padding-bottom:0}.VPMenu[data-v-20ed86d6] .group+.item{border-top:1px solid var(--vp-c-divider);padding:11px 16px 0}.VPMenu[data-v-20ed86d6] .item{padding:0 16px;white-space:nowrap}.VPMenu[data-v-20ed86d6] .label{flex-grow:1;line-height:28px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.VPMenu[data-v-20ed86d6] .action{padding-left:24px}.VPFlyout[data-v-bfe7971f]{position:relative}.VPFlyout[data-v-bfe7971f]:hover{color:var(--vp-c-brand-1);transition:color .25s}.VPFlyout:hover .text[data-v-bfe7971f]{color:var(--vp-c-text-2)}.VPFlyout:hover .icon[data-v-bfe7971f]{fill:var(--vp-c-text-2)}.VPFlyout.active .text[data-v-bfe7971f]{color:var(--vp-c-brand-1)}.VPFlyout.active:hover .text[data-v-bfe7971f]{color:var(--vp-c-brand-2)}.button[aria-expanded=false]+.menu[data-v-bfe7971f]{opacity:0;visibility:hidden;transform:translateY(0)}.VPFlyout:hover .menu[data-v-bfe7971f],.button[aria-expanded=true]+.menu[data-v-bfe7971f]{opacity:1;visibility:visible;transform:translateY(0)}.button[data-v-bfe7971f]{display:flex;align-items:center;padding:0 12px;height:var(--vp-nav-height);color:var(--vp-c-text-1);transition:color .5s}.text[data-v-bfe7971f]{display:flex;align-items:center;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.option-icon[data-v-bfe7971f]{margin-right:0;font-size:16px}.text-icon[data-v-bfe7971f]{margin-left:4px;font-size:14px}.icon[data-v-bfe7971f]{font-size:20px;transition:fill .25s}.menu[data-v-bfe7971f]{position:absolute;top:calc(var(--vp-nav-height) / 2 + 20px);right:0;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s,transform .25s}.VPSocialLink[data-v-358b6670]{display:flex;justify-content:center;align-items:center;width:36px;height:36px;color:var(--vp-c-text-2);transition:color .5s}.VPSocialLink[data-v-358b6670]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPSocialLink[data-v-358b6670]>svg,.VPSocialLink[data-v-358b6670]>[class^=vpi-social-]{width:20px;height:20px;fill:currentColor}.VPSocialLinks[data-v-e71e869c]{display:flex;justify-content:center}.VPNavBarExtra[data-v-f953d92f]{display:none;margin-right:-12px}@media (min-width: 768px){.VPNavBarExtra[data-v-f953d92f]{display:block}}@media (min-width: 1280px){.VPNavBarExtra[data-v-f953d92f]{display:none}}.trans-title[data-v-f953d92f]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.item.appearance[data-v-f953d92f],.item.social-links[data-v-f953d92f]{display:flex;align-items:center;padding:0 12px}.item.appearance[data-v-f953d92f]{min-width:176px}.appearance-action[data-v-f953d92f]{margin-right:-2px}.social-links-list[data-v-f953d92f]{margin:-4px -8px}.VPNavBarHamburger[data-v-6bee1efd]{display:flex;justify-content:center;align-items:center;width:48px;height:var(--vp-nav-height)}@media (min-width: 768px){.VPNavBarHamburger[data-v-6bee1efd]{display:none}}.container[data-v-6bee1efd]{position:relative;width:16px;height:14px;overflow:hidden}.VPNavBarHamburger:hover .top[data-v-6bee1efd]{top:0;left:0;transform:translate(4px)}.VPNavBarHamburger:hover .middle[data-v-6bee1efd]{top:6px;left:0;transform:translate(0)}.VPNavBarHamburger:hover .bottom[data-v-6bee1efd]{top:12px;left:0;transform:translate(8px)}.VPNavBarHamburger.active .top[data-v-6bee1efd]{top:6px;transform:translate(0) rotate(225deg)}.VPNavBarHamburger.active .middle[data-v-6bee1efd]{top:6px;transform:translate(16px)}.VPNavBarHamburger.active .bottom[data-v-6bee1efd]{top:6px;transform:translate(0) rotate(135deg)}.VPNavBarHamburger.active:hover .top[data-v-6bee1efd],.VPNavBarHamburger.active:hover .middle[data-v-6bee1efd],.VPNavBarHamburger.active:hover .bottom[data-v-6bee1efd]{background-color:var(--vp-c-text-2);transition:top .25s,background-color .25s,transform .25s}.top[data-v-6bee1efd],.middle[data-v-6bee1efd],.bottom[data-v-6bee1efd]{position:absolute;width:16px;height:2px;background-color:var(--vp-c-text-1);transition:top .25s,background-color .5s,transform .25s}.top[data-v-6bee1efd]{top:0;left:0;transform:translate(0)}.middle[data-v-6bee1efd]{top:6px;left:0;transform:translate(8px)}.bottom[data-v-6bee1efd]{top:12px;left:0;transform:translate(4px)}.VPNavBarMenuLink[data-v-815115f5]{display:flex;align-items:center;padding:0 12px;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.VPNavBarMenuLink.active[data-v-815115f5],.VPNavBarMenuLink[data-v-815115f5]:hover{color:var(--vp-c-brand-1)}.VPNavBarMenu[data-v-afb2845e]{display:none}@media (min-width: 768px){.VPNavBarMenu[data-v-afb2845e]{display:flex}}/*! @docsearch/css 3.6.2 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 1px 0 rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 1px 1px 0 rgba(3,4,9,.30196078431372547);--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;position:relative;padding:0 0 2px;border:0;top:-1px;width:20px}.DocSearch-Button-Key--pressed{transform:translate3d(0,1px,0);box-shadow:var(--docsearch-key-pressed-shadow)}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:2px;box-shadow:var(--docsearch-key-shadow);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;color:var(--docsearch-muted-color);border:0;width:20px}.DocSearch-VisuallyHiddenForAccessibility{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}[class*=DocSearch]{--docsearch-primary-color: var(--vp-c-brand-1);--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-text-color: var(--vp-c-text-1);--docsearch-muted-color: var(--vp-c-text-2);--docsearch-searchbox-shadow: none;--docsearch-searchbox-background: transparent;--docsearch-searchbox-focus-background: transparent;--docsearch-key-gradient: transparent;--docsearch-key-shadow: none;--docsearch-modal-background: var(--vp-c-bg-soft);--docsearch-footer-background: var(--vp-c-bg)}.dark [class*=DocSearch]{--docsearch-modal-shadow: none;--docsearch-footer-shadow: none;--docsearch-logo-color: var(--vp-c-text-2);--docsearch-hit-background: var(--vp-c-default-soft);--docsearch-hit-color: var(--vp-c-text-2);--docsearch-hit-shadow: none}.DocSearch-Button{display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:48px;height:55px;background:transparent;transition:border-color .25s}.DocSearch-Button:hover{background:transparent}.DocSearch-Button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.DocSearch-Button-Key--pressed{transform:none;box-shadow:none}.DocSearch-Button:focus:not(:focus-visible){outline:none!important}@media (min-width: 768px){.DocSearch-Button{justify-content:flex-start;border:1px solid transparent;border-radius:8px;padding:0 10px 0 12px;width:100%;height:40px;background-color:var(--vp-c-bg-alt)}.DocSearch-Button:hover{border-color:var(--vp-c-brand-1);background:var(--vp-c-bg-alt)}}.DocSearch-Button .DocSearch-Button-Container{display:flex;align-items:center}.DocSearch-Button .DocSearch-Search-Icon{position:relative;width:16px;height:16px;color:var(--vp-c-text-1);fill:currentColor;transition:color .5s}.DocSearch-Button:hover .DocSearch-Search-Icon{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Search-Icon{top:1px;margin-right:8px;width:14px;height:14px;color:var(--vp-c-text-2)}}.DocSearch-Button .DocSearch-Button-Placeholder{display:none;margin-top:2px;padding:0 16px 0 0;font-size:13px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.DocSearch-Button:hover .DocSearch-Button-Placeholder{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Placeholder{display:inline-block}}.DocSearch-Button .DocSearch-Button-Keys{direction:ltr;display:none;min-width:auto}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Keys{display:flex;align-items:center}}.DocSearch-Button .DocSearch-Button-Key{display:block;margin:2px 0 0;border:1px solid var(--vp-c-divider);border-right:none;border-radius:4px 0 0 4px;padding-left:6px;min-width:0;width:auto;height:22px;line-height:22px;font-family:var(--vp-font-family-base);font-size:12px;font-weight:500;transition:color .5s,border-color .5s}.DocSearch-Button .DocSearch-Button-Key+.DocSearch-Button-Key{border-right:1px solid var(--vp-c-divider);border-left:none;border-radius:0 4px 4px 0;padding-left:2px;padding-right:6px}.DocSearch-Button .DocSearch-Button-Key:first-child{font-size:0!important}.DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"Ctrl";font-size:12px;letter-spacing:normal;color:var(--docsearch-muted-color)}.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"⌘"}.DocSearch-Button .DocSearch-Button-Key:first-child>*{display:none}.DocSearch-Search-Icon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke-width='1.6' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='m14.386 14.386 4.088 4.088-4.088-4.088A7.533 7.533 0 1 1 3.733 3.733a7.533 7.533 0 0 1 10.653 10.653z'/%3E%3C/svg%3E")}.VPNavBarSearch{display:flex;align-items:center}@media (min-width: 768px){.VPNavBarSearch{flex-grow:1;padding-left:24px}}@media (min-width: 960px){.VPNavBarSearch{padding-left:32px}}.dark .DocSearch-Footer{border-top:1px solid var(--vp-c-divider)}.DocSearch-Form{border:1px solid var(--vp-c-brand-1);background-color:var(--vp-c-white)}.dark .DocSearch-Form{background-color:var(--vp-c-default-soft)}.DocSearch-Screen-Icon>svg{margin:auto}.VPNavBarSocialLinks[data-v-ef6192dc]{display:none}@media (min-width: 1280px){.VPNavBarSocialLinks[data-v-ef6192dc]{display:flex;align-items:center}}.title[data-v-0ad69264]{display:flex;align-items:center;border-bottom:1px solid transparent;width:100%;height:var(--vp-nav-height);font-size:16px;font-weight:600;color:var(--vp-c-text-1);transition:opacity .25s}@media (min-width: 960px){.title[data-v-0ad69264]{flex-shrink:0}.VPNavBarTitle.has-sidebar .title[data-v-0ad69264]{border-bottom-color:var(--vp-c-divider)}}[data-v-0ad69264] .logo{margin-right:8px;height:var(--vp-nav-logo-height)}.VPNavBarTranslations[data-v-acee064b]{display:none}@media (min-width: 1280px){.VPNavBarTranslations[data-v-acee064b]{display:flex;align-items:center}}.title[data-v-acee064b]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.VPNavBar[data-v-9fd4d1dd]{position:relative;height:var(--vp-nav-height);pointer-events:none;white-space:nowrap;transition:background-color .25s}.VPNavBar.screen-open[data-v-9fd4d1dd]{transition:none;background-color:var(--vp-nav-bg-color);border-bottom:1px solid var(--vp-c-divider)}.VPNavBar[data-v-9fd4d1dd]:not(.home){background-color:var(--vp-nav-bg-color)}@media (min-width: 960px){.VPNavBar[data-v-9fd4d1dd]:not(.home){background-color:transparent}.VPNavBar[data-v-9fd4d1dd]:not(.has-sidebar):not(.home.top){background-color:var(--vp-nav-bg-color)}}.wrapper[data-v-9fd4d1dd]{padding:0 8px 0 24px}@media (min-width: 768px){.wrapper[data-v-9fd4d1dd]{padding:0 32px}}@media (min-width: 960px){.VPNavBar.has-sidebar .wrapper[data-v-9fd4d1dd]{padding:0}}.container[data-v-9fd4d1dd]{display:flex;justify-content:space-between;margin:0 auto;max-width:calc(var(--vp-layout-max-width) - 64px);height:var(--vp-nav-height);pointer-events:none}.container>.title[data-v-9fd4d1dd],.container>.content[data-v-9fd4d1dd]{pointer-events:none}.container[data-v-9fd4d1dd] *{pointer-events:auto}@media (min-width: 960px){.VPNavBar.has-sidebar .container[data-v-9fd4d1dd]{max-width:100%}}.title[data-v-9fd4d1dd]{flex-shrink:0;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media (min-width: 960px){.VPNavBar.has-sidebar .title[data-v-9fd4d1dd]{position:absolute;top:0;left:0;z-index:2;padding:0 32px;width:var(--vp-sidebar-width);height:var(--vp-nav-height);background-color:transparent}}@media (min-width: 1440px){.VPNavBar.has-sidebar .title[data-v-9fd4d1dd]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}.content[data-v-9fd4d1dd]{flex-grow:1}@media (min-width: 960px){.VPNavBar.has-sidebar .content[data-v-9fd4d1dd]{position:relative;z-index:1;padding-right:32px;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .content[data-v-9fd4d1dd]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.content-body[data-v-9fd4d1dd]{display:flex;justify-content:flex-end;align-items:center;height:var(--vp-nav-height);transition:background-color .5s}@media (min-width: 960px){.VPNavBar:not(.home.top) .content-body[data-v-9fd4d1dd]{position:relative;background-color:var(--vp-nav-bg-color)}.VPNavBar:not(.has-sidebar):not(.home.top) .content-body[data-v-9fd4d1dd]{background-color:transparent}}@media (max-width: 767px){.content-body[data-v-9fd4d1dd]{column-gap:.5rem}}.menu+.translations[data-v-9fd4d1dd]:before,.menu+.appearance[data-v-9fd4d1dd]:before,.menu+.social-links[data-v-9fd4d1dd]:before,.translations+.appearance[data-v-9fd4d1dd]:before,.appearance+.social-links[data-v-9fd4d1dd]:before{margin-right:8px;margin-left:8px;width:1px;height:24px;background-color:var(--vp-c-divider);content:""}.menu+.appearance[data-v-9fd4d1dd]:before,.translations+.appearance[data-v-9fd4d1dd]:before{margin-right:16px}.appearance+.social-links[data-v-9fd4d1dd]:before{margin-left:16px}.social-links[data-v-9fd4d1dd]{margin-right:-8px}.divider[data-v-9fd4d1dd]{width:100%;height:1px}@media (min-width: 960px){.VPNavBar.has-sidebar .divider[data-v-9fd4d1dd]{padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .divider[data-v-9fd4d1dd]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.divider-line[data-v-9fd4d1dd]{width:100%;height:1px;transition:background-color .5s}.VPNavBar:not(.home) .divider-line[data-v-9fd4d1dd]{background-color:var(--vp-c-gutter)}@media (min-width: 960px){.VPNavBar:not(.home.top) .divider-line[data-v-9fd4d1dd]{background-color:var(--vp-c-gutter)}.VPNavBar:not(.has-sidebar):not(.home.top) .divider[data-v-9fd4d1dd]{background-color:var(--vp-c-gutter)}}.VPNavScreenAppearance[data-v-a3e2920d]{display:flex;justify-content:space-between;align-items:center;border-radius:8px;padding:12px 14px 12px 16px;background-color:var(--vp-c-bg-soft)}.text[data-v-a3e2920d]{line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.VPNavScreenMenuLink[data-v-fa963d97]{display:block;border-bottom:1px solid var(--vp-c-divider);padding:12px 0 11px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:border-color .25s,color .25s}.VPNavScreenMenuLink[data-v-fa963d97]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupLink[data-v-e04f3e85]{display:block;margin-left:12px;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-1);transition:color .25s}.VPNavScreenMenuGroupLink[data-v-e04f3e85]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupSection[data-v-f60dbfa7]{display:block}.title[data-v-f60dbfa7]{line-height:32px;font-size:13px;font-weight:700;color:var(--vp-c-text-2);transition:color .25s}.VPNavScreenMenuGroup[data-v-d99bfeec]{border-bottom:1px solid var(--vp-c-divider);height:48px;overflow:hidden;transition:border-color .5s}.VPNavScreenMenuGroup .items[data-v-d99bfeec]{visibility:hidden}.VPNavScreenMenuGroup.open .items[data-v-d99bfeec]{visibility:visible}.VPNavScreenMenuGroup.open[data-v-d99bfeec]{padding-bottom:10px;height:auto}.VPNavScreenMenuGroup.open .button[data-v-d99bfeec]{padding-bottom:6px;color:var(--vp-c-brand-1)}.VPNavScreenMenuGroup.open .button-icon[data-v-d99bfeec]{transform:rotate(45deg)}.button[data-v-d99bfeec]{display:flex;justify-content:space-between;align-items:center;padding:12px 4px 11px 0;width:100%;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.button[data-v-d99bfeec]:hover{color:var(--vp-c-brand-1)}.button-icon[data-v-d99bfeec]{transition:transform .25s}.group[data-v-d99bfeec]:first-child{padding-top:0}.group+.group[data-v-d99bfeec],.group+.item[data-v-d99bfeec]{padding-top:4px}.VPNavScreenTranslations[data-v-516e4bc3]{height:24px;overflow:hidden}.VPNavScreenTranslations.open[data-v-516e4bc3]{height:auto}.title[data-v-516e4bc3]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-text-1)}.icon[data-v-516e4bc3]{font-size:16px}.icon.lang[data-v-516e4bc3]{margin-right:8px}.icon.chevron[data-v-516e4bc3]{margin-left:4px}.list[data-v-516e4bc3]{padding:4px 0 0 24px}.link[data-v-516e4bc3]{line-height:32px;font-size:13px;color:var(--vp-c-text-1)}.VPNavScreen[data-v-2dd6d0c7]{position:fixed;top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px));right:0;bottom:0;left:0;padding:0 32px;width:100%;background-color:var(--vp-nav-screen-bg-color);overflow-y:auto;transition:background-color .25s;pointer-events:auto}.VPNavScreen.fade-enter-active[data-v-2dd6d0c7],.VPNavScreen.fade-leave-active[data-v-2dd6d0c7]{transition:opacity .25s}.VPNavScreen.fade-enter-active .container[data-v-2dd6d0c7],.VPNavScreen.fade-leave-active .container[data-v-2dd6d0c7]{transition:transform .25s ease}.VPNavScreen.fade-enter-from[data-v-2dd6d0c7],.VPNavScreen.fade-leave-to[data-v-2dd6d0c7]{opacity:0}.VPNavScreen.fade-enter-from .container[data-v-2dd6d0c7],.VPNavScreen.fade-leave-to .container[data-v-2dd6d0c7]{transform:translateY(-8px)}@media (min-width: 768px){.VPNavScreen[data-v-2dd6d0c7]{display:none}}.container[data-v-2dd6d0c7]{margin:0 auto;padding:24px 0 96px;max-width:288px}.menu+.translations[data-v-2dd6d0c7],.menu+.appearance[data-v-2dd6d0c7],.translations+.appearance[data-v-2dd6d0c7]{margin-top:24px}.menu+.social-links[data-v-2dd6d0c7]{margin-top:16px}.appearance+.social-links[data-v-2dd6d0c7]{margin-top:16px}.VPNav[data-v-7ad780c2]{position:relative;top:var(--vp-layout-top-height, 0px);left:0;z-index:var(--vp-z-index-nav);width:100%;pointer-events:none;transition:background-color .5s}@media (min-width: 960px){.VPNav[data-v-7ad780c2]{position:fixed}}.VPSidebarItem.level-0[data-v-edd2eed8]{padding-bottom:24px}.VPSidebarItem.collapsed.level-0[data-v-edd2eed8]{padding-bottom:10px}.item[data-v-edd2eed8]{position:relative;display:flex;width:100%}.VPSidebarItem.collapsible>.item[data-v-edd2eed8]{cursor:pointer}.indicator[data-v-edd2eed8]{position:absolute;top:6px;bottom:6px;left:-17px;width:2px;border-radius:2px;transition:background-color .25s}.VPSidebarItem.level-2.is-active>.item>.indicator[data-v-edd2eed8],.VPSidebarItem.level-3.is-active>.item>.indicator[data-v-edd2eed8],.VPSidebarItem.level-4.is-active>.item>.indicator[data-v-edd2eed8],.VPSidebarItem.level-5.is-active>.item>.indicator[data-v-edd2eed8]{background-color:var(--vp-c-brand-1)}.link[data-v-edd2eed8]{display:flex;align-items:center;flex-grow:1}.text[data-v-edd2eed8]{flex-grow:1;padding:4px 0;line-height:24px;font-size:14px;transition:color .25s}.VPSidebarItem.level-0 .text[data-v-edd2eed8]{font-weight:700;color:var(--vp-c-text-1)}.VPSidebarItem.level-1 .text[data-v-edd2eed8],.VPSidebarItem.level-2 .text[data-v-edd2eed8],.VPSidebarItem.level-3 .text[data-v-edd2eed8],.VPSidebarItem.level-4 .text[data-v-edd2eed8],.VPSidebarItem.level-5 .text[data-v-edd2eed8]{font-weight:500;color:var(--vp-c-text-2)}.VPSidebarItem.level-0.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-1.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-2.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-3.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-4.is-link>.item>.link:hover .text[data-v-edd2eed8],.VPSidebarItem.level-5.is-link>.item>.link:hover .text[data-v-edd2eed8]{color:var(--vp-c-brand-1)}.VPSidebarItem.level-0.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-1.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-2.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-3.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-4.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-5.has-active>.item>.text[data-v-edd2eed8],.VPSidebarItem.level-0.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-1.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-2.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-3.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-4.has-active>.item>.link>.text[data-v-edd2eed8],.VPSidebarItem.level-5.has-active>.item>.link>.text[data-v-edd2eed8]{color:var(--vp-c-text-1)}.VPSidebarItem.level-0.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-1.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-2.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-3.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-4.is-active>.item .link>.text[data-v-edd2eed8],.VPSidebarItem.level-5.is-active>.item .link>.text[data-v-edd2eed8]{color:var(--vp-c-brand-1)}.caret[data-v-edd2eed8]{display:flex;justify-content:center;align-items:center;margin-right:-7px;width:32px;height:32px;color:var(--vp-c-text-3);cursor:pointer;transition:color .25s;flex-shrink:0}.item:hover .caret[data-v-edd2eed8]{color:var(--vp-c-text-2)}.item:hover .caret[data-v-edd2eed8]:hover{color:var(--vp-c-text-1)}.caret-icon[data-v-edd2eed8]{font-size:18px;transform:rotate(90deg);transition:transform .25s}.VPSidebarItem.collapsed .caret-icon[data-v-edd2eed8]{transform:rotate(0)}.VPSidebarItem.level-1 .items[data-v-edd2eed8],.VPSidebarItem.level-2 .items[data-v-edd2eed8],.VPSidebarItem.level-3 .items[data-v-edd2eed8],.VPSidebarItem.level-4 .items[data-v-edd2eed8],.VPSidebarItem.level-5 .items[data-v-edd2eed8]{border-left:1px solid var(--vp-c-divider);padding-left:16px}.VPSidebarItem.collapsed .items[data-v-edd2eed8]{display:none}.no-transition[data-v-51288d80] .caret-icon{transition:none}.group+.group[data-v-51288d80]{border-top:1px solid var(--vp-c-divider);padding-top:10px}@media (min-width: 960px){.group[data-v-51288d80]{padding-top:10px;width:calc(var(--vp-sidebar-width) - 64px)}}.VPSidebar[data-v-42c4c606]{position:fixed;top:var(--vp-layout-top-height, 0px);bottom:0;left:0;z-index:var(--vp-z-index-sidebar);padding:32px 32px 96px;width:calc(100vw - 64px);max-width:320px;background-color:var(--vp-sidebar-bg-color);opacity:0;box-shadow:var(--vp-c-shadow-3);overflow-x:hidden;overflow-y:auto;transform:translate(-100%);transition:opacity .5s,transform .25s ease;overscroll-behavior:contain}.VPSidebar.open[data-v-42c4c606]{opacity:1;visibility:visible;transform:translate(0);transition:opacity .25s,transform .5s cubic-bezier(.19,1,.22,1)}.dark .VPSidebar[data-v-42c4c606]{box-shadow:var(--vp-shadow-1)}@media (min-width: 960px){.VPSidebar[data-v-42c4c606]{padding-top:var(--vp-nav-height);width:var(--vp-sidebar-width);max-width:100%;background-color:var(--vp-sidebar-bg-color);opacity:1;visibility:visible;box-shadow:none;transform:translate(0)}}@media (min-width: 1440px){.VPSidebar[data-v-42c4c606]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}@media (min-width: 960px){.curtain[data-v-42c4c606]{position:sticky;top:-64px;left:0;z-index:1;margin-top:calc(var(--vp-nav-height) * -1);margin-right:-32px;margin-left:-32px;height:var(--vp-nav-height);background-color:var(--vp-sidebar-bg-color)}}.nav[data-v-42c4c606]{outline:0}.VPSkipLink[data-v-c8291ffa]{top:8px;left:8px;padding:8px 16px;z-index:999;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;color:var(--vp-c-brand-1);box-shadow:var(--vp-shadow-3);background-color:var(--vp-c-bg)}.VPSkipLink[data-v-c8291ffa]:focus{height:auto;width:auto;clip:auto;clip-path:none}@media (min-width: 1280px){.VPSkipLink[data-v-c8291ffa]{top:14px;left:16px}}.Layout[data-v-d8b57b2d]{display:flex;flex-direction:column;min-height:100vh}.VPHomeSponsors[data-v-3dc26e1d]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPHomeSponsors[data-v-3dc26e1d]{margin:96px 0}@media (min-width: 768px){.VPHomeSponsors[data-v-3dc26e1d]{margin:128px 0}}.VPHomeSponsors[data-v-3dc26e1d]{padding:0 24px}@media (min-width: 768px){.VPHomeSponsors[data-v-3dc26e1d]{padding:0 48px}}@media (min-width: 960px){.VPHomeSponsors[data-v-3dc26e1d]{padding:0 64px}}.container[data-v-3dc26e1d]{margin:0 auto;max-width:1152px}.love[data-v-3dc26e1d]{margin:0 auto;width:fit-content;font-size:28px;color:var(--vp-c-text-3)}.icon[data-v-3dc26e1d]{display:inline-block}.message[data-v-3dc26e1d]{margin:0 auto;padding-top:10px;max-width:320px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.sponsors[data-v-3dc26e1d]{padding-top:32px}.action[data-v-3dc26e1d]{padding-top:40px;text-align:center}.VPTeamPage[data-v-a5329171]{margin:96px 0}@media (min-width: 768px){.VPTeamPage[data-v-a5329171]{margin:128px 0}}.VPHome .VPTeamPageTitle[data-v-a5329171-s]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPTeamPageSection+.VPTeamPageSection[data-v-a5329171-s],.VPTeamMembers+.VPTeamPageSection[data-v-a5329171-s]{margin-top:64px}.VPTeamMembers+.VPTeamMembers[data-v-a5329171-s]{margin-top:24px}@media (min-width: 768px){.VPTeamPageTitle+.VPTeamPageSection[data-v-a5329171-s]{margin-top:16px}.VPTeamPageSection+.VPTeamPageSection[data-v-a5329171-s],.VPTeamMembers+.VPTeamPageSection[data-v-a5329171-s]{margin-top:96px}}.VPTeamMembers[data-v-a5329171-s]{padding:0 24px}@media (min-width: 768px){.VPTeamMembers[data-v-a5329171-s]{padding:0 48px}}@media (min-width: 960px){.VPTeamMembers[data-v-a5329171-s]{padding:0 64px}}.VPTeamPageTitle[data-v-46c5e327]{padding:48px 32px;text-align:center}@media (min-width: 768px){.VPTeamPageTitle[data-v-46c5e327]{padding:64px 48px 48px}}@media (min-width: 960px){.VPTeamPageTitle[data-v-46c5e327]{padding:80px 64px 48px}}.title[data-v-46c5e327]{letter-spacing:0;line-height:44px;font-size:36px;font-weight:500}@media (min-width: 768px){.title[data-v-46c5e327]{letter-spacing:-.5px;line-height:56px;font-size:48px}}.lead[data-v-46c5e327]{margin:0 auto;max-width:512px;padding-top:12px;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 768px){.lead[data-v-46c5e327]{max-width:592px;letter-spacing:.15px;line-height:28px;font-size:20px}}.VPTeamPageSection[data-v-3bf2e850]{padding:0 32px}@media (min-width: 768px){.VPTeamPageSection[data-v-3bf2e850]{padding:0 48px}}@media (min-width: 960px){.VPTeamPageSection[data-v-3bf2e850]{padding:0 64px}}.title[data-v-3bf2e850]{position:relative;margin:0 auto;max-width:1152px;text-align:center;color:var(--vp-c-text-2)}.title-line[data-v-3bf2e850]{position:absolute;top:16px;left:0;width:100%;height:1px;background-color:var(--vp-c-divider)}.title-text[data-v-3bf2e850]{position:relative;display:inline-block;padding:0 24px;letter-spacing:0;line-height:32px;font-size:20px;font-weight:500;background-color:var(--vp-c-bg)}.lead[data-v-3bf2e850]{margin:0 auto;max-width:480px;padding-top:12px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.members[data-v-3bf2e850]{padding-top:40px}.VPTeamMembersItem[data-v-acff304e]{display:flex;flex-direction:column;gap:2px;border-radius:12px;width:100%;height:100%;overflow:hidden}.VPTeamMembersItem.small .profile[data-v-acff304e]{padding:32px}.VPTeamMembersItem.small .data[data-v-acff304e]{padding-top:20px}.VPTeamMembersItem.small .avatar[data-v-acff304e]{width:64px;height:64px}.VPTeamMembersItem.small .name[data-v-acff304e]{line-height:24px;font-size:16px}.VPTeamMembersItem.small .affiliation[data-v-acff304e]{padding-top:4px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .desc[data-v-acff304e]{padding-top:12px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .links[data-v-acff304e]{margin:0 -16px -20px;padding:10px 0 0}.VPTeamMembersItem.medium .profile[data-v-acff304e]{padding:48px 32px}.VPTeamMembersItem.medium .data[data-v-acff304e]{padding-top:24px;text-align:center}.VPTeamMembersItem.medium .avatar[data-v-acff304e]{width:96px;height:96px}.VPTeamMembersItem.medium .name[data-v-acff304e]{letter-spacing:.15px;line-height:28px;font-size:20px}.VPTeamMembersItem.medium .affiliation[data-v-acff304e]{padding-top:4px;font-size:16px}.VPTeamMembersItem.medium .desc[data-v-acff304e]{padding-top:16px;max-width:288px;font-size:16px}.VPTeamMembersItem.medium .links[data-v-acff304e]{margin:0 -16px -12px;padding:16px 12px 0}.profile[data-v-acff304e]{flex-grow:1;background-color:var(--vp-c-bg-soft)}.data[data-v-acff304e]{text-align:center}.avatar[data-v-acff304e]{position:relative;flex-shrink:0;margin:0 auto;border-radius:50%;box-shadow:var(--vp-shadow-3)}.avatar-img[data-v-acff304e]{position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;object-fit:cover}.name[data-v-acff304e]{margin:0;font-weight:600}.affiliation[data-v-acff304e]{margin:0;font-weight:500;color:var(--vp-c-text-2)}.org.link[data-v-acff304e]{color:var(--vp-c-text-2);transition:color .25s}.org.link[data-v-acff304e]:hover{color:var(--vp-c-brand-1)}.desc[data-v-acff304e]{margin:0 auto}.desc[data-v-acff304e] a{font-weight:500;color:var(--vp-c-brand-1);text-decoration-style:dotted;transition:color .25s}.links[data-v-acff304e]{display:flex;justify-content:center;height:56px}.sp-link[data-v-acff304e]{display:flex;justify-content:center;align-items:center;text-align:center;padding:16px;font-size:14px;font-weight:500;color:var(--vp-c-sponsor);background-color:var(--vp-c-bg-soft);transition:color .25s,background-color .25s}.sp .sp-link.link[data-v-acff304e]:hover,.sp .sp-link.link[data-v-acff304e]:focus{outline:none;color:var(--vp-c-white);background-color:var(--vp-c-sponsor)}.sp-icon[data-v-acff304e]{margin-right:8px;font-size:16px}.VPTeamMembers.small .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(224px,1fr))}.VPTeamMembers.small.count-1 .container[data-v-bf782009]{max-width:276px}.VPTeamMembers.small.count-2 .container[data-v-bf782009]{max-width:576px}.VPTeamMembers.small.count-3 .container[data-v-bf782009]{max-width:876px}.VPTeamMembers.medium .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(256px,1fr))}@media (min-width: 375px){.VPTeamMembers.medium .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(288px,1fr))}}.VPTeamMembers.medium.count-1 .container[data-v-bf782009]{max-width:368px}.VPTeamMembers.medium.count-2 .container[data-v-bf782009]{max-width:760px}.container[data-v-bf782009]{display:grid;gap:24px;margin:0 auto;max-width:1152px}:root{--vp-c-brand-1: #08557F;--vp-c-brand-2: #347FAA;--vp-c-brand-3: #61AAD4;--vp-c-brand-4: #8DD4FF}:root.dark{--vp-c-brand-0: #8DD4FF;--vp-c-brand-1: #61AAD4;--vp-c-brand-2: #347FAA;--vp-c-brand-3: #08557F}:root{--vp-home-hero-name-color: transparent;--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #38af90 30%, #417fac);--vp-home-hero-image-background-image: linear-gradient(-45deg, #38af90 50%, #417fac 50%);--vp-home-hero-image-filter: blur(44px)}@media (min-width: 640px){:root{--vp-home-hero-image-filter: blur(56px)}}@media (min-width: 960px){:root{--vp-home-hero-image-filter: blur(68px)}}:root{--theme-foreground: #1b1e23;--theme-foreground-focus: var(--vp-c-brand-3);--theme-background-a: #ffffff;--theme-background-b: color-mix(in srgb, var(--theme-foreground) 4%, var(--theme-background-a));--theme-background: var(--theme-background-a);--theme-background-alt: var(--theme-background-b);--theme-foreground-alt: color-mix(in srgb, var(--theme-foreground) 90%, var(--theme-background-a));--theme-foreground-muted: color-mix(in srgb, var(--theme-foreground) 60%, var(--theme-background-a));--theme-foreground-faint: color-mix(in srgb, var(--theme-foreground) 50%, var(--theme-background-a));--theme-foreground-fainter: color-mix(in srgb, var(--theme-foreground) 30%, var(--theme-background-a));--theme-foreground-faintest: color-mix(in srgb, var(--theme-foreground) 14%, var(--theme-background-a));color-scheme:light}:root.dark{--theme-foreground: #ffffff;--theme-foreground-focus: var(--vp-c-brand-4);--theme-background-a: #000000;--theme-background-b: color-mix(in srgb, var(--theme-foreground) 15%, var(--theme-background-a));--theme-background: var(--theme-background-a);--theme-background-alt: var(--theme-background-b);--theme-foreground-alt: color-mix(in srgb, var(--theme-foreground) 90%, var(--theme-background-a));--theme-foreground-muted: color-mix(in srgb, var(--theme-foreground) 60%, var(--theme-background-a));--theme-foreground-faint: color-mix(in srgb, var(--theme-foreground) 50%, var(--theme-background-a));--theme-foreground-fainter: color-mix(in srgb, var(--theme-foreground) 50%, var(--theme-background-a));--theme-foreground-faintest: color-mix(in srgb, var(--theme-foreground) 50%, var(--theme-background-a));color-scheme:dark}:root{--monospace: Menlo, Consolas, monospace;--monospace-font: 14px/1.5 var(--monospace);--serif: "Source Serif 4", "Iowan Old Style", "Apple Garamond", "Palatino Linotype", "Times New Roman", "Droid Serif", Times, serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--sans-serif: -apple-system, BlinkMacSystemFont, "avenir next", avenir, helvetica, "helvetica neue", ubuntu, roboto, noto, "segoe ui", arial, sans-serif;--theme-blue: #4269d0;--theme-green: #3ca951;--theme-red: #ff725c;--theme-yellow: #efb118}.VPDoc div.grid{color:var(--theme-foreground-muted)}.VPDoc div.grid h1,.VPDoc div.grid h2,.VPDoc div.grid h3,.VPDoc div.grid h4,.VPDoc div.grid h5,.VPDoc div.grid h6{border:none;padding-top:0;color:var(--theme-foreground-muted);font-weight:700;line-height:1.15;margin-top:0;margin-bottom:.25rem;text-wrap:balance}.VPDoc div.grid h2+p,.VPDoc div.grid h3+p,.VPDoc div.grid h4+p,.VPDoc div.grid h2+table,.VPDoc div.grid h3+table,.VPDoc div.grid h4+table{margin-top:0}.VPDoc div.grid h1+h2{color:var(--theme-foreground);font-size:20px;font-style:italic;font-weight:400;margin-bottom:1rem}.VPDoc div.grid a[href]{text-decoration:none}.VPDoc div.grid a[href]:hover,.VPDoc div.grid a[href]:focus{text-decoration:underline}.VPDoc div.grid h1 code,.VPDoc div.grid h2 code,.VPDoc div.grid h3 code,.VPDoc div.grid h4 code,.VPDoc div.grid h5 code,.VPDoc div.grid h6 code{font-size:90%}.VPDoc div.grid pre{line-height:1.5}.VPDoc div.grid pre,.VPDoc div.grid code,.VPDoc div.grid tt{font-family:var(--monospace);font-size:14px}.VPDoc div.grid img{max-width:100%}.VPDoc div.grid p,.VPDoc div.grid table,.VPDoc div.grid figure,.VPDoc div.grid figcaption,.VPDoc div.grid h1,.VPDoc div.grid h2,.VPDoc div.grid h3,.VPDoc div.grid h4,.VPDoc div.grid h5,.VPDoc div.grid h6,.VPDoc div.grid .katex-display{max-width:640px}.VPDoc div.grid blockquote,.VPDoc div.grid ol,.VPDoc div.grid ul{max-width:600px}.VPDoc div.grid blockquote{margin:1rem 1.5rem}.VPDoc div.grid ul ol{padding-left:28px}.VPDoc div.grid hr{height:1px;margin:1rem 0;padding:1rem 0;border:none;background:no-repeat center/100% 1px linear-gradient(to right,var(--theme-foreground-faintest),var(--theme-foreground-faintest))}.VPDoc div.grid pre{background-color:var(--theme-background-alt);border-radius:4px;margin:1rem -1rem;max-width:960px;min-height:1.5em;padding:.5rem 1rem;overflow-x:auto;box-sizing:border-box}.VPDoc div.grid input:not([type]),.VPDoc div.grid input[type=email],.VPDoc div.grid input[type=number],.VPDoc div.grid input[type=password],.VPDoc div.grid input[type=range],.VPDoc div.grid input[type=search],.VPDoc div.grid input[type=tel],.VPDoc div.grid input[type=text],.VPDoc div.grid input[type=url]{width:240px}.VPDoc div.grid input,.VPDoc div.grid canvas,.VPDoc div.grid button{vertical-align:middle}.VPDoc div.grid button,.VPDoc div.grid input,.VPDoc div.grid textarea{accent-color:var(--theme-blue)}.VPDoc div.grid table{width:100%;border-collapse:collapse;font:13px/1.2 var(--sans-serif)}.VPDoc div.grid table pre,.VPDoc div.grid table code,.VPDoc div.grid table tt{font-size:inherit;line-height:inherit}.VPDoc div.grid th>pre:only-child,.VPDoc div.grid td>pre:only-child{margin:0;padding:0}.VPDoc div.grid th{color:var(--theme-foreground);text-align:left;vertical-align:bottom}.VPDoc div.grid td{color:var(--theme-foreground-alt);vertical-align:top}.VPDoc div.grid th,.VPDoc div.grid td{padding:3px 6.5px 3px 0}.VPDoc div.grid th:last-child,.VPDoc div.grid td:last-child{padding-right:0}.VPDoc div.grid tr:not(:last-child){border-bottom:solid 1px var(--theme-foreground-faintest)}.VPDoc div.grid thead tr{border-bottom:solid 1px var(--theme-foreground-fainter)}.VPDoc div.grid figure,.VPDoc div.grid table{margin:1rem 0}.VPDoc div.grid figure img{max-width:100%}.VPDoc div.grid figure>h2,.VPDoc div.grid figure>h3{font-family:var(--sans-serif)}.VPDoc div.grid figure>h2{font-size:20px}.VPDoc div.grid figure>h3{font-size:16px;font-weight:400}.VPDoc div.grid figcaption{font:small var(--sans-serif);color:var(--theme-foreground-muted)}.VPDoc div.grid a[href].observablehq-header-anchor{color:inherit}:root{--font-big: 700 32px/1 var(--sans-serif);--font-small: 14px var(--sans-serif)}.VPDoc div.grid .big{font:var(--font-big)}.VPDoc div.grid .small{font:var(--font-small)}.VPDoc div.grid .red{color:var(--theme-red)}.VPDoc div.grid .yellow{color:var(--theme-yellow)}.VPDoc div.grid .green{color:var(--theme-green)}.VPDoc div.grid .blue{color:var(--theme-blue)}.VPDoc div.grid .muted{color:var(--theme-foreground-faint)}.VPDoc div.grid .observablehq--draft>h1:first-of-type:after{content:" [DRAFT]";color:var(--theme-foreground-faint)}:root{--theme-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M5 7L8.125 9.5L11.25 7' stroke='black' stroke-width='1.5' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");--theme-toggle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='m10.5,11 2.5-3-2.5-3 M6,8h7' fill='none' stroke='black' stroke-width='2'/%3E%3Crect x='2' y='2' fill='currentColor' height='12' rx='0.5' width='2'/%3E%3C/svg%3E");--theme-magnifier: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath stroke='currentColor' stroke-width='2' fill='none' d='M15,15L10.5,10.5a3,3 0 1,0 -6 -6a3,3 0 1,0 6 6'%3E%3C/path%3E%3C/svg%3E");--observablehq-max-width: 1440px;--observablehq-header-height: 2.2rem;scroll-padding-top:2.5rem}:root:has(#observablehq-header){scroll-padding-top:calc(var(--observablehq-header-height) + 2.5rem)}body{max-width:var(--observablehq-max-width);margin:auto}#observablehq-main,#observablehq-header,#observablehq-footer{margin:1rem auto}#observablehq-header{position:fixed;top:0;left:calc(max(0rem,(100vw - var(--observablehq-max-width)) / 2) + var(--observablehq-inset-left) + 2rem);right:calc(max(0rem,(100vw - var(--observablehq-max-width)) / 2) + var(--observablehq-inset-right) + 2rem);z-index:1;display:flex;align-items:center;gap:.5rem;height:var(--observablehq-header-height);margin:0 -2rem 2rem;padding:1rem 2rem .5rem;background:var(--theme-background);border-bottom:solid 1px var(--theme-foreground-faintest);font:500 16px var(--sans-serif)}#observablehq-main{min-height:calc(100vh - 20rem);position:relative;z-index:0}#observablehq-header~#observablehq-main{margin-top:calc(var(--observablehq-header-height) + 1.5rem + 2rem)}#observablehq-footer{display:block;margin-top:10rem;font:12px var(--sans-serif);color:var(--theme-foreground-faint)}#observablehq-footer nav{display:grid;max-width:640px;grid-template-columns:1fr 1fr;column-gap:1rem;margin-bottom:1rem}#observablehq-footer nav a{display:flex;flex-direction:column;border:1px solid var(--theme-foreground-fainter);border-radius:8px;padding:1rem;line-height:1rem;text-decoration:none}#observablehq-footer nav a span{font-size:14px}#observablehq-footer nav a:hover span{text-decoration:underline}#observablehq-footer nav a:hover{border-color:var(--theme-foreground-focus)}#observablehq-footer nav a[rel=prev]{grid-column:1;align-items:start}#observablehq-footer nav a[rel=next]{grid-column:2;align-items:end}#observablehq-footer nav a:before{color:var(--theme-foreground-faint)}#observablehq-footer nav a[rel=prev]:before{content:"Previous page"}#observablehq-footer nav a[rel=next]:before{content:"Next page"}#observablehq-center{margin:2rem;--observablehq-inset-left: 0rem;--observablehq-inset-right: 0rem}#observablehq-sidebar{--observablehq-sidebar-padding-left: max(0rem, (100vw - var(--observablehq-max-width)) / 2) ;position:fixed;background:var(--theme-background-alt);color:var(--theme-foreground-muted);font:14px var(--sans-serif);visibility:hidden;font-weight:500;width:calc(272px + var(--observablehq-sidebar-padding-left));z-index:2;top:0;bottom:0;left:-272px;box-sizing:border-box;padding:0 .5rem 1rem calc(var(--observablehq-sidebar-padding-left) + .5rem);overflow-y:auto}#observablehq-sidebar ol,#observablehq-toc ol{list-style:none;margin:0;padding:0}#observablehq-sidebar>ol,#observablehq-sidebar>details,#observablehq-sidebar>section{position:relative;padding-bottom:.5rem;margin:.5rem 0;border-bottom:solid 1px var(--theme-foreground-faintest)}#observablehq-sidebar>ol:first-child{position:sticky;top:0;z-index:1;font-size:16px;font-weight:700;padding-top:1rem;margin:0;color:var(--theme-foreground)}#observablehq-sidebar>ol:first-child:before{content:"";position:absolute;top:0;right:-.5rem;bottom:0;left:-.5rem;background:var(--theme-background-alt)}#observablehq-sidebar>ol:first-child>li{position:relative}#observablehq-sidebar>ol:first-child>li>a{height:calc(var(--observablehq-header-height) - 1rem)}#observablehq-sidebar>ol:last-child,#observablehq-sidebar>details:last-child,#observablehq-sidebar>section:last-child{border-bottom:none}#observablehq-sidebar summary{font-weight:700;color:var(--theme-foreground);cursor:default}#observablehq-sidebar summary::-webkit-details-marker,#observablehq-sidebar summary::marker{display:none}#observablehq-sidebar details summary:after{position:absolute;right:0;width:1rem;height:1rem;background:var(--theme-foreground-muted);content:"";-webkit-mask:var(--theme-caret);-webkit-mask-repeat:no-repeat;-webkit-mask-position:center;mask:var(--theme-caret);mask-repeat:no-repeat;mask-position:center;padding:.5rem;transition:transform .25s ease;transform:rotate(-90deg);transform-origin:50% 50%}#observablehq-sidebar details summary:hover:after{color:var(--theme-foreground)}#observablehq-sidebar details[open] summary:after{transform:rotate(0)}#observablehq-sidebar-toggle{position:fixed;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;top:0;left:0;height:100%;width:2rem;display:flex;align-items:center;justify-content:center;cursor:e-resize;margin:0;color:var(--theme-foreground-muted);z-index:1}#observablehq-sidebar-close{position:absolute;top:1rem;right:0;width:2rem;height:var(--observablehq-header-height);display:flex;align-items:center;justify-content:center;color:var(--theme-foreground-muted);cursor:w-resize;z-index:2}#observablehq-sidebar-toggle:before,#observablehq-sidebar-close:before{content:"";width:1rem;height:1rem;background:currentColor;-webkit-mask:var(--theme-toggle);mask:var(--theme-toggle)}#observablehq-sidebar-close:before{transform:scaleX(-1)}#observablehq-sidebar summary,.observablehq-link a{display:flex;padding:.5rem 1rem .5rem 1.5rem;margin-left:-.5rem;align-items:center}#observablehq-sidebar summary a{flex-grow:1;color:inherit}#observablehq-sidebar summary.observablehq-link{padding:0;margin-left:0}#observablehq-sidebar details summary:hover,.observablehq-link-active a,.observablehq-link a:hover{background:var(--theme-background)}.observablehq-link a:hover{color:var(--theme-foreground-focus)}#observablehq-toc{display:none;position:fixed;color:var(--theme-foreground-muted);font:400 14px var(--sans-serif);z-index:1;top:0;right:calc(max(0rem,(100% - var(--observablehq-max-width)) / 2) + 1rem);bottom:0;overflow-y:auto}#observablehq-header~#observablehq-toc{top:calc(var(--observablehq-header-height) + 1.5rem)}#observablehq-toc nav{width:192px;margin:2rem 0;padding:0 1rem;box-sizing:border-box;border-left:solid 1px var(--theme-foreground-faintest)}#observablehq-toc div{font-weight:700;color:var(--theme-foreground);margin-bottom:.5rem}.observablehq-secondary-link a{display:block;padding:.25rem 0}.observablehq-link:not(.observablehq-link-active) a[href]:not(:hover),.observablehq-secondary-link:not(.observablehq-secondary-link-active) a[href]:not(:hover){color:inherit}.observablehq-link-active,.observablehq-secondary-link-active{position:relative}.observablehq-link-active:before,.observablehq-secondary-link-highlight{content:"";position:absolute;width:3px;background:var(--theme-foreground-focus)}.observablehq-link-active:before{top:0;bottom:0;left:-.5rem}.observablehq-secondary-link-highlight{left:1px;top:2rem;height:0;transition:top .15s ease,height .15s ease}#observablehq-sidebar{transition:visibility .15s 0ms,left .15s 0ms ease}#observablehq-sidebar:focus-within,#observablehq-sidebar-toggle:checked~#observablehq-sidebar{left:0;visibility:initial;box-shadow:0 0 8px 4px #0000001a;transition:visibility 0ms 0ms,left .15s 0ms ease}#observablehq-sidebar-backdrop{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2}#observablehq-sidebar-backdrop:has(~#observablehq-sidebar:focus-within),#observablehq-sidebar-toggle:checked~#observablehq-sidebar-backdrop{display:initial}@media (prefers-color-scheme: dark){#observablehq-sidebar:focus-within,#observablehq-sidebar-toggle:checked~#observablehq-sidebar{box-shadow:0 0 8px 4px #00000080}}@media (min-width: calc(912px + 6rem)){#observablehq-sidebar{transition:none!important}#observablehq-sidebar-toggle:checked~#observablehq-sidebar-backdrop{display:none}#observablehq-sidebar-toggle:checked~#observablehq-sidebar,#observablehq-sidebar-toggle:indeterminate~#observablehq-sidebar{left:0;visibility:initial;box-shadow:none}#observablehq-sidebar-toggle:checked~#observablehq-center,#observablehq-sidebar-toggle:indeterminate~#observablehq-center{--observablehq-inset-left: calc(272px + 1rem) ;--observablehq-inset-right: 1rem;padding-left:var(--observablehq-inset-left);padding-right:1rem}}@media (min-width: calc(832px + 5rem)){#observablehq-toc~#observablehq-main{padding-right:calc(192px + 1rem)}#observablehq-toc{display:block}}@media (min-width: calc(912px + 6rem)){#observablehq-sidebar-toggle:checked~#observablehq-center #observablehq-toc,#observablehq-sidebar-toggle:indeterminate~#observablehq-center #observablehq-toc{display:none}#observablehq-sidebar-toggle:checked~#observablehq-center #observablehq-toc~#observablehq-main,#observablehq-sidebar-toggle:indeterminate~#observablehq-center #observablehq-toc~#observablehq-main{padding-right:0}}@media (min-width: calc(1104px + 7rem)){#observablehq-sidebar-toggle:checked~#observablehq-center #observablehq-toc,#observablehq-sidebar-toggle:indeterminate~#observablehq-center #observablehq-toc,#observablehq-toc{display:block}#observablehq-sidebar-toggle:checked~#observablehq-center #observablehq-toc~#observablehq-main,#observablehq-sidebar-toggle:indeterminate~#observablehq-center #observablehq-toc~#observablehq-main{padding-right:calc(192px + 1rem)}}.observablehq-pre-container{position:relative;margin:1rem -1rem;max-width:960px}.observablehq-pre-container:after{position:absolute;top:0;right:0;height:21px;font:12px var(--sans-serif);color:var(--theme-foreground-muted);background:linear-gradient(to right,transparent,var(--theme-background-alt) 40%);padding:.5rem .5rem .5rem 1.5rem}.observablehq-pre-container[data-language]:after{content:attr(data-language)}.observablehq-pre-container pre{padding-right:4rem;margin:0;max-width:none}.observablehq-pre-copy{position:absolute;top:0;right:0;background:none;color:transparent;border:none;border-radius:4px;padding:0 8px;margin:4px;height:29px;cursor:pointer;z-index:1;display:flex;align-items:center}.observablehq-pre-copied:before{content:"Copied!";position:absolute;right:calc(100% + .25rem);background:linear-gradient(to right,transparent,var(--theme-background-alt) 10%);color:var(--theme-green);font:var(--font-small);padding:4px 8px 4px 16px;pointer-events:none;animation-name:observablehq-pre-copied;animation-duration:.25s;animation-direction:alternate;animation-iteration-count:2}@keyframes observablehq-pre-copied{0%{opacity:0;transform:translate(.5rem)}50%{opacity:1}to{transform:translate(0)}}.observablehq-pre-container[data-copy] .observablehq-pre-copy,.observablehq-pre-container:hover .observablehq-pre-copy,.observablehq-pre-container .observablehq-pre-copy:focus{background:var(--theme-background-alt);color:var(--theme-foreground-faint)}.observablehq-pre-container .observablehq-pre-copy:hover{color:var(--theme-foreground-muted)}.observablehq-pre-container .observablehq-pre-copy:active{color:var(--theme-foreground);background:var(--theme-foreground-faintest)}#observablehq-sidebar.observablehq-search-results>ol:not(:first-child),#observablehq-sidebar.observablehq-search-results>details,#observablehq-sidebar.observablehq-search-results>section{display:none}#observablehq-search{position:relative;padding:.5rem 0 0;display:flex;align-items:center}#observablehq-search input{padding:6px 4px 6px 2.2em;width:100%;border:none;border-radius:4px;background-color:var(--theme-background);font-size:13.3px;height:28px}#observablehq-search input::placeholder{color:var(--theme-foreground-faint)}#observablehq-search:before{position:absolute;left:.5rem;content:"";width:1rem;height:1rem;background:currentColor;-webkit-mask:var(--theme-magnifier);mask:var(--theme-magnifier);pointer-events:none}#observablehq-search:after{position:absolute;right:6px;content:attr(data-shortcut);pointer-events:none}#observablehq-search:focus-within:after{content:""}#observablehq-search-results{--relevance-width: 32px;position:absolute;overflow-y:auto;top:6.5rem;left:var(--observablehq-sidebar-padding-left);right:.5rem;bottom:0}#observablehq-search-results a span{max-width:184px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#observablehq-search-results div{text-align:right;font-size:10px;margin:.5em}#observablehq-search-results li{position:relative;display:flex;align-items:center}#observablehq-search-results a{flex-grow:1}#observablehq-search-results li:after,#observablehq-search-results a span:after{content:"";width:var(--relevance-width);height:4px;position:absolute;top:14px;right:.5em;border-radius:2px;background:var(--theme-foreground-muted)}#observablehq-search-results li.observablehq-link-active:after{background:var(--theme-foreground-focus)}#observablehq-search-results a span:after{background:var(--theme-foreground-faintest)}#observablehq-search-results li[data-score="0"]:after{width:calc(var(--relevance-width) * .125)}#observablehq-search-results li[data-score="1"]:after{width:calc(var(--relevance-width) * .25)}#observablehq-search-results li[data-score="2"]:after{width:calc(var(--relevance-width) * .4375)}#observablehq-search-results li[data-score="3"]:after{width:calc(var(--relevance-width) * .625)}#observablehq-search-results li[data-score="4"]:after{width:calc(var(--relevance-width) * .8125)}@media print{#observablehq-center{padding-left:1em!important}#observablehq-sidebar,#observablehq-footer{display:none!important}}#VPContent{container-type:inline-size}.VPDoc .grid{margin:1rem 0;display:grid;gap:1rem;grid-auto-rows:1fr}.VPDoc .grid svg{overflow:visible}.VPDoc .grid figure{margin:0}.VPDoc .grid>*>p:first-child{margin-top:0}.VPDoc .grid>*>p:last-child{margin-bottom:0}@container (min-width: 640px){.grid-cols-2,.grid-cols-4{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-2 .grid-colspan-2,.grid-cols-2 .grid-colspan-3,.grid-cols-2 .grid-colspan-4,.grid-cols-4 .grid-colspan-2,.grid-cols-4 .grid-colspan-3,.grid-cols-4 .grid-colspan-4{grid-column:span 2}}@container (min-width: 720px){.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-3 .grid-colspan-2{grid-column:span 2}.grid-cols-3 .grid-colspan-3{grid-column:span 3}}@container (min-width: 900px){.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-4 .grid-colspan-3{grid-column:span 3}.grid-cols-4 .grid-colspan-4{grid-column:span 4}}.grid-rowspan-2{grid-row:span 2}.grid-rowspan-3{grid-row:span 3}.grid-rowspan-4{grid-row:span 4}div.card{background:var(--theme-background-alt);border:solid 1px var(--theme-foreground-faintest);border-radius:.75rem;padding:1rem;margin:1rem 0;font:14px var(--sans-serif)}.grid>div.card{margin:0}div.card>:first-child,div.card>:first-child>:first-child{margin-top:0}div.card>:last-child,div.card>:last-child>:last-child{margin-bottom:0}div.card h2,div.card h3{font-size:inherit}div.card h2{font-weight:500;font-size:15px}div.card h3{font-weight:400;color:var(--theme-foreground-muted)}div.card h2~svg,div.card h3~svg,div.card h2~p,div.card h3~p{margin-top:1rem}.plot-d6a7b5{--plot-background: var(--theme-background)}p .plot-d6a7b5{display:inline-block}.VPLocalSearchBox[data-v-ce12919d]{position:fixed;z-index:100;top:0;right:0;bottom:0;left:0;display:flex}.backdrop[data-v-ce12919d]{position:absolute;top:0;right:0;bottom:0;left:0;background:var(--vp-backdrop-bg-color);transition:opacity .5s}.shell[data-v-ce12919d]{position:relative;padding:12px;margin:64px auto;display:flex;flex-direction:column;gap:16px;background:var(--vp-local-search-bg);width:min(100vw - 60px,900px);height:min-content;max-height:min(100vh - 128px,900px);border-radius:6px}@media (max-width: 767px){.shell[data-v-ce12919d]{margin:0;width:100vw;height:100vh;max-height:none;border-radius:0}}.search-bar[data-v-ce12919d]{border:1px solid var(--vp-c-divider);border-radius:4px;display:flex;align-items:center;padding:0 12px;cursor:text}@media (max-width: 767px){.search-bar[data-v-ce12919d]{padding:0 8px}}.search-bar[data-v-ce12919d]:focus-within{border-color:var(--vp-c-brand-1)}.local-search-icon[data-v-ce12919d]{display:block;font-size:18px}.navigate-icon[data-v-ce12919d]{display:block;font-size:14px}.search-icon[data-v-ce12919d]{margin:8px}@media (max-width: 767px){.search-icon[data-v-ce12919d]{display:none}}.search-input[data-v-ce12919d]{padding:6px 12px;font-size:inherit;width:100%}@media (max-width: 767px){.search-input[data-v-ce12919d]{padding:6px 4px}}.search-actions[data-v-ce12919d]{display:flex;gap:4px}@media (any-pointer: coarse){.search-actions[data-v-ce12919d]{gap:8px}}@media (min-width: 769px){.search-actions.before[data-v-ce12919d]{display:none}}.search-actions button[data-v-ce12919d]{padding:8px}.search-actions button[data-v-ce12919d]:not([disabled]):hover,.toggle-layout-button.detailed-list[data-v-ce12919d]{color:var(--vp-c-brand-1)}.search-actions button.clear-button[data-v-ce12919d]:disabled{opacity:.37}.search-keyboard-shortcuts[data-v-ce12919d]{font-size:.8rem;opacity:75%;display:flex;flex-wrap:wrap;gap:16px;line-height:14px}.search-keyboard-shortcuts span[data-v-ce12919d]{display:flex;align-items:center;gap:4px}@media (max-width: 767px){.search-keyboard-shortcuts[data-v-ce12919d]{display:none}}.search-keyboard-shortcuts kbd[data-v-ce12919d]{background:#8080801a;border-radius:4px;padding:3px 6px;min-width:24px;display:inline-block;text-align:center;vertical-align:middle;border:1px solid rgba(128,128,128,.15);box-shadow:0 2px 2px #0000001a}.results[data-v-ce12919d]{display:flex;flex-direction:column;gap:6px;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}.result[data-v-ce12919d]{display:flex;align-items:center;gap:8px;border-radius:4px;transition:none;line-height:1rem;border:solid 2px var(--vp-local-search-result-border);outline:none}.result>div[data-v-ce12919d]{margin:12px;width:100%;overflow:hidden}@media (max-width: 767px){.result>div[data-v-ce12919d]{margin:8px}}.titles[data-v-ce12919d]{display:flex;flex-wrap:wrap;gap:4px;position:relative;z-index:1001;padding:2px 0}.title[data-v-ce12919d]{display:flex;align-items:center;gap:4px}.title.main[data-v-ce12919d]{font-weight:500}.title-icon[data-v-ce12919d]{opacity:.5;font-weight:500;color:var(--vp-c-brand-1)}.title svg[data-v-ce12919d]{opacity:.5}.result.selected[data-v-ce12919d]{--vp-local-search-result-bg: var(--vp-local-search-result-selected-bg);border-color:var(--vp-local-search-result-selected-border)}.excerpt-wrapper[data-v-ce12919d]{position:relative}.excerpt[data-v-ce12919d]{opacity:50%;pointer-events:none;max-height:140px;overflow:hidden;position:relative;margin-top:4px}.result.selected .excerpt[data-v-ce12919d]{opacity:1}.excerpt[data-v-ce12919d] *{font-size:.8rem!important;line-height:130%!important}.titles[data-v-ce12919d] mark,.excerpt[data-v-ce12919d] mark{background-color:var(--vp-local-search-highlight-bg);color:var(--vp-local-search-highlight-text);border-radius:2px;padding:0 2px}.excerpt[data-v-ce12919d] .vp-code-group .tabs{display:none}.excerpt[data-v-ce12919d] .vp-code-group div[class*=language-]{border-radius:8px!important}.excerpt-gradient-bottom[data-v-ce12919d]{position:absolute;bottom:-1px;left:0;width:100%;height:8px;background:linear-gradient(transparent,var(--vp-local-search-result-bg));z-index:1000}.excerpt-gradient-top[data-v-ce12919d]{position:absolute;top:-1px;left:0;width:100%;height:8px;background:linear-gradient(var(--vp-local-search-result-bg),transparent);z-index:1000}.result.selected .titles[data-v-ce12919d],.result.selected .title-icon[data-v-ce12919d]{color:var(--vp-c-brand-1)!important}.no-results[data-v-ce12919d]{font-size:.9rem;text-align:center;padding:12px}svg[data-v-ce12919d]{flex:none} diff --git a/assets/system_httplib_README.md.WIkDaVQZ.js b/assets/system_httplib_README.md.WIkDaVQZ.js new file mode 100644 index 00000000000..1fb5081d0e9 --- /dev/null +++ b/assets/system_httplib_README.md.WIkDaVQZ.js @@ -0,0 +1,287 @@ +import{_ as i,c as a,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const g=JSON.parse('{"title":"cpp-httplib","description":"","frontmatter":{},"headers":[],"relativePath":"system/httplib/README.md","filePath":"system/httplib/README.md","lastUpdated":1731340314000}'),h={name:"system/httplib/README.md"};function l(k,s,p,e,E,r){return n(),a("div",null,s[0]||(s[0]=[t(`

cpp-httplib

A C++11 single-file header-only cross platform HTTP/HTTPS library.

It's extremely easy to setup. Just include httplib.h file in your code!

Server Example

c++
#include <httplib.h>
+
+int main(void)
+{
+  using namespace httplib;
+
+  Server svr;
+
+  svr.Get("/hi", [](const Request& req, Response& res) {
+    res.set_content("Hello World!", "text/plain");
+  });
+
+  svr.Get(R"(/numbers/(\\d+))", [&](const Request& req, Response& res) {
+    auto numbers = req.matches[1];
+    res.set_content(numbers, "text/plain");
+  });
+
+  svr.Get("/body-header-param", [](const Request& req, Response& res) {
+    if (req.has_header("Content-Length")) {
+      auto val = req.get_header_value("Content-Length");
+    }
+    if (req.has_param("key")) {
+      auto val = req.get_param_value("key");
+    }
+    res.set_content(req.body, "text/plain");
+  });
+
+  svr.Get("/stop", [&](const Request& req, Response& res) {
+    svr.stop();
+  });
+
+  svr.listen("localhost", 1234);
+}

Post, Put, Delete and Options methods are also supported.

Bind a socket to multiple interfaces and any available port

cpp
int port = svr.bind_to_any_port("0.0.0.0");
+svr.listen_after_bind();

Static File Server

cpp
// Mount / to ./www directory
+auto ret = svr.set_mount_point("/", "./www");
+if (!ret) {
+  // The specified base directory doesn't exist...
+}
+
+// Mount /public to ./www directory
+ret = svr.set_mount_point("/public", "./www");
+
+// Mount /public to ./www1 and ./www2 directories
+ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
+ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
+
+// Remove mount /
+ret = svr.remove_mount_point("/");
+
+// Remove mount /public
+ret = svr.remove_mount_point("/public");
cpp
// User defined file extension and MIME type mappings
+svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
+svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
+svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");

The followings are built-in mappings:

ExtensionMIME Type
txttext/plain
html, htmtext/html
csstext/css
jpeg, jpgimage/jpg
pngimage/png
gifimage/gif
svgimage/svg+xml
icoimage/x-icon
jsonapplication/json
pdfapplication/pdf
jsapplication/javascript
wasmapplication/wasm
xmlapplication/xml
xhtmlapplication/xhtml+xml

NOTE: These the static file server methods are not thread safe.

Logging

cpp
svr.set_logger([](const auto& req, const auto& res) {
+  your_logger(req, res);
+});

Error handler

cpp
svr.set_error_handler([](const auto& req, auto& res) {
+  auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
+  char buf[BUFSIZ];
+  snprintf(buf, sizeof(buf), fmt, res.status);
+  res.set_content(buf, "text/html");
+});

'multipart/form-data' POST data

cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
+  auto size = req.files.size();
+  auto ret = req.has_file("name1");
+  const auto& file = req.get_file_value("name1");
+  // file.filename;
+  // file.content_type;
+  // file.content;
+});

Receive content with Content receiver

cpp
svr.Post("/content_receiver",
+  [&](const Request &req, Response &res, const ContentReader &content_reader) {
+    if (req.is_multipart_form_data()) {
+      MultipartFormDataItems files;
+      content_reader(
+        [&](const MultipartFormData &file) {
+          files.push_back(file);
+          return true;
+        },
+        [&](const char *data, size_t data_length) {
+          files.back().content.append(data, data_length);
+          return true;
+        });
+    } else {
+      std::string body;
+      content_reader([&](const char *data, size_t data_length) {
+        body.append(data, data_length);
+        return true;
+      });
+      res.set_content(body, "text/plain");
+    }
+  });

Send content with Content provider

cpp
const size_t DATA_CHUNK_SIZE = 4;
+
+svr.Get("/stream", [&](const Request &req, Response &res) {
+  auto data = new std::string("abcdefg");
+
+  res.set_content_provider(
+    data->size(), // Content length
+    "text/plain", // Content type
+    [data](size_t offset, size_t length, DataSink &sink) {
+      const auto &d = *data;
+      sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
+      return true; // return 'false' if you want to cancel the process.
+    },
+    [data] { delete data; });
+});

Without content length:

cpp
svr.Get("/stream", [&](const Request &req, Response &res) {
+  res.set_content_provider(
+    "text/plain", // Content type
+    [&](size_t offset, size_t length, DataSink &sink) {
+      if (/* there is still data */) {
+        std::vector<char> data;
+        // prepare data...
+        sink.write(data.data(), data.size());
+      } else {
+        done(); // No more data
+      }
+      return true; // return 'false' if you want to cancel the process.
+    });
+});

Chunked transfer encoding

cpp
svr.Get("/chunked", [&](const Request& req, Response& res) {
+  res.set_chunked_content_provider(
+    [](size_t offset, DataSink &sink) {
+      sink.write("123", 3);
+      sink.write("345", 3);
+      sink.write("789", 3);
+      sink.done(); // No more data
+      return true; // return 'false' if you want to cancel the process.
+    }
+  );
+});

'Expect: 100-continue' handler

As default, the server sends 100 Continue response for Expect: 100-continue header.

cpp
// Send a '417 Expectation Failed' response.
+svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
+  return 417;
+});
cpp
// Send a final status without reading the message body.
+svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
+  return res.status = 401;
+});

Keep-Alive connection

cpp
svr.set_keep_alive_max_count(2); // Default is 5

Timeout

c++
svr.set_read_timeout(5, 0); // 5 seconds
+svr.set_write_timeout(5, 0); // 5 seconds
+svr.set_idle_interval(0, 100000); // 100 milliseconds

Set maximum payload length for reading request body

c++
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB

Server-Sent Events

Please see Server example and Client example.

Default thread pool support

ThreadPool is used as a default task queue, and the default thread count is set to value from std::thread::hardware_concurrency().

You can change the thread count by setting CPPHTTPLIB_THREAD_POOL_COUNT.

Override the default thread pool with yours

cpp
class YourThreadPoolTaskQueue : public TaskQueue {
+public:
+  YourThreadPoolTaskQueue(size_t n) {
+    pool_.start_with_thread_count(n);
+  }
+
+  virtual void enqueue(std::function<void()> fn) override {
+    pool_.enqueue(fn);
+  }
+
+  virtual void shutdown() override {
+    pool_.shutdown_gracefully();
+  }
+
+private:
+  YourThreadPool pool_;
+};
+
+svr.new_task_queue = [] {
+  return new YourThreadPoolTaskQueue(12);
+};

Client Example

c++
#include <httplib.h>
+#include <iostream>
+
+int main(void)
+{
+  httplib::Client cli("localhost", 1234);
+
+  if (auto res = cli.Get("/hi")) {
+    if (res->status == 200) {
+      std::cout << res->body << std::endl;
+    }
+  } else {
+    auto err = res.error();
+    ...
+  }
+}

NOTE: Constructor with scheme-host-port string is now supported!

c++
httplib::Client cli("localhost");
+httplib::Client cli("localhost:8080");
+httplib::Client cli("http://localhost");
+httplib::Client cli("http://localhost:8080");
+httplib::Client cli("https://localhost");

GET with HTTP headers

c++
httplib::Headers headers = {
+  { "Accept-Encoding", "gzip, deflate" }
+};
+auto res = cli.Get("/hi", headers);

or

c++
cli.set_default_headers({
+  { "Accept-Encoding", "gzip, deflate" }
+});
+auto res = cli.Get("/hi");

POST

c++
res = cli.Post("/post", "text", "text/plain");
+res = cli.Post("/person", "name=john1&note=coder", "application/x-www-form-urlencoded");

POST with parameters

c++
httplib::Params params;
+params.emplace("name", "john");
+params.emplace("note", "coder");
+
+auto res = cli.Post("/post", params);

or

c++
httplib::Params params{
+  { "name", "john" },
+  { "note", "coder" }
+};
+
+auto res = cli.Post("/post", params);

POST with Multipart Form Data

c++
httplib::MultipartFormDataItems items = {
+  { "text1", "text default", "", "" },
+  { "text2", "aωb", "", "" },
+  { "file1", "h\\ne\\n\\nl\\nl\\no\\n", "hello.txt", "text/plain" },
+  { "file2", "{\\n  \\"world\\", true\\n}\\n", "world.json", "application/json" },
+  { "file3", "", "", "application/octet-stream" },
+};
+
+auto res = cli.Post("/multipart", items);

PUT

c++
res = cli.Put("/resource/foo", "text", "text/plain");

DELETE

c++
res = cli.Delete("/resource/foo");

OPTIONS

c++
res = cli.Options("*");
+res = cli.Options("/resource/foo");

Timeout

c++
cli.set_connection_timeout(0, 300000); // 300 milliseconds
+cli.set_read_timeout(5, 0); // 5 seconds
+cli.set_write_timeout(5, 0); // 5 seconds

Receive content with Content receiver

c++
std::string body;
+
+auto res = cli.Get("/large-data",
+  [&](const char *data, size_t data_length) {
+    body.append(data, data_length);
+    return true;
+  });
cpp
std::string body;
+
+auto res = cli.Get(
+  "/stream", Headers(),
+  [&](const Response &response) {
+    EXPECT_EQ(200, response.status);
+    return true; // return 'false' if you want to cancel the request.
+  },
+  [&](const char *data, size_t data_length) {
+    body.append(data, data_length);
+    return true; // return 'false' if you want to cancel the request.
+  });

Send content with Content provider

cpp
std::string body = ...;
+
+auto res = cli_.Post(
+  "/stream", body.size(),
+  [](size_t offset, size_t length, DataSink &sink) {
+    sink.write(body.data() + offset, length);
+    return true; // return 'false' if you want to cancel the request.
+  },
+  "text/plain");

With Progress Callback

cpp
httplib::Client client(url, port);
+
+// prints: 0 / 000 bytes => 50% complete
+auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
+  printf("%lld / %lld bytes => %d%% complete\\n",
+    len, total,
+    (int)(len*100/total));
+  return true; // return 'false' if you want to cancel the request.
+}
+);

progress

Authentication

cpp
// Basic Authentication
+cli.set_basic_auth("user", "pass");
+
+// Digest Authentication
+cli.set_digest_auth("user", "pass");
+
+// Bearer Token Authentication
+cli.set_bearer_token_auth("token");

NOTE: OpenSSL is required for Digest Authentication.

Proxy server support

cpp
cli.set_proxy("host", port);
+
+// Basic Authentication
+cli.set_proxy_basic_auth("user", "pass");
+
+// Digest Authentication
+cli.set_proxy_digest_auth("user", "pass");
+
+// Bearer Token Authentication
+cli.set_proxy_bearer_token_auth("pass");

NOTE: OpenSSL is required for Digest Authentication.

Range

cpp
httplib::Client cli("httpbin.org");
+
+auto res = cli.Get("/range/32", {
+  httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
+});
+// res->status should be 206.
+// res->body should be "bcdefghijk".
cpp
httplib::make_range_header({{1, 10}, {20, -1}})      // 'Range: bytes=1-10, 20-'
+httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599'
+httplib::make_range_header({{0, 0}, {-1, 1}})        // 'Range: bytes=0-0, -1'

Keep-Alive connection

cpp
httplib::Client cli("localhost", 1234);
+
+cli.Get("/hello");         // with "Connection: close"
+
+cli.set_keep_alive(true);
+cli.Get("/world");
+
+cli.set_keep_alive(false);
+cli.Get("/last-request");  // with "Connection: close"

Redirect

cpp
httplib::Client cli("yahoo.com");
+
+auto res = cli.Get("/");
+res->status; // 301
+
+cli.set_follow_location(true);
+res = cli.Get("/");
+res->status; // 200

Use a specitic network interface

NOTE: This feature is not available on Windows, yet.

cpp
cli.set_interface("eth0"); // Interface name, IP address or host name

OpenSSL Support

SSL support is available with CPPHTTPLIB_OPENSSL_SUPPORT. libssl and libcrypto should be linked.

NOTE: cpp-httplib currently supports only version 1.1.1.

c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
+
+SSLServer svr("./cert.pem", "./key.pem");
+
+SSLClient cli("localhost", 8080);
+cli.set_ca_cert_path("./ca-bundle.crt");
+cli.enable_server_certificate_verification(true);

Compression

The server can applie compression to the following MIME type contents:

  • all text types except text/event-stream
  • image/svg+xml
  • application/javascript
  • application/json
  • application/xml
  • application/xhtml+xml

Zlib Support

'gzip' compression is available with CPPHTTPLIB_ZLIB_SUPPORT. libz should be linked.

Brotli Support

Brotli compression is available with CPPHTTPLIB_BROTLI_SUPPORT. Necessary libraries should be linked. Please see https://github.com/google/brotli for more detail.

Compress request body on client

c++
cli.set_compress(true);
+res = cli.Post("/resource/foo", "...", "text/plain");

Compress response body on client

c++
cli.set_decompress(false);
+res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
+res->body; // Compressed data

Split httplib.h into .h and .cc

bash
> python3 split.py
+> ls out
+httplib.h  httplib.cc

NOTE

g++

g++ 4.8 and below cannot build this library since <regex> in the versions are broken.

Windows

Include httplib.h before Windows.h or include Windows.h by defining WIN32_LEAN_AND_MEAN beforehand.

cpp
#include <httplib.h>
+#include <Windows.h>
cpp
#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <httplib.h>

Note: Cygwin on Windows is not supported.

License

MIT license (© 2020 Yuji Hirose)

Special Thanks To

These folks made great contributions to polish this library to totally another level from a simple toy!

`,123)]))}const y=i(h,[["render",l]]);export{g as __pageData,y as default}; diff --git a/assets/system_httplib_README.md.WIkDaVQZ.lean.js b/assets/system_httplib_README.md.WIkDaVQZ.lean.js new file mode 100644 index 00000000000..1fb5081d0e9 --- /dev/null +++ b/assets/system_httplib_README.md.WIkDaVQZ.lean.js @@ -0,0 +1,287 @@ +import{_ as i,c as a,a3 as t,o as n}from"./chunks/framework.DkhCEVKm.js";const g=JSON.parse('{"title":"cpp-httplib","description":"","frontmatter":{},"headers":[],"relativePath":"system/httplib/README.md","filePath":"system/httplib/README.md","lastUpdated":1731340314000}'),h={name:"system/httplib/README.md"};function l(k,s,p,e,E,r){return n(),a("div",null,s[0]||(s[0]=[t(`

cpp-httplib

A C++11 single-file header-only cross platform HTTP/HTTPS library.

It's extremely easy to setup. Just include httplib.h file in your code!

Server Example

c++
#include <httplib.h>
+
+int main(void)
+{
+  using namespace httplib;
+
+  Server svr;
+
+  svr.Get("/hi", [](const Request& req, Response& res) {
+    res.set_content("Hello World!", "text/plain");
+  });
+
+  svr.Get(R"(/numbers/(\\d+))", [&](const Request& req, Response& res) {
+    auto numbers = req.matches[1];
+    res.set_content(numbers, "text/plain");
+  });
+
+  svr.Get("/body-header-param", [](const Request& req, Response& res) {
+    if (req.has_header("Content-Length")) {
+      auto val = req.get_header_value("Content-Length");
+    }
+    if (req.has_param("key")) {
+      auto val = req.get_param_value("key");
+    }
+    res.set_content(req.body, "text/plain");
+  });
+
+  svr.Get("/stop", [&](const Request& req, Response& res) {
+    svr.stop();
+  });
+
+  svr.listen("localhost", 1234);
+}

Post, Put, Delete and Options methods are also supported.

Bind a socket to multiple interfaces and any available port

cpp
int port = svr.bind_to_any_port("0.0.0.0");
+svr.listen_after_bind();

Static File Server

cpp
// Mount / to ./www directory
+auto ret = svr.set_mount_point("/", "./www");
+if (!ret) {
+  // The specified base directory doesn't exist...
+}
+
+// Mount /public to ./www directory
+ret = svr.set_mount_point("/public", "./www");
+
+// Mount /public to ./www1 and ./www2 directories
+ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
+ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
+
+// Remove mount /
+ret = svr.remove_mount_point("/");
+
+// Remove mount /public
+ret = svr.remove_mount_point("/public");
cpp
// User defined file extension and MIME type mappings
+svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
+svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
+svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");

The followings are built-in mappings:

ExtensionMIME Type
txttext/plain
html, htmtext/html
csstext/css
jpeg, jpgimage/jpg
pngimage/png
gifimage/gif
svgimage/svg+xml
icoimage/x-icon
jsonapplication/json
pdfapplication/pdf
jsapplication/javascript
wasmapplication/wasm
xmlapplication/xml
xhtmlapplication/xhtml+xml

NOTE: These the static file server methods are not thread safe.

Logging

cpp
svr.set_logger([](const auto& req, const auto& res) {
+  your_logger(req, res);
+});

Error handler

cpp
svr.set_error_handler([](const auto& req, auto& res) {
+  auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
+  char buf[BUFSIZ];
+  snprintf(buf, sizeof(buf), fmt, res.status);
+  res.set_content(buf, "text/html");
+});

'multipart/form-data' POST data

cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
+  auto size = req.files.size();
+  auto ret = req.has_file("name1");
+  const auto& file = req.get_file_value("name1");
+  // file.filename;
+  // file.content_type;
+  // file.content;
+});

Receive content with Content receiver

cpp
svr.Post("/content_receiver",
+  [&](const Request &req, Response &res, const ContentReader &content_reader) {
+    if (req.is_multipart_form_data()) {
+      MultipartFormDataItems files;
+      content_reader(
+        [&](const MultipartFormData &file) {
+          files.push_back(file);
+          return true;
+        },
+        [&](const char *data, size_t data_length) {
+          files.back().content.append(data, data_length);
+          return true;
+        });
+    } else {
+      std::string body;
+      content_reader([&](const char *data, size_t data_length) {
+        body.append(data, data_length);
+        return true;
+      });
+      res.set_content(body, "text/plain");
+    }
+  });

Send content with Content provider

cpp
const size_t DATA_CHUNK_SIZE = 4;
+
+svr.Get("/stream", [&](const Request &req, Response &res) {
+  auto data = new std::string("abcdefg");
+
+  res.set_content_provider(
+    data->size(), // Content length
+    "text/plain", // Content type
+    [data](size_t offset, size_t length, DataSink &sink) {
+      const auto &d = *data;
+      sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
+      return true; // return 'false' if you want to cancel the process.
+    },
+    [data] { delete data; });
+});

Without content length:

cpp
svr.Get("/stream", [&](const Request &req, Response &res) {
+  res.set_content_provider(
+    "text/plain", // Content type
+    [&](size_t offset, size_t length, DataSink &sink) {
+      if (/* there is still data */) {
+        std::vector<char> data;
+        // prepare data...
+        sink.write(data.data(), data.size());
+      } else {
+        done(); // No more data
+      }
+      return true; // return 'false' if you want to cancel the process.
+    });
+});

Chunked transfer encoding

cpp
svr.Get("/chunked", [&](const Request& req, Response& res) {
+  res.set_chunked_content_provider(
+    [](size_t offset, DataSink &sink) {
+      sink.write("123", 3);
+      sink.write("345", 3);
+      sink.write("789", 3);
+      sink.done(); // No more data
+      return true; // return 'false' if you want to cancel the process.
+    }
+  );
+});

'Expect: 100-continue' handler

As default, the server sends 100 Continue response for Expect: 100-continue header.

cpp
// Send a '417 Expectation Failed' response.
+svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
+  return 417;
+});
cpp
// Send a final status without reading the message body.
+svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
+  return res.status = 401;
+});

Keep-Alive connection

cpp
svr.set_keep_alive_max_count(2); // Default is 5

Timeout

c++
svr.set_read_timeout(5, 0); // 5 seconds
+svr.set_write_timeout(5, 0); // 5 seconds
+svr.set_idle_interval(0, 100000); // 100 milliseconds

Set maximum payload length for reading request body

c++
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB

Server-Sent Events

Please see Server example and Client example.

Default thread pool support

ThreadPool is used as a default task queue, and the default thread count is set to value from std::thread::hardware_concurrency().

You can change the thread count by setting CPPHTTPLIB_THREAD_POOL_COUNT.

Override the default thread pool with yours

cpp
class YourThreadPoolTaskQueue : public TaskQueue {
+public:
+  YourThreadPoolTaskQueue(size_t n) {
+    pool_.start_with_thread_count(n);
+  }
+
+  virtual void enqueue(std::function<void()> fn) override {
+    pool_.enqueue(fn);
+  }
+
+  virtual void shutdown() override {
+    pool_.shutdown_gracefully();
+  }
+
+private:
+  YourThreadPool pool_;
+};
+
+svr.new_task_queue = [] {
+  return new YourThreadPoolTaskQueue(12);
+};

Client Example

c++
#include <httplib.h>
+#include <iostream>
+
+int main(void)
+{
+  httplib::Client cli("localhost", 1234);
+
+  if (auto res = cli.Get("/hi")) {
+    if (res->status == 200) {
+      std::cout << res->body << std::endl;
+    }
+  } else {
+    auto err = res.error();
+    ...
+  }
+}

NOTE: Constructor with scheme-host-port string is now supported!

c++
httplib::Client cli("localhost");
+httplib::Client cli("localhost:8080");
+httplib::Client cli("http://localhost");
+httplib::Client cli("http://localhost:8080");
+httplib::Client cli("https://localhost");

GET with HTTP headers

c++
httplib::Headers headers = {
+  { "Accept-Encoding", "gzip, deflate" }
+};
+auto res = cli.Get("/hi", headers);

or

c++
cli.set_default_headers({
+  { "Accept-Encoding", "gzip, deflate" }
+});
+auto res = cli.Get("/hi");

POST

c++
res = cli.Post("/post", "text", "text/plain");
+res = cli.Post("/person", "name=john1&note=coder", "application/x-www-form-urlencoded");

POST with parameters

c++
httplib::Params params;
+params.emplace("name", "john");
+params.emplace("note", "coder");
+
+auto res = cli.Post("/post", params);

or

c++
httplib::Params params{
+  { "name", "john" },
+  { "note", "coder" }
+};
+
+auto res = cli.Post("/post", params);

POST with Multipart Form Data

c++
httplib::MultipartFormDataItems items = {
+  { "text1", "text default", "", "" },
+  { "text2", "aωb", "", "" },
+  { "file1", "h\\ne\\n\\nl\\nl\\no\\n", "hello.txt", "text/plain" },
+  { "file2", "{\\n  \\"world\\", true\\n}\\n", "world.json", "application/json" },
+  { "file3", "", "", "application/octet-stream" },
+};
+
+auto res = cli.Post("/multipart", items);

PUT

c++
res = cli.Put("/resource/foo", "text", "text/plain");

DELETE

c++
res = cli.Delete("/resource/foo");

OPTIONS

c++
res = cli.Options("*");
+res = cli.Options("/resource/foo");

Timeout

c++
cli.set_connection_timeout(0, 300000); // 300 milliseconds
+cli.set_read_timeout(5, 0); // 5 seconds
+cli.set_write_timeout(5, 0); // 5 seconds

Receive content with Content receiver

c++
std::string body;
+
+auto res = cli.Get("/large-data",
+  [&](const char *data, size_t data_length) {
+    body.append(data, data_length);
+    return true;
+  });
cpp
std::string body;
+
+auto res = cli.Get(
+  "/stream", Headers(),
+  [&](const Response &response) {
+    EXPECT_EQ(200, response.status);
+    return true; // return 'false' if you want to cancel the request.
+  },
+  [&](const char *data, size_t data_length) {
+    body.append(data, data_length);
+    return true; // return 'false' if you want to cancel the request.
+  });

Send content with Content provider

cpp
std::string body = ...;
+
+auto res = cli_.Post(
+  "/stream", body.size(),
+  [](size_t offset, size_t length, DataSink &sink) {
+    sink.write(body.data() + offset, length);
+    return true; // return 'false' if you want to cancel the request.
+  },
+  "text/plain");

With Progress Callback

cpp
httplib::Client client(url, port);
+
+// prints: 0 / 000 bytes => 50% complete
+auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
+  printf("%lld / %lld bytes => %d%% complete\\n",
+    len, total,
+    (int)(len*100/total));
+  return true; // return 'false' if you want to cancel the request.
+}
+);

progress

Authentication

cpp
// Basic Authentication
+cli.set_basic_auth("user", "pass");
+
+// Digest Authentication
+cli.set_digest_auth("user", "pass");
+
+// Bearer Token Authentication
+cli.set_bearer_token_auth("token");

NOTE: OpenSSL is required for Digest Authentication.

Proxy server support

cpp
cli.set_proxy("host", port);
+
+// Basic Authentication
+cli.set_proxy_basic_auth("user", "pass");
+
+// Digest Authentication
+cli.set_proxy_digest_auth("user", "pass");
+
+// Bearer Token Authentication
+cli.set_proxy_bearer_token_auth("pass");

NOTE: OpenSSL is required for Digest Authentication.

Range

cpp
httplib::Client cli("httpbin.org");
+
+auto res = cli.Get("/range/32", {
+  httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
+});
+// res->status should be 206.
+// res->body should be "bcdefghijk".
cpp
httplib::make_range_header({{1, 10}, {20, -1}})      // 'Range: bytes=1-10, 20-'
+httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599'
+httplib::make_range_header({{0, 0}, {-1, 1}})        // 'Range: bytes=0-0, -1'

Keep-Alive connection

cpp
httplib::Client cli("localhost", 1234);
+
+cli.Get("/hello");         // with "Connection: close"
+
+cli.set_keep_alive(true);
+cli.Get("/world");
+
+cli.set_keep_alive(false);
+cli.Get("/last-request");  // with "Connection: close"

Redirect

cpp
httplib::Client cli("yahoo.com");
+
+auto res = cli.Get("/");
+res->status; // 301
+
+cli.set_follow_location(true);
+res = cli.Get("/");
+res->status; // 200

Use a specitic network interface

NOTE: This feature is not available on Windows, yet.

cpp
cli.set_interface("eth0"); // Interface name, IP address or host name

OpenSSL Support

SSL support is available with CPPHTTPLIB_OPENSSL_SUPPORT. libssl and libcrypto should be linked.

NOTE: cpp-httplib currently supports only version 1.1.1.

c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
+
+SSLServer svr("./cert.pem", "./key.pem");
+
+SSLClient cli("localhost", 8080);
+cli.set_ca_cert_path("./ca-bundle.crt");
+cli.enable_server_certificate_verification(true);

Compression

The server can applie compression to the following MIME type contents:

  • all text types except text/event-stream
  • image/svg+xml
  • application/javascript
  • application/json
  • application/xml
  • application/xhtml+xml

Zlib Support

'gzip' compression is available with CPPHTTPLIB_ZLIB_SUPPORT. libz should be linked.

Brotli Support

Brotli compression is available with CPPHTTPLIB_BROTLI_SUPPORT. Necessary libraries should be linked. Please see https://github.com/google/brotli for more detail.

Compress request body on client

c++
cli.set_compress(true);
+res = cli.Post("/resource/foo", "...", "text/plain");

Compress response body on client

c++
cli.set_decompress(false);
+res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
+res->body; // Compressed data

Split httplib.h into .h and .cc

bash
> python3 split.py
+> ls out
+httplib.h  httplib.cc

NOTE

g++

g++ 4.8 and below cannot build this library since <regex> in the versions are broken.

Windows

Include httplib.h before Windows.h or include Windows.h by defining WIN32_LEAN_AND_MEAN beforehand.

cpp
#include <httplib.h>
+#include <Windows.h>
cpp
#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <httplib.h>

Note: Cygwin on Windows is not supported.

License

MIT license (© 2020 Yuji Hirose)

Special Thanks To

These folks made great contributions to polish this library to totally another level from a simple toy!

`,123)]))}const y=i(h,[["render",l]]);export{g as __pageData,y as default}; diff --git a/assets/system_masking_include_readme.md.CNC1sarR.js b/assets/system_masking_include_readme.md.CNC1sarR.js new file mode 100644 index 00000000000..11bf837832d --- /dev/null +++ b/assets/system_masking_include_readme.md.CNC1sarR.js @@ -0,0 +1,93 @@ +import{_ as t,c as a,a3 as n,o as s}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Data Masking Framework","description":"","frontmatter":{},"headers":[],"relativePath":"system/masking/include/readme.md","filePath":"system/masking/include/readme.md","lastUpdated":1731340314000}'),o={name:"system/masking/include/readme.md"};function i(l,e,r,p,c,d){return s(),a("div",null,e[0]||(e[0]=[n(`

Data Masking Framework

This is a high level description of the data obfuscation framework defined in system/masking/include. The framework includes a platform component, the engine, that exposes obfuscation logic supplied by dynamically loaded plugin libraries. The framework can be used to obfuscate sensitive data, such as passwords and PII. Possible use cases including preventing trace output from revealing sensitive values and partially masking values in situations where a user needs to see "enough" of a value to confirm its correctness without seeing all of the value (e.g., when a user is asked to confirm the last four digits of an account number).

FileContents
datamasking.hDeclares the core framework interfaces. Most are used by the engine and plugins.
datamaskingengine.hppDefines the engine component from the core engine interface.
datamaskingplugin.hppDefines a set of classes derived from the core plugin interfaces that can be used to provide rudimentary obfuscation. It is expected that the some or all classes will be subclassed, if not replaced outright, in new plugins.
datamaskingshared.hppDefines utilities shared by engine and plugin implementations.

Glossary

Domain

A domain is a representation of the obfuscation requirements applicable to a set of data.

Consider that the data used to represent individuals likely differs between countries. With different data, requirements for obfuscation may reasonably be expected to vary. Assuming that requirements do change between countries, each country's requirements could logically constitute a separate domain. The capacity to define multiple domains does not create a requirement to do so.

Requirements can change over time. To support this, a domain can be seen as a collection of requirement snapshots where each snapshot defines the complete set of requirements for the domain at a point in time. Snapshots are referenced by unique version numbers, which should be sequential starting at 1.

Obfuscation is always applied based on a single snapshot of a domain's requirements.

Domains are represented in the framework interface as text identifiers. Each distinct domain is identified by at least one unique identifier.

Masker

A masker is a provider of obfuscation for a single snapshot of a domain's requirements. There are three masking operations defined in this framework. Each instance decides which of the three it will support, and how it will support them. The three operations are:

  • maskValue obfuscates individual values based on snapshot-defined meanings. For example, a value identified as a password might require complete obfuscation anywhere it appears, or an account number may require complete obfuscation in some cases and partial obfuscation in others.
  • maskContent obfuscates a variable number of values based on context provided by surrounding text. For example, the value of an HTTP authentication header or the text between <Password> and </Password> might require obfuscation. This operation can apply to both structured and unstructured text content.
  • maskMarkupValue obfuscates individual values based on their locationa within an in-memory representation of a structured document, such as XML or JSON. For example, the value of element Password might require obfuscation unconditionally, while the value of element Value might require obfuscation only if a sibling element named Name exists with value password. This operation relies on the caller's ability to supply context parsed from structured content.

A masker may be either stateless or stateful. With a stateless masker, identical input will produce identical output for every requested operation. A stateful masker, however, enables its user to affect operation outputs (i.e., identical input may not produce identical output for each operation).

Maskers are represented in the framework interface using IDataMasker, with IDataMaskerInspector providing access to less frequently used information about the domain.

Profile

A profile is a stateless masker. Each instance defines the requirements of one or more snapshots of a single domain.

Snapshots are versioned. Each profile declares a minimum, maximum, and default version. Masker operations apply to the default version. Other declared versions may be accessed using a stateful context, which the profile can create on demand.

Each instance must support at least one version of a domain's requirements. Whether an instance supports more than one version depends on the implementation and on user preference. A domain can be viewed as a collection of one or more profiles where each profile defines a unique set of requirement snapshots applicable to the same underlying data.

Refer to IDataMaskingProfile (extending IDataMasker) and IDataMaskingProfileInspector (extending IDataMaskerInspector) for additional information.

Context

A context is a stateful masker. Instantiated by and tightly coupled to a profile, it provides some user control over how masking operations are completed.

  • For a profile supporting multiple versions, the requirements of a non-default version may be applied.
  • Custom properties, defined by a profile, may be managed.
    • valuetype-set is a pre-defined property used to select the group of value types that may be masked by any operation.
    • rule-set is a pre-defined property used to select the group of rules that will be applied for maskContent requests.
    • Profile implementations may define additional properties as needed. For example, one might define mask-pattern to override the default obfuscation pattern to avoid replicating (and complicating) configurations just to change the appearance of obfuscated data.
  • Trace output produced by operation requests, including errors and warnings encountered during request processing, can be controlled per context. This does not affect the operation output, per-se, but provides compatibility with transactional trace output control.

Refer to IDataMaskingProfileContext (extending IDataMasker) and IDataMaskingProfileContextInspector (extending IDataMaskerInspector) for additional information.

Value Type

Each snapshot defines one or more value types. A value type is a representation of the requirements pertaining to a particular concept of a domain datum. Requirements include:

  • instructions for identifying occurrences based on contextual clues found in a body of text; and
  • instructions for applying obfuscation to an identified value.

A Social Security Number, or SSN, is a U.S.-centric datum that requires obfuscation and for which a value type may be defined. Element names associated with an SSN may include, but are not limited to, SSN and SOCS; the value type is expected to identify all such names used within the domain. SSN occurrences are frequently partially masked, with common formats being to mask only the first four or the last five digits of the nine digit number; the value type defines which formats are available besides the default of masking all characters.

Value types are represented in the framework using IDataMaskingProfileValueType. They are accessed through the IDataMaskerInspector interface.

Mask Style

A mask style describes how obfuscation is applied to a value. It is always defined in the context of a value type, and a value type may define multiple.

A value type is not required to define any mask styles. If none are defined, all value characters are obfuscated. If the requested mask style is not defined, the default obfuscation occurs; the value type will not attempt to guess which of the defined styles is appropriate.

Mask styles are represented in the framework using IDataMaskingProfileMaskStyle. They are accessed through the IDataMaskingProfileValueType interface.

Rule

A rule contains the information necessary to locate at least one occurrence of a value type datum to be obfuscated. It is always defined in the context of a value type, and a value type may define multiple.

maskValue requests do not use rules. maskContent requests rely on rules for locating affected values, with the relationship between a profile and its rules an implementation detail. maskMarkupValue requests, when implemented, may also use rules or may take an entirely different approach.

Rules are represented in the framework as an abastract concept that cannot be inspected individually. Inspection may be used to establish the presence of rules, but not to examine individual instances.

Plugin

The combination of a shared library and entry point function describes a plugin. The input to a plugin is a property tree describing one or more profiles. The output of an entry point function is an iterator of profiles. The profiles created by the function may all be associated with the same domain, but this is not required.

Plugin results are represented in the framework using IDataMaskingProfileIterator.

Engine

An engine is the platform's interface to obfuscation. It loads domains by loading one or more plugins. Plugins yield profiles, from which domains are inferred.

Once configured with at least one domain, a caller can obtain obfuscation in multiple ways:

  1. As an instance of a stateless masker, an engine can provide obfuscation. The default version of the default profile of the default domain will be used, and no custom context properties can be used. This is the simplest integration, appropriate for use when the host process implicitly trusts that the default configuration is sufficient.
  2. An engine may be used to obtain a stateless profile for a specific domain. If no version is requested, the default profile of the requested domain will be used. If a version is specified, the domain profile supporting the requested version is used. This usage supports host processes that are aware of domains and need to use specific instances.
  3. An engine may be used to obtain a context tied to a specific version of a domain. The default domain may be requested with an empty string. The default version may be requested by passing 0. With a context, a caller may manipulate further the environment for obfuscation requests.

Engines are represented in the framework using IDataMaskingEngine (extending IDataMasker) and IDataMaskingEngineInspector for additional information.

Environment

This section assumes a context is in use. Absent a context, only the default state of the default version of a profile can be used.

The framework reserves multiple custom property names, which are described in subsequent subsections. It also allows implementations to define additional properties using any non-reserved name.

Suppose an implementation defines a property to override the default obfuscation pattern. Let's call this property default-pattern. A caller could interrogate a masker to know if default-pattern is accepted. If accepted, setProperty("default-patterns", "#") can be used to register an override.

All custom properties, whether defined in the framework or in third party libraries, are managed using a generic context interface. This interface includes:

bool hasProperties() cosnt
+bool hasProperty(const char* name) const
+const char* queryProperty(const char* name) const
+bool setProperty(const char* name, const char* value)
+bool removeProperty(const char* name)

Value Type Sets

bool setProperty(const char* name, const char* value);

Overview

The abstraction includes the concept of sets of related value types. All value types should be assigned membership in at least one set. One set should be selected by default. The custom context property valuetype-set is used to select a different set.

The rationale for this is an expectation that certain data always requires obfuscation. A password, for example, would never not require obfuscation. Other data may only require obfuscacation in certain situations, such as when required by individual customer agreements. Callers should not be required to complete additional steps to act on data that must always be obfuscated, and should not need to know which data falls in which category.

The set name "*" is reserved to select all value types regardless of their defined set membership. This mechanism is intended to assist with compatibility checks, and should be used with care in other situations.

Runtime Compatibility

Use acceptsProperty to determine whether a snapshot recognizes a custom property name. Use usesProperty to learn if the snapshot includes references to the custom property name. Acceptance implies awareness of the set (and what it represents) even if selecting it will not change the outcome of any masking request. Usage implies that selecting the set will change the outcome of at least one masking operation.

The property valuetype-set is used to select a named set. A check of this property addresses whether the concept of a set is accepted or used by the snapshot.

For improved compatibility checks, implementations are encouraged to reserve additional property names that enable checks for individual set names. For each set name, foo, the included implementations report property valuetype-set:foo as used.

Implementation

The included implementations allow membership in either a default, unnamed set or in any number of named sets. Absent a contextual request for a named set, the unnamed set is selected by default. With a contextual set request, the members of the named set are selected in addition to members of the unnamed set. Members of the unnamed set are never not selected.

Unnamed set membership cannot be defined explicitly. Because this set is always selected, assigning a value type to both a named and the unnamed set is redundant - the type will be selected whether the named set is requested or not.

Example 1a: default value type

profile:
+  valueType:
+    - name: type1

Value type type1 belongs to the default, unnamed value type set. Property valuetype-set is accepted, but not used; an attempt to use it will change no results.

Example 1b: sample set memberships

profile:
+  valueType:
+    - name: type1
+    - name: type2
+      memberOf:
+        - name: set1
+        - name: set2
+    - name: type3
+      memberOf:
+        - name: set2
+        - name: set3
+    - name: type4
+      memberOf:
+        - name: set1
+    - name: type5
+      memberOf:
+        - name: set3

Extending the previous example, five types and four sets are in use. Property valuetype-set is both accepted and used, as it can now impact results. Properties valuetype-set:set1, valuetype-set:set2 and valuetype-set:set3 are also both accepted and used, but are intended only for use with compatibility checks.

This table shows which types are selected for each requested set:

unnamedset1set2set3*
type1YYYYY
type2NYYNY
type3NNYYY
type4NYNNY
type5NNNYY

Example 1c: sample accepted sets

profile:
+  valueType:
+    - name: type1
+    - name: type2
+      memberOf:
+        - name: set1
+        - name: set2
+    - name: type3
+      memberOf:
+        - name: set2
+        - name: set3
+    - name: type4
+      memberOf:
+        - name: set1
+    - name: type5
+      memberOf:
+        - name: set3
+  property:
+    - name: 'valuetype-set:set4'
+    - name: 'valuetype-set:set5'

Continuing the previous example, the profile has declared acceptance of two additional sets using properties valuetype-set:set4 and valuetype-set:set5. Selection of these sets will not change results, but a caller might accept acceptance of a set as sufficient to establish compatibility.

Rule Sets

bool setProperty(const char* name, const char* value);

Overview

The abstraction includes the concept of sets of related rules. All rules should be assigned membership in at least one set. One set should be selected by default. The custom context property rule-set is used to select a different set.

The rationale for this is similar to yet different than that of value type sets. Instead of one set of rules that are always selected, backward compatibility with a proprietary legacy implementation requires that the default set be replaced by an alternate collection.

The set name "*" is reserved to select all rules regardless of their defined set membership. This does not override constraints imposed by the current value type set. This mechanism is intended to assist with compatibility checks, and should be used with care in other situations.

Runtime Compatibility

Use acceptsProperty to determine whether a snapshot recognizes a custom property name. Use usesProperty to learn if the snapshot includes references to the custom property name. Acceptance implies awareness of the set (and what it represents) even if selecting it will select no rules. Usage implies that selecting the set will select at least one rule.

The property rule-set is used to select a named set. A check of this property addresses whether the concept of a set is accepted or used by the snapshot.

For improved compatibility checks, implementations are encouraged to reserve additional property names that enable checks for individual set names. For each set name, foo, the included implementations report property rule-set:foo as used.

Implementation

The included implementations allow membership in any number of named sets as well as a default, unnamed set. Absent a contextual request for a named set, the unnamed set is selected by default. With a contextual set request, only the members of the requested set are selected.

Unlike value type sets, the unnamed set is not always selected. Because of this difference, a rule may be assigned explicit membership in the unnamed set. This is optional for rules that belong only to the unnamed set and is required for rules meant to be selected as part of the unnamed set and one or more named sets.

Example 2: rule set memberships

profile:
+  valueType:
+    name: type1
+    rule:
+      - name: foo
+      - memberOf:
+          - name: ''
+      - memberOf:
+          - name: ''
+          - name: set1
+      - memberOf:
+          - name: set1
+  property:
+    name: 'rule-set:set2'

The rules described here are intentionally incomplete, showing only what is necessary for this example. Four rules are defined:

  1. The rule named foo implicitly belongs to the unnamed set.
  2. The second rule explicitly belongs to the unnamed set.
  3. The third rule explicitly belongs to the unnamed set and to set1.
  4. The fourth rule belongs to set1.

property rule-set is both accepted and used. Properties rule-set: and rule-set:set1 are both accepted and used, intended for use with compatibility checks. Property rule-set:set2 is accepted.

Operations

maskValue

bool maskValue(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length, bool conditionally) const;
+bool maskValueConditionally(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length) const;
+bool maskValueUnconditionally(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length) const;

Overview

Given a single domain datum, obfuscate the content "as needed". The framework anticipates two interpretations of "as needed", either conditional or unconditional:

  • Conditional means that values of a named type are only obfuscated if the named type is known by and selected in the snapshot. This usage assumes that the profile is the authoritative source for obfuscation requirements. The profile's consumer may ask for any value to be obfuscated, but the profile is not required to act.
  • Unconditional means that all value obfuscation requests will result in obfuscation. The named type is not required to be known by or selected in the snapshot. This usage assumes that the profile's consumer is the authoritative source for obfuscation requirements. If a consumer asks for a value to be obfuscated, the profile must act.

Each plugin will define its own interpretation of "as needed". The API distinction between conditional and unconditional is a hint intended to guide implementations capable of both, and should be ignored by implementations that are not.

The buffer, offset, and length parameters define what is assumed to be a single domain datum. That is, every character in the given character range is subject to obfuscation.

The valueType parameter determines if obfuscation is required, with conditionally controlling whether an unrecognized valueType is ignored (true) or forced to obfuscate (false).

The maskStyle parameter may be used to affect the nature of obfuscation to be applied. If the parameter names a defined mask style, that obfuscation format is applied. If the parameter does not name a define mask style, the value type's default obfuscation format is applied.

Runtime Compatibility

The canMaskValue method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The hasValueType method of IDataMaskerInspector can be used to check if a given name is selected in the snapshot. The reserved name "*" may be used to detect unconditional obfuscation support when available in the snapshot. Names defined yet unselected in the snapshot are not directly detectable, but can be detected by comparing results of using multiple context configurations.

The name "*" is reserved by the abstraction for compatibility checks.

To confirm the availability of a specific mask style first requires confirmation of the value type to which the mask style is related. Use queryValueType to obtain the defined instance and, from the result, call queryMaskStyle to confirm the existence of the mask stye. For each of these, corresponding get... methods are defined to obtain new references to the objects. The methods are declared by IDataMaskerInspector.

Implementations

The included implementations are inherently conditional. Obfuscation depends on matching valueType with a selected value type instance in the snapshot, where an instance is selected if it belongs to the currently selected value type set.

Unconditional obfuscation is enabled in profiles that include a value type instance named "*". If an instance with this name is selected by the currently selected value type set, all values for undefined value types will be obfuscated. Values for defined but unselected value types will not be obfuscated.

  • A value of type "foo" will be obfuscated when type "foo" is selected by the currently selected value type set.
  • A value of type "foo" will be obfuscated when type "foo" is undefined and type "*" is selected by the currently selected value type set.
  • A value of type "foo" will not be obfuscated when type "foo" is defined but unselected by the currently selected value type set.

Example 3a: conditional obfuscation

valueType:
+  - name: type1
+  - name: type2
+    memberOf:
+      - name: set1

This snippet declares two value types, with two total sets. The table shows which values will be conditionally obfuscated based on a the combination of valueType and the currently selected value type set.

valueTypeSelected SetObfuscated
type1N/AYes
type1set1Yes
type2N/ANo
type2set1Yes
typeNN/ANo
typeNset1No

"typeN" in the table represents any named type, excluding the reserved "*", not explicitly defined in the profile.

Example 3b: unconditional obfuscation

valueType:
+  - name: type1
+  - name: type2
+    memberOf:
+      - name: set1
+  - name: *

Extending the previous example with a third value type, values of unknown type are now obfuscated. Values of known but unselected type remain unobfuscated.

valueTypeSelected SetObfuscated
type1N/AYes
type1set1Yes
type2N/ANo
type2set1Yes
typeNN/AYes
typeNset1Yes

The name reserved by the abstraction to detect support for unconditional obfuscation is the same name reserved by the implementation to define that support.

"typeN" in the table represents any named type, excluding the reserved "*", not explicitly defined in the profile.

maskContent

bool maskContent(const char* contentType, char* buffer, size_t offset, size_t length) const;

Overview

The buffer, offset, and length parameters define what is assumed to be a blob of content that may contain zero or more occurrences of domain data. The blob is expected to contain sufficient context allowing a snapshot's rules to locate any included occurrences.

The context parameter optionally influences which rules are selected in a snapshot, while the contentType parameter offers a hint as to the blob's data format. Each snapshot rule may be assigned a format to which it applies. Operation requests may be optimized by limiting the number of selected rules applied by describing the format. Selected rules not assigned a format will be applied in all requests.

There is no equivalent to the unconditional mode offered by maskValue. Content obfuscation is always conditional on matching rules.

Runtime Compatibility

The canMaskContent method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The hasRule method of IDataMaskerInspector indicates if at least one rule is selected in the snapshot for a given content type. Rules not associated with any content type may be interrogated using an empty string.

It is not possible to discern any information about the selected rules, such as their associated value types or the cues used to apply them in a buffer.

Implementations

The groundwork exists for two types of rules, serial and parallel. Serial rules, as the name implies, are evaluated sequentially. Parallel rules, on the other hand, are evaluated concurrently. Concurrent evaluation is expected to be more efficient than sequential, but no concurrent implementation is provided.

The included sequential implementation should be viewed as a starting point. Each rule defines a start token and an optional end token. For each occurrence of the start token in the blob, a corresponding search for an end token (newline if omitted), is performed. When both parts are found, the content between is obfuscated using the associated value type's default mask style.

Traversing a potentially large blob of text once per rule is inefficient. A concurrent implementation is in development to improve performance and capabilities. A domain originated using the original implementation and migrated to the new implementation illustrates one domain implemented by multiple plugins and, by extension, multiple profiles.

maskMarkupValue

bool maskMarkupValue(const char* element, const char* attribute, char* buffer, size_t offset, size_t length, IDataMaskingDependencyCallback& callback) const;

Overview

Where maskValue obfuscates individual values based on what the caller believes the value to be, and maskContent obfuscates any number of values it identifies based on cues found in surrounding text, maskMarkupValue obfuscastes individual values based on cues not provided to it.

Given a value and a relative location within a structure document, an implementation must decide whether obfuscation is required, may be required, or is not required. If required, it can obfuscate immediately. If not required, and can return immediately. If it might be required, the implementation must request the context it needs from the caller in order to make a final determination.

A request to mask the content of an element named Value might depend on the content of a sibling element named Name. If value of Value probably does not require obfuscation when the value of Name is city, but almost certainly does require obfuscation when the value of Name is password. When processing the request for Value, an implementation must ask for the value of a sibline named Name to decide whether or not to obfuscate the data.

There is no equivalent to the unconditional mode offered by maskValue. Markup value obfuscation presumes that the caller is traversing a structured document without awareness of which values require obfuscation. If the caller knows which values require obfuscation, it should use maskValue on those specific values.

Runtime Compatibility

    virtual bool getMarkupValueInfo(const char* element, const char* attribute, const char* buffer, size_t offset, size_t length, IDataMaskingDependencyCallback& callback, DataMaskingMarkupValueInfo& info) const = 0;

The canMaskMarkupValue method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The getMarkupValueInfo method of IDataMaskerInspector determines if the value of an element or attribute requires obfuscation. If required, the info parameter will describe on completion how to perform the obfuscation using maskValue instead of maskMarkupValue. This is done to optimize performance by eliminating the need to reevaluate rules to re-identify a match.

Assme a value type representing passwords is defined. If given an element value containing a URL with an embedded password, obfuscation is required. But most likely not for the entire value. In addition to identifying the value type associated with value, the offset and length of the embedded substring requiring obfuscation are supplied; use of maskValue with these three values and no mask style will apply the default obfuscation only to the embedded password. Alternatively, if a mask style is supplied, use of maskValue with this style and the original value offset and length should obfuscate only the embedded password.

A callback interface is required for all calls to getMarkupValueInfo. The interface is only used when additional document context is required to make a determination about the requested value. In these cases, the checks are proximity-dependent and cannot be performed as part of load-time compatibility checks, when no document is available.

Implementations

TBD

Load-time Compatibility

bool checkCompatibility(const IPTree& requirements) const;

Overview

Use of runtime compatibility checks may be unavoidable in some circumstances, but reliance on this in every script is inefficient. It may also devalue trace output by omitting messaging required to debug an issue, a problem that might only be found when said messaging is needed.

The requirements parameter represents a standardized set of runtime compatibility checks to be applied. By default, each check must pass to establish compatibility. Explicitly optional checks may be included to detect conditions the caller is prepared to work around.

This can test which values will or won't be affected by maskValue, and which obfuscation styles are available. It can test which rule sets will impact maskContent. It specifically excludes maskMarkupValue checks that depend on the proximity of one value to another.

Returning to the earlier SSN example, a caller might require that an SSN value type will be obfuscated. It might also want to use a particilar mask style, but may be prepared for its absence. A caller may prefer to mask the first four digits but may accept masking the last five instead, or may omit certain trace messages.

Implementation

compatibility:
+  - context:
+      - domain: optional text
+        version: optional number
+        property:
+          - name: required text
+            value: required text
+    accepts:
+      - name: required text
+        presence: one of "r", "o", or "p"
+    uses:
+      - name: required text
+        presence: one of "r", "o", or "p"
+    operation:
+      - name: one of "maskValue", "maskContent", or "maskMarkupValue"
+        presence: one of "r", "o", or "p"
+    valueType
+      - name: required text
+        presence: one of "r", "o", or "p"
+        maskStyle:
+          - name: required text
+            presence: one of "r", "o", or "p"
+        Set:
+          - name: required text
+            presence: one of "r", "o", or "p"
+    rule:
+      - contentType: required text
+        presence: one of "r", "o", or "p"

The requirements parameter of checkCompatibility must be either a compatibility element or the parent of a collection of compatibility elements. Each instance may contain at most one context element, three operation elements (one per operation), and a variable number of accepts, uses, valueType, or rule elements.

The context element describes the target of an evaluation. It may include a domain identifier, or assume the default domain. It may include a version number, or assume the default snapshot of the domain. It may specify any number of custom context properties to select various profile elements in the snapshot.

The accepts and uses elements identify custom context property names either accepted or used within a snapshot. Acceptance indicates some part of a snapshot has declared an understanding of the named property. Usage indicates the some part of a snapshot will react to the named value being set. Usage implies acceptance.

To improve compatibility check capabilities, a snapshot may synthesize properties that, if set, will have no effect. Specifically, a caller may be more interested to know if a particular set name (for either value type or rule set membership) is used than to know that an unidentified set name is used.

The Operations element identifies which operations must be supported. Its purpose is to enforce availability of an operation when no more explicit requirements are given (for example, one may require maskContent without also requiring the presence of rules for any given content type). When more explicit requirements are given, the attributes of this element are redundant. If all attributes are redundant, the element may be omitted.

The valueType element describes all optional and mandatory requirements for using maskValue with a single value type name and optional mask style name. Set membership requirements can also be evaluated, which is most useful when compatibility/context/property/@name is "*".

The rule element describes all optional and mandatory requirements for using maskContent with a single content type value. Unlike evaluable value type set membership, rule set membership requirements cannot be evaluated.

In all elements described as accepting @presence, the acceptable values are

  • r: the check is required to pass
  • o: the check is optional
  • p: the check is prohibited from passing
`,170)]))}const h=t(o,[["render",i]]);export{u as __pageData,h as default}; diff --git a/assets/system_masking_include_readme.md.CNC1sarR.lean.js b/assets/system_masking_include_readme.md.CNC1sarR.lean.js new file mode 100644 index 00000000000..11bf837832d --- /dev/null +++ b/assets/system_masking_include_readme.md.CNC1sarR.lean.js @@ -0,0 +1,93 @@ +import{_ as t,c as a,a3 as n,o as s}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"Data Masking Framework","description":"","frontmatter":{},"headers":[],"relativePath":"system/masking/include/readme.md","filePath":"system/masking/include/readme.md","lastUpdated":1731340314000}'),o={name:"system/masking/include/readme.md"};function i(l,e,r,p,c,d){return s(),a("div",null,e[0]||(e[0]=[n(`

Data Masking Framework

This is a high level description of the data obfuscation framework defined in system/masking/include. The framework includes a platform component, the engine, that exposes obfuscation logic supplied by dynamically loaded plugin libraries. The framework can be used to obfuscate sensitive data, such as passwords and PII. Possible use cases including preventing trace output from revealing sensitive values and partially masking values in situations where a user needs to see "enough" of a value to confirm its correctness without seeing all of the value (e.g., when a user is asked to confirm the last four digits of an account number).

FileContents
datamasking.hDeclares the core framework interfaces. Most are used by the engine and plugins.
datamaskingengine.hppDefines the engine component from the core engine interface.
datamaskingplugin.hppDefines a set of classes derived from the core plugin interfaces that can be used to provide rudimentary obfuscation. It is expected that the some or all classes will be subclassed, if not replaced outright, in new plugins.
datamaskingshared.hppDefines utilities shared by engine and plugin implementations.

Glossary

Domain

A domain is a representation of the obfuscation requirements applicable to a set of data.

Consider that the data used to represent individuals likely differs between countries. With different data, requirements for obfuscation may reasonably be expected to vary. Assuming that requirements do change between countries, each country's requirements could logically constitute a separate domain. The capacity to define multiple domains does not create a requirement to do so.

Requirements can change over time. To support this, a domain can be seen as a collection of requirement snapshots where each snapshot defines the complete set of requirements for the domain at a point in time. Snapshots are referenced by unique version numbers, which should be sequential starting at 1.

Obfuscation is always applied based on a single snapshot of a domain's requirements.

Domains are represented in the framework interface as text identifiers. Each distinct domain is identified by at least one unique identifier.

Masker

A masker is a provider of obfuscation for a single snapshot of a domain's requirements. There are three masking operations defined in this framework. Each instance decides which of the three it will support, and how it will support them. The three operations are:

  • maskValue obfuscates individual values based on snapshot-defined meanings. For example, a value identified as a password might require complete obfuscation anywhere it appears, or an account number may require complete obfuscation in some cases and partial obfuscation in others.
  • maskContent obfuscates a variable number of values based on context provided by surrounding text. For example, the value of an HTTP authentication header or the text between <Password> and </Password> might require obfuscation. This operation can apply to both structured and unstructured text content.
  • maskMarkupValue obfuscates individual values based on their locationa within an in-memory representation of a structured document, such as XML or JSON. For example, the value of element Password might require obfuscation unconditionally, while the value of element Value might require obfuscation only if a sibling element named Name exists with value password. This operation relies on the caller's ability to supply context parsed from structured content.

A masker may be either stateless or stateful. With a stateless masker, identical input will produce identical output for every requested operation. A stateful masker, however, enables its user to affect operation outputs (i.e., identical input may not produce identical output for each operation).

Maskers are represented in the framework interface using IDataMasker, with IDataMaskerInspector providing access to less frequently used information about the domain.

Profile

A profile is a stateless masker. Each instance defines the requirements of one or more snapshots of a single domain.

Snapshots are versioned. Each profile declares a minimum, maximum, and default version. Masker operations apply to the default version. Other declared versions may be accessed using a stateful context, which the profile can create on demand.

Each instance must support at least one version of a domain's requirements. Whether an instance supports more than one version depends on the implementation and on user preference. A domain can be viewed as a collection of one or more profiles where each profile defines a unique set of requirement snapshots applicable to the same underlying data.

Refer to IDataMaskingProfile (extending IDataMasker) and IDataMaskingProfileInspector (extending IDataMaskerInspector) for additional information.

Context

A context is a stateful masker. Instantiated by and tightly coupled to a profile, it provides some user control over how masking operations are completed.

  • For a profile supporting multiple versions, the requirements of a non-default version may be applied.
  • Custom properties, defined by a profile, may be managed.
    • valuetype-set is a pre-defined property used to select the group of value types that may be masked by any operation.
    • rule-set is a pre-defined property used to select the group of rules that will be applied for maskContent requests.
    • Profile implementations may define additional properties as needed. For example, one might define mask-pattern to override the default obfuscation pattern to avoid replicating (and complicating) configurations just to change the appearance of obfuscated data.
  • Trace output produced by operation requests, including errors and warnings encountered during request processing, can be controlled per context. This does not affect the operation output, per-se, but provides compatibility with transactional trace output control.

Refer to IDataMaskingProfileContext (extending IDataMasker) and IDataMaskingProfileContextInspector (extending IDataMaskerInspector) for additional information.

Value Type

Each snapshot defines one or more value types. A value type is a representation of the requirements pertaining to a particular concept of a domain datum. Requirements include:

  • instructions for identifying occurrences based on contextual clues found in a body of text; and
  • instructions for applying obfuscation to an identified value.

A Social Security Number, or SSN, is a U.S.-centric datum that requires obfuscation and for which a value type may be defined. Element names associated with an SSN may include, but are not limited to, SSN and SOCS; the value type is expected to identify all such names used within the domain. SSN occurrences are frequently partially masked, with common formats being to mask only the first four or the last five digits of the nine digit number; the value type defines which formats are available besides the default of masking all characters.

Value types are represented in the framework using IDataMaskingProfileValueType. They are accessed through the IDataMaskerInspector interface.

Mask Style

A mask style describes how obfuscation is applied to a value. It is always defined in the context of a value type, and a value type may define multiple.

A value type is not required to define any mask styles. If none are defined, all value characters are obfuscated. If the requested mask style is not defined, the default obfuscation occurs; the value type will not attempt to guess which of the defined styles is appropriate.

Mask styles are represented in the framework using IDataMaskingProfileMaskStyle. They are accessed through the IDataMaskingProfileValueType interface.

Rule

A rule contains the information necessary to locate at least one occurrence of a value type datum to be obfuscated. It is always defined in the context of a value type, and a value type may define multiple.

maskValue requests do not use rules. maskContent requests rely on rules for locating affected values, with the relationship between a profile and its rules an implementation detail. maskMarkupValue requests, when implemented, may also use rules or may take an entirely different approach.

Rules are represented in the framework as an abastract concept that cannot be inspected individually. Inspection may be used to establish the presence of rules, but not to examine individual instances.

Plugin

The combination of a shared library and entry point function describes a plugin. The input to a plugin is a property tree describing one or more profiles. The output of an entry point function is an iterator of profiles. The profiles created by the function may all be associated with the same domain, but this is not required.

Plugin results are represented in the framework using IDataMaskingProfileIterator.

Engine

An engine is the platform's interface to obfuscation. It loads domains by loading one or more plugins. Plugins yield profiles, from which domains are inferred.

Once configured with at least one domain, a caller can obtain obfuscation in multiple ways:

  1. As an instance of a stateless masker, an engine can provide obfuscation. The default version of the default profile of the default domain will be used, and no custom context properties can be used. This is the simplest integration, appropriate for use when the host process implicitly trusts that the default configuration is sufficient.
  2. An engine may be used to obtain a stateless profile for a specific domain. If no version is requested, the default profile of the requested domain will be used. If a version is specified, the domain profile supporting the requested version is used. This usage supports host processes that are aware of domains and need to use specific instances.
  3. An engine may be used to obtain a context tied to a specific version of a domain. The default domain may be requested with an empty string. The default version may be requested by passing 0. With a context, a caller may manipulate further the environment for obfuscation requests.

Engines are represented in the framework using IDataMaskingEngine (extending IDataMasker) and IDataMaskingEngineInspector for additional information.

Environment

This section assumes a context is in use. Absent a context, only the default state of the default version of a profile can be used.

The framework reserves multiple custom property names, which are described in subsequent subsections. It also allows implementations to define additional properties using any non-reserved name.

Suppose an implementation defines a property to override the default obfuscation pattern. Let's call this property default-pattern. A caller could interrogate a masker to know if default-pattern is accepted. If accepted, setProperty("default-patterns", "#") can be used to register an override.

All custom properties, whether defined in the framework or in third party libraries, are managed using a generic context interface. This interface includes:

bool hasProperties() cosnt
+bool hasProperty(const char* name) const
+const char* queryProperty(const char* name) const
+bool setProperty(const char* name, const char* value)
+bool removeProperty(const char* name)

Value Type Sets

bool setProperty(const char* name, const char* value);

Overview

The abstraction includes the concept of sets of related value types. All value types should be assigned membership in at least one set. One set should be selected by default. The custom context property valuetype-set is used to select a different set.

The rationale for this is an expectation that certain data always requires obfuscation. A password, for example, would never not require obfuscation. Other data may only require obfuscacation in certain situations, such as when required by individual customer agreements. Callers should not be required to complete additional steps to act on data that must always be obfuscated, and should not need to know which data falls in which category.

The set name "*" is reserved to select all value types regardless of their defined set membership. This mechanism is intended to assist with compatibility checks, and should be used with care in other situations.

Runtime Compatibility

Use acceptsProperty to determine whether a snapshot recognizes a custom property name. Use usesProperty to learn if the snapshot includes references to the custom property name. Acceptance implies awareness of the set (and what it represents) even if selecting it will not change the outcome of any masking request. Usage implies that selecting the set will change the outcome of at least one masking operation.

The property valuetype-set is used to select a named set. A check of this property addresses whether the concept of a set is accepted or used by the snapshot.

For improved compatibility checks, implementations are encouraged to reserve additional property names that enable checks for individual set names. For each set name, foo, the included implementations report property valuetype-set:foo as used.

Implementation

The included implementations allow membership in either a default, unnamed set or in any number of named sets. Absent a contextual request for a named set, the unnamed set is selected by default. With a contextual set request, the members of the named set are selected in addition to members of the unnamed set. Members of the unnamed set are never not selected.

Unnamed set membership cannot be defined explicitly. Because this set is always selected, assigning a value type to both a named and the unnamed set is redundant - the type will be selected whether the named set is requested or not.

Example 1a: default value type

profile:
+  valueType:
+    - name: type1

Value type type1 belongs to the default, unnamed value type set. Property valuetype-set is accepted, but not used; an attempt to use it will change no results.

Example 1b: sample set memberships

profile:
+  valueType:
+    - name: type1
+    - name: type2
+      memberOf:
+        - name: set1
+        - name: set2
+    - name: type3
+      memberOf:
+        - name: set2
+        - name: set3
+    - name: type4
+      memberOf:
+        - name: set1
+    - name: type5
+      memberOf:
+        - name: set3

Extending the previous example, five types and four sets are in use. Property valuetype-set is both accepted and used, as it can now impact results. Properties valuetype-set:set1, valuetype-set:set2 and valuetype-set:set3 are also both accepted and used, but are intended only for use with compatibility checks.

This table shows which types are selected for each requested set:

unnamedset1set2set3*
type1YYYYY
type2NYYNY
type3NNYYY
type4NYNNY
type5NNNYY

Example 1c: sample accepted sets

profile:
+  valueType:
+    - name: type1
+    - name: type2
+      memberOf:
+        - name: set1
+        - name: set2
+    - name: type3
+      memberOf:
+        - name: set2
+        - name: set3
+    - name: type4
+      memberOf:
+        - name: set1
+    - name: type5
+      memberOf:
+        - name: set3
+  property:
+    - name: 'valuetype-set:set4'
+    - name: 'valuetype-set:set5'

Continuing the previous example, the profile has declared acceptance of two additional sets using properties valuetype-set:set4 and valuetype-set:set5. Selection of these sets will not change results, but a caller might accept acceptance of a set as sufficient to establish compatibility.

Rule Sets

bool setProperty(const char* name, const char* value);

Overview

The abstraction includes the concept of sets of related rules. All rules should be assigned membership in at least one set. One set should be selected by default. The custom context property rule-set is used to select a different set.

The rationale for this is similar to yet different than that of value type sets. Instead of one set of rules that are always selected, backward compatibility with a proprietary legacy implementation requires that the default set be replaced by an alternate collection.

The set name "*" is reserved to select all rules regardless of their defined set membership. This does not override constraints imposed by the current value type set. This mechanism is intended to assist with compatibility checks, and should be used with care in other situations.

Runtime Compatibility

Use acceptsProperty to determine whether a snapshot recognizes a custom property name. Use usesProperty to learn if the snapshot includes references to the custom property name. Acceptance implies awareness of the set (and what it represents) even if selecting it will select no rules. Usage implies that selecting the set will select at least one rule.

The property rule-set is used to select a named set. A check of this property addresses whether the concept of a set is accepted or used by the snapshot.

For improved compatibility checks, implementations are encouraged to reserve additional property names that enable checks for individual set names. For each set name, foo, the included implementations report property rule-set:foo as used.

Implementation

The included implementations allow membership in any number of named sets as well as a default, unnamed set. Absent a contextual request for a named set, the unnamed set is selected by default. With a contextual set request, only the members of the requested set are selected.

Unlike value type sets, the unnamed set is not always selected. Because of this difference, a rule may be assigned explicit membership in the unnamed set. This is optional for rules that belong only to the unnamed set and is required for rules meant to be selected as part of the unnamed set and one or more named sets.

Example 2: rule set memberships

profile:
+  valueType:
+    name: type1
+    rule:
+      - name: foo
+      - memberOf:
+          - name: ''
+      - memberOf:
+          - name: ''
+          - name: set1
+      - memberOf:
+          - name: set1
+  property:
+    name: 'rule-set:set2'

The rules described here are intentionally incomplete, showing only what is necessary for this example. Four rules are defined:

  1. The rule named foo implicitly belongs to the unnamed set.
  2. The second rule explicitly belongs to the unnamed set.
  3. The third rule explicitly belongs to the unnamed set and to set1.
  4. The fourth rule belongs to set1.

property rule-set is both accepted and used. Properties rule-set: and rule-set:set1 are both accepted and used, intended for use with compatibility checks. Property rule-set:set2 is accepted.

Operations

maskValue

bool maskValue(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length, bool conditionally) const;
+bool maskValueConditionally(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length) const;
+bool maskValueUnconditionally(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length) const;

Overview

Given a single domain datum, obfuscate the content "as needed". The framework anticipates two interpretations of "as needed", either conditional or unconditional:

  • Conditional means that values of a named type are only obfuscated if the named type is known by and selected in the snapshot. This usage assumes that the profile is the authoritative source for obfuscation requirements. The profile's consumer may ask for any value to be obfuscated, but the profile is not required to act.
  • Unconditional means that all value obfuscation requests will result in obfuscation. The named type is not required to be known by or selected in the snapshot. This usage assumes that the profile's consumer is the authoritative source for obfuscation requirements. If a consumer asks for a value to be obfuscated, the profile must act.

Each plugin will define its own interpretation of "as needed". The API distinction between conditional and unconditional is a hint intended to guide implementations capable of both, and should be ignored by implementations that are not.

The buffer, offset, and length parameters define what is assumed to be a single domain datum. That is, every character in the given character range is subject to obfuscation.

The valueType parameter determines if obfuscation is required, with conditionally controlling whether an unrecognized valueType is ignored (true) or forced to obfuscate (false).

The maskStyle parameter may be used to affect the nature of obfuscation to be applied. If the parameter names a defined mask style, that obfuscation format is applied. If the parameter does not name a define mask style, the value type's default obfuscation format is applied.

Runtime Compatibility

The canMaskValue method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The hasValueType method of IDataMaskerInspector can be used to check if a given name is selected in the snapshot. The reserved name "*" may be used to detect unconditional obfuscation support when available in the snapshot. Names defined yet unselected in the snapshot are not directly detectable, but can be detected by comparing results of using multiple context configurations.

The name "*" is reserved by the abstraction for compatibility checks.

To confirm the availability of a specific mask style first requires confirmation of the value type to which the mask style is related. Use queryValueType to obtain the defined instance and, from the result, call queryMaskStyle to confirm the existence of the mask stye. For each of these, corresponding get... methods are defined to obtain new references to the objects. The methods are declared by IDataMaskerInspector.

Implementations

The included implementations are inherently conditional. Obfuscation depends on matching valueType with a selected value type instance in the snapshot, where an instance is selected if it belongs to the currently selected value type set.

Unconditional obfuscation is enabled in profiles that include a value type instance named "*". If an instance with this name is selected by the currently selected value type set, all values for undefined value types will be obfuscated. Values for defined but unselected value types will not be obfuscated.

  • A value of type "foo" will be obfuscated when type "foo" is selected by the currently selected value type set.
  • A value of type "foo" will be obfuscated when type "foo" is undefined and type "*" is selected by the currently selected value type set.
  • A value of type "foo" will not be obfuscated when type "foo" is defined but unselected by the currently selected value type set.

Example 3a: conditional obfuscation

valueType:
+  - name: type1
+  - name: type2
+    memberOf:
+      - name: set1

This snippet declares two value types, with two total sets. The table shows which values will be conditionally obfuscated based on a the combination of valueType and the currently selected value type set.

valueTypeSelected SetObfuscated
type1N/AYes
type1set1Yes
type2N/ANo
type2set1Yes
typeNN/ANo
typeNset1No

"typeN" in the table represents any named type, excluding the reserved "*", not explicitly defined in the profile.

Example 3b: unconditional obfuscation

valueType:
+  - name: type1
+  - name: type2
+    memberOf:
+      - name: set1
+  - name: *

Extending the previous example with a third value type, values of unknown type are now obfuscated. Values of known but unselected type remain unobfuscated.

valueTypeSelected SetObfuscated
type1N/AYes
type1set1Yes
type2N/ANo
type2set1Yes
typeNN/AYes
typeNset1Yes

The name reserved by the abstraction to detect support for unconditional obfuscation is the same name reserved by the implementation to define that support.

"typeN" in the table represents any named type, excluding the reserved "*", not explicitly defined in the profile.

maskContent

bool maskContent(const char* contentType, char* buffer, size_t offset, size_t length) const;

Overview

The buffer, offset, and length parameters define what is assumed to be a blob of content that may contain zero or more occurrences of domain data. The blob is expected to contain sufficient context allowing a snapshot's rules to locate any included occurrences.

The context parameter optionally influences which rules are selected in a snapshot, while the contentType parameter offers a hint as to the blob's data format. Each snapshot rule may be assigned a format to which it applies. Operation requests may be optimized by limiting the number of selected rules applied by describing the format. Selected rules not assigned a format will be applied in all requests.

There is no equivalent to the unconditional mode offered by maskValue. Content obfuscation is always conditional on matching rules.

Runtime Compatibility

The canMaskContent method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The hasRule method of IDataMaskerInspector indicates if at least one rule is selected in the snapshot for a given content type. Rules not associated with any content type may be interrogated using an empty string.

It is not possible to discern any information about the selected rules, such as their associated value types or the cues used to apply them in a buffer.

Implementations

The groundwork exists for two types of rules, serial and parallel. Serial rules, as the name implies, are evaluated sequentially. Parallel rules, on the other hand, are evaluated concurrently. Concurrent evaluation is expected to be more efficient than sequential, but no concurrent implementation is provided.

The included sequential implementation should be viewed as a starting point. Each rule defines a start token and an optional end token. For each occurrence of the start token in the blob, a corresponding search for an end token (newline if omitted), is performed. When both parts are found, the content between is obfuscated using the associated value type's default mask style.

Traversing a potentially large blob of text once per rule is inefficient. A concurrent implementation is in development to improve performance and capabilities. A domain originated using the original implementation and migrated to the new implementation illustrates one domain implemented by multiple plugins and, by extension, multiple profiles.

maskMarkupValue

bool maskMarkupValue(const char* element, const char* attribute, char* buffer, size_t offset, size_t length, IDataMaskingDependencyCallback& callback) const;

Overview

Where maskValue obfuscates individual values based on what the caller believes the value to be, and maskContent obfuscates any number of values it identifies based on cues found in surrounding text, maskMarkupValue obfuscastes individual values based on cues not provided to it.

Given a value and a relative location within a structure document, an implementation must decide whether obfuscation is required, may be required, or is not required. If required, it can obfuscate immediately. If not required, and can return immediately. If it might be required, the implementation must request the context it needs from the caller in order to make a final determination.

A request to mask the content of an element named Value might depend on the content of a sibling element named Name. If value of Value probably does not require obfuscation when the value of Name is city, but almost certainly does require obfuscation when the value of Name is password. When processing the request for Value, an implementation must ask for the value of a sibline named Name to decide whether or not to obfuscate the data.

There is no equivalent to the unconditional mode offered by maskValue. Markup value obfuscation presumes that the caller is traversing a structured document without awareness of which values require obfuscation. If the caller knows which values require obfuscation, it should use maskValue on those specific values.

Runtime Compatibility

    virtual bool getMarkupValueInfo(const char* element, const char* attribute, const char* buffer, size_t offset, size_t length, IDataMaskingDependencyCallback& callback, DataMaskingMarkupValueInfo& info) const = 0;

The canMaskMarkupValue method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The getMarkupValueInfo method of IDataMaskerInspector determines if the value of an element or attribute requires obfuscation. If required, the info parameter will describe on completion how to perform the obfuscation using maskValue instead of maskMarkupValue. This is done to optimize performance by eliminating the need to reevaluate rules to re-identify a match.

Assme a value type representing passwords is defined. If given an element value containing a URL with an embedded password, obfuscation is required. But most likely not for the entire value. In addition to identifying the value type associated with value, the offset and length of the embedded substring requiring obfuscation are supplied; use of maskValue with these three values and no mask style will apply the default obfuscation only to the embedded password. Alternatively, if a mask style is supplied, use of maskValue with this style and the original value offset and length should obfuscate only the embedded password.

A callback interface is required for all calls to getMarkupValueInfo. The interface is only used when additional document context is required to make a determination about the requested value. In these cases, the checks are proximity-dependent and cannot be performed as part of load-time compatibility checks, when no document is available.

Implementations

TBD

Load-time Compatibility

bool checkCompatibility(const IPTree& requirements) const;

Overview

Use of runtime compatibility checks may be unavoidable in some circumstances, but reliance on this in every script is inefficient. It may also devalue trace output by omitting messaging required to debug an issue, a problem that might only be found when said messaging is needed.

The requirements parameter represents a standardized set of runtime compatibility checks to be applied. By default, each check must pass to establish compatibility. Explicitly optional checks may be included to detect conditions the caller is prepared to work around.

This can test which values will or won't be affected by maskValue, and which obfuscation styles are available. It can test which rule sets will impact maskContent. It specifically excludes maskMarkupValue checks that depend on the proximity of one value to another.

Returning to the earlier SSN example, a caller might require that an SSN value type will be obfuscated. It might also want to use a particilar mask style, but may be prepared for its absence. A caller may prefer to mask the first four digits but may accept masking the last five instead, or may omit certain trace messages.

Implementation

compatibility:
+  - context:
+      - domain: optional text
+        version: optional number
+        property:
+          - name: required text
+            value: required text
+    accepts:
+      - name: required text
+        presence: one of "r", "o", or "p"
+    uses:
+      - name: required text
+        presence: one of "r", "o", or "p"
+    operation:
+      - name: one of "maskValue", "maskContent", or "maskMarkupValue"
+        presence: one of "r", "o", or "p"
+    valueType
+      - name: required text
+        presence: one of "r", "o", or "p"
+        maskStyle:
+          - name: required text
+            presence: one of "r", "o", or "p"
+        Set:
+          - name: required text
+            presence: one of "r", "o", or "p"
+    rule:
+      - contentType: required text
+        presence: one of "r", "o", or "p"

The requirements parameter of checkCompatibility must be either a compatibility element or the parent of a collection of compatibility elements. Each instance may contain at most one context element, three operation elements (one per operation), and a variable number of accepts, uses, valueType, or rule elements.

The context element describes the target of an evaluation. It may include a domain identifier, or assume the default domain. It may include a version number, or assume the default snapshot of the domain. It may specify any number of custom context properties to select various profile elements in the snapshot.

The accepts and uses elements identify custom context property names either accepted or used within a snapshot. Acceptance indicates some part of a snapshot has declared an understanding of the named property. Usage indicates the some part of a snapshot will react to the named value being set. Usage implies acceptance.

To improve compatibility check capabilities, a snapshot may synthesize properties that, if set, will have no effect. Specifically, a caller may be more interested to know if a particular set name (for either value type or rule set membership) is used than to know that an unidentified set name is used.

The Operations element identifies which operations must be supported. Its purpose is to enforce availability of an operation when no more explicit requirements are given (for example, one may require maskContent without also requiring the presence of rules for any given content type). When more explicit requirements are given, the attributes of this element are redundant. If all attributes are redundant, the element may be omitted.

The valueType element describes all optional and mandatory requirements for using maskValue with a single value type name and optional mask style name. Set membership requirements can also be evaluated, which is most useful when compatibility/context/property/@name is "*".

The rule element describes all optional and mandatory requirements for using maskContent with a single content type value. Unlike evaluable value type set membership, rule set membership requirements cannot be evaluated.

In all elements described as accepting @presence, the acceptable values are

  • r: the check is required to pass
  • o: the check is optional
  • p: the check is prohibited from passing
`,170)]))}const h=t(o,[["render",i]]);export{u as __pageData,h as default}; diff --git a/assets/system_masking_plugins_datamasker_readme.md.DKeIe1BA.js b/assets/system_masking_plugins_datamasker_readme.md.DKeIe1BA.js new file mode 100644 index 00000000000..5a7408709c3 --- /dev/null +++ b/assets/system_masking_plugins_datamasker_readme.md.DKeIe1BA.js @@ -0,0 +1,24 @@ +import{_ as t,c as i,a3 as n,o as a}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"system/masking/plugins/datamasker/readme.md","filePath":"system/masking/plugins/datamasker/readme.md","lastUpdated":1731340314000}'),o={name:"system/masking/plugins/datamasker/readme.md"};function s(r,e,l,m,d,p){return a(),i("div",null,e[0]||(e[0]=[n(`

libdatamasker.so

This implements a plugin library producing instances of IDataMaskingProfileIterator to be used by an IDataMaskingEngine instance. The library is written in C++, and this section is organized according to the namespace and class names used.

namespace DataMasking

CContext

Standard implementation of IDataMaskingProfileContext and IDataMaskingProfileContextInspector tightly coupled to a specific version of a specific profile instance. It manages custom properties that are accepted by the associated profile, and rejects attempts to set those not accepted. The rejection is one way of letting the caller know that something it might deem essential is not handled by the profile.

Depends on CProfile, an abstract implementation of IDataMaskingProfile and IDataMaskingProfileInspector.

CMaskStyle

Implementation of IDataMaskingProfileMaskStyle providing customized masked output of values. Configuration options include:

  • @maximumVersion is an optional version number, needed only when the style does not apply to the maximum version of the value type in which it is defined.
  • @minimumVersion is an optional version number, needed only when the style does not apply to the minumum version of the value type in which it is defined.
  • @name is a required unique identifier for the style.
  • @overrideDefault is an optional Boolean flag controlling whether the style should replace the default style for the value type in which it is defined.
  • @pattern is an optional character sequence (one or more characters in length) that will be used to mask value content.

CPartialMaskStyle

Am extension of CMaskStyle, this is intended to partially mask values, such as account numbers or telephone numbers, without assuming knowledge of the values. Configuration is generally expressed as:

[ [ @action ] [ @location ] @count  [ @characters ] ]
+

Where:

  • @action what to do, either keep or mask (default); and
  • @location where to do it, either first or last (default); and
  • @count how many characters to examine, a positive integer value; and
  • @characters type of characters to be considered, either numbers, letters, alphanumeric, or all (default)

All four values may be omitted when only the inherited options are needed. If any of the four values are given, count is required and the other three are optional.

A value substring containing at most @count instances of the character class denoted by @characters is identified. Character classes are ASCII numeric characters (numbers), ASCII alphabetic characters (letters), ASCII alphabetic and numeric characters (alphanumeric), or any characters (all).

The value substring is at the start of the value when @location is first, and at the end of the value when @location is last.

The value substring is masked when @action is mask. All of the value with the exception of the substring is masked when @action is keep.

CRule

A base class for rule implementations, this defines standard rule properties without providing any content knowledge for use with maskContent.

Configuration includes:

  • @contentType is an optional, user-defined, label describing the content format to which the rule should be applied. maskContent requests that indicate a content type should apply all rules that match the type in addition to all rules that omit a type.
  • memberOf is an optional and repeating element identifying user-defined set labels. Only rules with membership in the requested set are considered; in the absence of an explictly requested set, a default set with an empty name is implied.
  • memberOf/@name is a required user-defined set label.

rule instances will frequently be specific to a content format. For example, a rule applied to XML markup value may include dependencies on XML syntax which are not applicable when masking JSON content. Rules specific to a markup format can be associated with that format using @contentType. Requests to mask content of a type will apply all rules without a @contentType value or with a matching value, and will exclude all rules with a non-matching value.

CSerialTokenRule

An extension of CRule, this identifies content substrings to be masked based on matching start and end tokens in the content buffer. For each occurrence of a configured start token that is balanced by a corresponding configured end token, the characters between the tokens are masked.

This class may be used with TSerialProfile.

Configuration includes:

  • @endToken is an optional character sequence expected to immediately follow a content substring to be masked. This might be an XML element end tag, or a terminating double quote for a JSON value. A newline, i.e., \\n, is assumed if omitted or empty, for masking values such as HTTP headers.
  • @matchCase is an optional Boolean flag controlling whether token matches are case sensitive (true) or insensitivie (false). Matches are case insensitive by default. The implementation is based on ASCII data; case sensitive comparisons of similarly encoded non-ASCI text can work, but case insensitive comparisons are not supported.
  • @startToken is a required character sequence expected to precede a content substring to be masked.

No content type knowledge is implied by this class. An instance with @contentType of xml does not inherently know how to find values in XML markup. The defined tokens must include characters such as < and > to match an element name and quotes to match attribute values.

TPlugin

An implementation of IDataMaskingProfileIterator used for transforming a configuration property tree into a collection of profiles to be returned by a library entry point function. Created profiles are of the same class, identified by the template parameter profile_t.

Configuration includes:

  • profile An optional and repeating element defining a profile. The content of this element depends on profile_t.
  • If and only if profile is not included as a child of the configuration node, the node itself will be treated as a configuration for profile_t. The name of the configuration node is not required to be profile.

TProfile

A concrete extension of CProfile, this manages value types and rules, providing a default implementation of maskValue but leaving other operations to subclasses.

Template parameters are:

  • valuetype_t identifies the concrete implementation of IDataMaskingProfileValueType to be instantiated during configuration.
  • rule_t identifies the concrete implementation of rules to be managed. Creation is assumed to be the responsibility of the value type.
  • context_t identifies the concrete implementation of IDataMaskingProfileContext to be instantiated on demand.

Configuration includes:

  • @defaultVersion is an optional version number identifying the version to be used when version 0 is requested. Omission or 0 implies the maximum version (which is configured before this value).
  • @domain is the required default identifier used to select this profile.
  • legacyDomain is an optional and repeating element identifying alternate identifiers by which the profile may be selected. This enables domain identifier naming conventions to be changed without breaking pre-existing references.
  • legacyDomain/@id is a required identifier of this profile.
  • @maximumVersion is an optional version number identifying the highest version supported by the configuration. Omission or 0 implies a value matching the minimum version (whether implicitly or explicitly defined).
  • @minimumVersion is an optional version number identifying the lowest version supported by the configuration. Omission or 0 implies 1.
  • @name is an optional label for the instance. If given, the value is used in trace output.
  • property is an optional and repeating element describing a custom context property recognized by the profile. The profile can declare awareness of properties without explicit use of them.
  • property/@name is a required context property name.
  • property/@minimumVersion is an optional version number indicating the lowest profile version aware of the property. Omission or 0 implies the profile minimum.
  • property/@maximumVersion is an optional version number indicating the highest profile version aware of the property. Omisssion or 0 implies the profile maximum.
  • valueType is an optional and repeating element describing the value types defined by the profile. Element content depends on the value of valuetype_t.

For profiles supporting a single version, value type names must be unique. For profiles supporting multiple versions, value type names may be repeated but must be unique for each version. To illustrate, consider this snippet:

profile:
+  minimumVersion: 1
+  maximumVersion: 2
+  valueType:
+    - name: foo
+      maximumVersion: 1
+    - name: foo
+      minimumVersion: 2
+    - name: bar
+    - name: bar
+      minimumVersion: 2
+

In the example, foo and bar are each defined twice. The redefinition of foo is acceptable because each instance applies to a different version. The redefinition of bar is invalid because both instances claim to apply to version 2.

The value type name * is reserved by this class. A profile that includes a value type named * supports unconditional value masking. maskValue requests specifying unknown value type names can fall back to this special type and force masking for these values. Without this type, maskValue masks what it thinks should be masked; with this type, it trusts the caller to request masking for only those values known to require it. Value types included in unselected value type sets are known to the profile and, as such, can be used to prevent specific typed values from ever being masked.

The requirement of a type definition instead of a simpler flag is to enable the definition of mask styles. It has the side effect of enabling the definition of rules. In theory, an entire profile could be defined using a single value type. This may make sense in some cases, but not in all. For example, a partial mask style intended for use with U.S. Social Security numbers could be inappropriately applied to a password. Use care when configuring this type.

TSerialProfile

An extension of TProfile that adds support for maskContent. It is assumed by maskContent that each applicable rule must be applied serially, i.e., one after the other, using an bool applyRule(buffer, length, context) interface.

Template parameters are unchanged from TProfile.

Configuration options are unchanged from TProfile.

TValueType

An implementation of IDataMaskingProfileValueType that manages mask styles and creates rules for the profile.

Template parameters are:

  • maskstyle_t identifies the concrete implementation of IDataMaskingProfileMaskStyle to be instantiated during configuration.
  • rule_t identifies the concrete implementation of rules to be created during configuration.

Configuration includes:

  • @maximumVersion is an optional version number, needed only when the type does not apply to the maximum version of the profile in which it is defined.
  • @minimumVersion is an optional version number, needed only when the type does not apply to the minumum version of the profile in which it is defined.
  • maskStyle is an optional and repeating element describing mask styles defined by the type. Element content depends on the value of maskstyle_t.
  • memberOf is an optional and repeating element identifying user-defined set labels. All value types that are not explicitly assigned set membership are considered for all masking requests. Value types assigned set membership are considered only when one of their assigned sets is selected with the request context.
  • memberOf/@name is a required user-defined set label.
  • @name is a required unique identifier for the type.
  • rule is an optional and repeating element describing rules defined by the type. Element content depends on the value of rule_t.

For value types supporting a single version, mask style names must be unique. For types supporting multiple versions, style names may be repeated but must be unique for each version. To illustrate, consider this snippet:

valueType:
+  minimumVersion: 1
+  maximumVersion: 2
+  maskStyle:
+    - name: foo
+      maximumVersion: 1
+    - name: foo
+      minimumVersion: 2
+    - name: bar
+    - name: bar
+      minimumVersion: 2
+

In the example, foo and bar are each defined twice. The redefinition of foo is acceptable because each instance applies to a different version. The redefinition of bar is invalid because both instances claim to apply to version 2.

Entry Point Functions

This section describes the entry point functions exported by the shared library. The library must export one function, and may export multiple functions.

The description of each entry point will identify which of the previously described classes is used to represent the returned collection of profiles. If a templated class is identified, the template parameters are also listed. Refer to the class descriptions for additional information.

newPartialMaskSerialToken

Returns a (possibly empty) collection of profiles supporting maskValue and maskContent operations.

  • The profile collection is an instance of TPlugin.
  • Collection profiles are implemented using TProfile.
  • Profile value types are implemented using TValueType.
  • Value type mask styles are implemented using CPartialMaskStyle.
  • Value type and profile rules are implemented using CSerialTokenRule.
  • Profile contexts are implemented using CContext.
`,63)]))}const h=t(o,[["render",s]]);export{u as __pageData,h as default}; diff --git a/assets/system_masking_plugins_datamasker_readme.md.DKeIe1BA.lean.js b/assets/system_masking_plugins_datamasker_readme.md.DKeIe1BA.lean.js new file mode 100644 index 00000000000..5a7408709c3 --- /dev/null +++ b/assets/system_masking_plugins_datamasker_readme.md.DKeIe1BA.lean.js @@ -0,0 +1,24 @@ +import{_ as t,c as i,a3 as n,o as a}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"system/masking/plugins/datamasker/readme.md","filePath":"system/masking/plugins/datamasker/readme.md","lastUpdated":1731340314000}'),o={name:"system/masking/plugins/datamasker/readme.md"};function s(r,e,l,m,d,p){return a(),i("div",null,e[0]||(e[0]=[n(`

libdatamasker.so

This implements a plugin library producing instances of IDataMaskingProfileIterator to be used by an IDataMaskingEngine instance. The library is written in C++, and this section is organized according to the namespace and class names used.

namespace DataMasking

CContext

Standard implementation of IDataMaskingProfileContext and IDataMaskingProfileContextInspector tightly coupled to a specific version of a specific profile instance. It manages custom properties that are accepted by the associated profile, and rejects attempts to set those not accepted. The rejection is one way of letting the caller know that something it might deem essential is not handled by the profile.

Depends on CProfile, an abstract implementation of IDataMaskingProfile and IDataMaskingProfileInspector.

CMaskStyle

Implementation of IDataMaskingProfileMaskStyle providing customized masked output of values. Configuration options include:

  • @maximumVersion is an optional version number, needed only when the style does not apply to the maximum version of the value type in which it is defined.
  • @minimumVersion is an optional version number, needed only when the style does not apply to the minumum version of the value type in which it is defined.
  • @name is a required unique identifier for the style.
  • @overrideDefault is an optional Boolean flag controlling whether the style should replace the default style for the value type in which it is defined.
  • @pattern is an optional character sequence (one or more characters in length) that will be used to mask value content.

CPartialMaskStyle

Am extension of CMaskStyle, this is intended to partially mask values, such as account numbers or telephone numbers, without assuming knowledge of the values. Configuration is generally expressed as:

[ [ @action ] [ @location ] @count  [ @characters ] ]
+

Where:

  • @action what to do, either keep or mask (default); and
  • @location where to do it, either first or last (default); and
  • @count how many characters to examine, a positive integer value; and
  • @characters type of characters to be considered, either numbers, letters, alphanumeric, or all (default)

All four values may be omitted when only the inherited options are needed. If any of the four values are given, count is required and the other three are optional.

A value substring containing at most @count instances of the character class denoted by @characters is identified. Character classes are ASCII numeric characters (numbers), ASCII alphabetic characters (letters), ASCII alphabetic and numeric characters (alphanumeric), or any characters (all).

The value substring is at the start of the value when @location is first, and at the end of the value when @location is last.

The value substring is masked when @action is mask. All of the value with the exception of the substring is masked when @action is keep.

CRule

A base class for rule implementations, this defines standard rule properties without providing any content knowledge for use with maskContent.

Configuration includes:

  • @contentType is an optional, user-defined, label describing the content format to which the rule should be applied. maskContent requests that indicate a content type should apply all rules that match the type in addition to all rules that omit a type.
  • memberOf is an optional and repeating element identifying user-defined set labels. Only rules with membership in the requested set are considered; in the absence of an explictly requested set, a default set with an empty name is implied.
  • memberOf/@name is a required user-defined set label.

rule instances will frequently be specific to a content format. For example, a rule applied to XML markup value may include dependencies on XML syntax which are not applicable when masking JSON content. Rules specific to a markup format can be associated with that format using @contentType. Requests to mask content of a type will apply all rules without a @contentType value or with a matching value, and will exclude all rules with a non-matching value.

CSerialTokenRule

An extension of CRule, this identifies content substrings to be masked based on matching start and end tokens in the content buffer. For each occurrence of a configured start token that is balanced by a corresponding configured end token, the characters between the tokens are masked.

This class may be used with TSerialProfile.

Configuration includes:

  • @endToken is an optional character sequence expected to immediately follow a content substring to be masked. This might be an XML element end tag, or a terminating double quote for a JSON value. A newline, i.e., \\n, is assumed if omitted or empty, for masking values such as HTTP headers.
  • @matchCase is an optional Boolean flag controlling whether token matches are case sensitive (true) or insensitivie (false). Matches are case insensitive by default. The implementation is based on ASCII data; case sensitive comparisons of similarly encoded non-ASCI text can work, but case insensitive comparisons are not supported.
  • @startToken is a required character sequence expected to precede a content substring to be masked.

No content type knowledge is implied by this class. An instance with @contentType of xml does not inherently know how to find values in XML markup. The defined tokens must include characters such as < and > to match an element name and quotes to match attribute values.

TPlugin

An implementation of IDataMaskingProfileIterator used for transforming a configuration property tree into a collection of profiles to be returned by a library entry point function. Created profiles are of the same class, identified by the template parameter profile_t.

Configuration includes:

  • profile An optional and repeating element defining a profile. The content of this element depends on profile_t.
  • If and only if profile is not included as a child of the configuration node, the node itself will be treated as a configuration for profile_t. The name of the configuration node is not required to be profile.

TProfile

A concrete extension of CProfile, this manages value types and rules, providing a default implementation of maskValue but leaving other operations to subclasses.

Template parameters are:

  • valuetype_t identifies the concrete implementation of IDataMaskingProfileValueType to be instantiated during configuration.
  • rule_t identifies the concrete implementation of rules to be managed. Creation is assumed to be the responsibility of the value type.
  • context_t identifies the concrete implementation of IDataMaskingProfileContext to be instantiated on demand.

Configuration includes:

  • @defaultVersion is an optional version number identifying the version to be used when version 0 is requested. Omission or 0 implies the maximum version (which is configured before this value).
  • @domain is the required default identifier used to select this profile.
  • legacyDomain is an optional and repeating element identifying alternate identifiers by which the profile may be selected. This enables domain identifier naming conventions to be changed without breaking pre-existing references.
  • legacyDomain/@id is a required identifier of this profile.
  • @maximumVersion is an optional version number identifying the highest version supported by the configuration. Omission or 0 implies a value matching the minimum version (whether implicitly or explicitly defined).
  • @minimumVersion is an optional version number identifying the lowest version supported by the configuration. Omission or 0 implies 1.
  • @name is an optional label for the instance. If given, the value is used in trace output.
  • property is an optional and repeating element describing a custom context property recognized by the profile. The profile can declare awareness of properties without explicit use of them.
  • property/@name is a required context property name.
  • property/@minimumVersion is an optional version number indicating the lowest profile version aware of the property. Omission or 0 implies the profile minimum.
  • property/@maximumVersion is an optional version number indicating the highest profile version aware of the property. Omisssion or 0 implies the profile maximum.
  • valueType is an optional and repeating element describing the value types defined by the profile. Element content depends on the value of valuetype_t.

For profiles supporting a single version, value type names must be unique. For profiles supporting multiple versions, value type names may be repeated but must be unique for each version. To illustrate, consider this snippet:

profile:
+  minimumVersion: 1
+  maximumVersion: 2
+  valueType:
+    - name: foo
+      maximumVersion: 1
+    - name: foo
+      minimumVersion: 2
+    - name: bar
+    - name: bar
+      minimumVersion: 2
+

In the example, foo and bar are each defined twice. The redefinition of foo is acceptable because each instance applies to a different version. The redefinition of bar is invalid because both instances claim to apply to version 2.

The value type name * is reserved by this class. A profile that includes a value type named * supports unconditional value masking. maskValue requests specifying unknown value type names can fall back to this special type and force masking for these values. Without this type, maskValue masks what it thinks should be masked; with this type, it trusts the caller to request masking for only those values known to require it. Value types included in unselected value type sets are known to the profile and, as such, can be used to prevent specific typed values from ever being masked.

The requirement of a type definition instead of a simpler flag is to enable the definition of mask styles. It has the side effect of enabling the definition of rules. In theory, an entire profile could be defined using a single value type. This may make sense in some cases, but not in all. For example, a partial mask style intended for use with U.S. Social Security numbers could be inappropriately applied to a password. Use care when configuring this type.

TSerialProfile

An extension of TProfile that adds support for maskContent. It is assumed by maskContent that each applicable rule must be applied serially, i.e., one after the other, using an bool applyRule(buffer, length, context) interface.

Template parameters are unchanged from TProfile.

Configuration options are unchanged from TProfile.

TValueType

An implementation of IDataMaskingProfileValueType that manages mask styles and creates rules for the profile.

Template parameters are:

  • maskstyle_t identifies the concrete implementation of IDataMaskingProfileMaskStyle to be instantiated during configuration.
  • rule_t identifies the concrete implementation of rules to be created during configuration.

Configuration includes:

  • @maximumVersion is an optional version number, needed only when the type does not apply to the maximum version of the profile in which it is defined.
  • @minimumVersion is an optional version number, needed only when the type does not apply to the minumum version of the profile in which it is defined.
  • maskStyle is an optional and repeating element describing mask styles defined by the type. Element content depends on the value of maskstyle_t.
  • memberOf is an optional and repeating element identifying user-defined set labels. All value types that are not explicitly assigned set membership are considered for all masking requests. Value types assigned set membership are considered only when one of their assigned sets is selected with the request context.
  • memberOf/@name is a required user-defined set label.
  • @name is a required unique identifier for the type.
  • rule is an optional and repeating element describing rules defined by the type. Element content depends on the value of rule_t.

For value types supporting a single version, mask style names must be unique. For types supporting multiple versions, style names may be repeated but must be unique for each version. To illustrate, consider this snippet:

valueType:
+  minimumVersion: 1
+  maximumVersion: 2
+  maskStyle:
+    - name: foo
+      maximumVersion: 1
+    - name: foo
+      minimumVersion: 2
+    - name: bar
+    - name: bar
+      minimumVersion: 2
+

In the example, foo and bar are each defined twice. The redefinition of foo is acceptable because each instance applies to a different version. The redefinition of bar is invalid because both instances claim to apply to version 2.

Entry Point Functions

This section describes the entry point functions exported by the shared library. The library must export one function, and may export multiple functions.

The description of each entry point will identify which of the previously described classes is used to represent the returned collection of profiles. If a templated class is identified, the template parameters are also listed. Refer to the class descriptions for additional information.

newPartialMaskSerialToken

Returns a (possibly empty) collection of profiles supporting maskValue and maskContent operations.

  • The profile collection is an instance of TPlugin.
  • Collection profiles are implemented using TProfile.
  • Profile value types are implemented using TValueType.
  • Value type mask styles are implemented using CPartialMaskStyle.
  • Value type and profile rules are implemented using CSerialTokenRule.
  • Profile contexts are implemented using CContext.
`,63)]))}const h=t(o,[["render",s]]);export{u as __pageData,h as default}; diff --git a/assets/system_security_plugins_jwtSecurity_README.md.B1LGJUHj.js b/assets/system_security_plugins_jwtSecurity_README.md.B1LGJUHj.js new file mode 100644 index 00000000000..8ad890ce01f --- /dev/null +++ b/assets/system_security_plugins_jwtSecurity_README.md.B1LGJUHj.js @@ -0,0 +1,8 @@ +import{_ as t,c as o,a3 as i,o as s}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"system/security/plugins/jwtSecurity/README.md","filePath":"system/security/plugins/jwtSecurity/README.md","lastUpdated":1731340314000}'),a={name:"system/security/plugins/jwtSecurity/README.md"};function n(r,e,l,d,c,h){return s(),o("div",null,e[0]||(e[0]=[i(`

JWT Authorization Security Manager Plugin

The purpose of this plugin is to provide authentication and authorization capabilities for HPCC Systems users, with the credentials passed via valid JWT tokens.

The intention is to adhere as closely as possibly to the OpenID Connect (OIC) specification, which is a simple identity layer on top of the OAuth 2.0 protocol, while maintaining compatibility with the way HPCC Systems performs authentication and authorization today. More information about the OpenID Connect specification can be found at https://openid.net/specs/openid-connect-core-1_0.html.

One of the big advantages of OAuth 2.0 and OIC is that the service (in this case, HPCC Systems) never interacts with the user directly. Instead, authentication is performed by a trusted third party and the (successful) results are passed to the service in the form of a verifiable encoded token.

Unfortunately, HPCC Systems does not support the concept of third-party verification. It assumes that users -- really, any client application that operates as a user, including things like IDEs -- will submit username/password credentials for authentication. Until that is changed, HPCC Systems won't be able to fully adhere to the OIC specification.

We can, however, implement most of the specification. That is what this plugin does.

NOTE: This plugin is not available in a Windows build.

Code Documentation

Doxygen (https://www.doxygen.nl/index.html) can be used to create nice HTML documentation for the code. Call/caller graphs are also generated for functions if you have dot (https://www.graphviz.org/download/) installed and available on your path.

Assuming doxygen is on your path, you can build the documentation via:

cd system/security/plugins/jwtSecurity
+doxygen Doxyfile
+

The documentation can then be accessed via docs/html/index.html.

Theory of Operations

The plugin is called by the HPCC Systems esp process when a user needs to be authenticated. That call will contain the user's username and either a reference to a session token or a password. The session token is present only for already-authenticated users.

If the session token is not present, the plugin will call a JWT login service (also known as a JWT login endpoint) with the username and password, plus a nonce value for additional security.

That service authenticates the username/password credentials. If everything is good, the service constructs an OIC-compatible token that includes authorization information for that user and returns it to the plugin. The token is validated according to the OIC specification, including signature verification.

Note that token signature verification requires an additional piece of information. Tokens can be signed with a hash-based algorithm or with a public key-based algorithm (the actual algorithm used is determined by the JWT service). To verify either kind of algorithm, the plugin will need either the secret hash key or the public key that matches what the JWT service used. That key is read by the plugin from a file, and the file is determined by a configuration setting (see below). It is possible to change the contents of that file without restarting the esp process. Note, though, that the plugin may not notice that the file's contents have changed for several seconds (changes do not immediately take effect).

HPCC Systems uses a well-defined authorization scheme, originally designed around an LDAP implementation. That scheme is represented within the token as JWT claims. This plugin will unpack those claims and map to the authorization checks already in place within the HPCC Systems platform.

OIC includes the concept of refresh tokens. Refresh tokens enable a service to re-authorize an existing token without user intervention. Re-authorization typically happens due to a token expiring. Tokens should have a relatively short lifetime -- e.g. 15-30 minutes -- to promote good security and also give administrators the ability to modify a user's authorization while the user is logged in. This plugin fully supports refresh tokens by validating token lifetime at every authorization check and calling a JWT refresh service (also known as a JWT refresh endpoint) when needed. This largely follows the OIC specification.

Deviations From OIC Specification

  • Initial authentication: As stated above, the esp process will gather the username/password credentials instead of a third party, then send those credentials off to another service. In a true OIC configuration, the client process (the esp process) never sees user credentials and relies on an external service to gather them from the user.
  • The request made to the JWT login service is a POST HTTP or HTTPS call (depending on your configuration) containing four items in JSON format; example:
	{
+		"username": "my_username",
+		"password": "my_password",
+		"client_id": "https://myhpcccluster.com",
+		"nonce": "hf674DTRMd4Z1s"
+	}

Implications of Deviations

The most obvious outcome of this implementation is that a custom service/endpoint needs to be available. Or rather two services: One to handle the initial user login and one to handle token refreshes. Neither service precisely handles requests and replies in an OIC-compatible way, but the tokens themselves are OIC-compatible, which is good. That allows you to use third-party JWT libraries to construct and validate those tokens.

HPCC Systems Configuration Notes

Several items must be defined in the platform's configuration. Within configmgr, the jwtsecmgr Security Manager plugin must be added as a component and then modified according to your environment:

  • The URL or unique name of this HPCC Systems cluster, used as the client_id in token requests
  • Full URL to the JWT Login Endpoint (should be HTTPS, but not required)
  • Full URL to the JWT Refresh Endpoint (should be HTTPS, but not required)
  • Boolean indicating whether to accept self-signed certificates for those endpoints; defaults to false
  • Secrets vault key/name or subdirectory under /opt/HPCCSystems/secrets/esp in which the JWT key used for the chosen signature algorithm is stored; defaults to "jwt-security"
  • Default permission access level (either "Full" or "None"); defaults to "Full"
  • Default workunit scope access level (either "Full" or "None"); defaults to "Full"
  • Default file scope access level (either "Full" or "None"); defaults to "Full"

Only the first three items have no default values and must be supplied.

Once the jwtseccmgr component is added, you have to tell other parts of the system to use the plugin. For user authentication and permissions affecting features and workunit scopes, you need to add the plugin to the esp component. Instructions for doing so can be found in the HPCC Systems Administrator's Guide manual (though the manual uses the htpasswd plugin as an example, the process is the same).

If you intend to implement file scope permissions then you will also need provide Dali information about the JWT plugin. In configmgr, within the Dali Server component, select the LDAP tab. Change the authMethod entry to secmgrPlugin and enter "jwtsecmgr" as the authPluginType. Make sure checkScopeScans is set to true.

HPCC Systems Authorization and JWT Claims

This plugin supports all authorizations documented in the HPCC Systems® Administrator's Guide with the exception of "View Permissions". Loosely speaking, the permissions are divided into three groups: Feature, Workunit Scope, and File Scope.

Feature permissions are supported exactly as documented. A specific permission would exist as a JWT claim, by name, with the associated value being the name of the permission. For example, to grant read-only access to ECL Watch, use this claim:

	{ "SmcAccess": "Read" }

File and workunit scope permissions are handled the same way, but different from feature permissions. The claim is one of the Claim constants in the tables below, and the associated value is a matching pattern. A pattern can be simple string or it can use wildcards (specifically, Linux's file globbing wildcards). Wildcards are not typically needed.

Multiple patterns can be set for each claim.

Workunit Scope Permissions

MeaningClaimValue
User has view rights to workunit scopeAllowWorkunitScopeViewpattern
User has modify rights to workunit scopeAllowWorkunitScopeModifypattern
User has delete rights to workunit scopeAllowWorkunitScopeDeletepattern
User does not have view rights to workunit scopeDenyWorkunitScopeViewpattern
User does not have modify rights to workunit scopeDenyWorkunitScopeModifypattern
User does not have delete rights to workunit scopeDenyWorkunitScopeDeletepattern

File Scope Permissions

MeaningClaimValue
User has view rights to file scopeAllowFileScopeViewpattern
User has modify rights to file scopeAllowFileScopeModifypattern
User has delete rights to file scopeAllowFileScopeDeletepattern
User does not have view rights to file scopeDenyFileScopeViewpattern
User does not have modify rights to file scopeDenyFileScopeModifypattern
User does not have delete rights to file scopeDenyFileScopeDeletepattern
`,41)]))}const m=t(a,[["render",n]]);export{u as __pageData,m as default}; diff --git a/assets/system_security_plugins_jwtSecurity_README.md.B1LGJUHj.lean.js b/assets/system_security_plugins_jwtSecurity_README.md.B1LGJUHj.lean.js new file mode 100644 index 00000000000..8ad890ce01f --- /dev/null +++ b/assets/system_security_plugins_jwtSecurity_README.md.B1LGJUHj.lean.js @@ -0,0 +1,8 @@ +import{_ as t,c as o,a3 as i,o as s}from"./chunks/framework.DkhCEVKm.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"system/security/plugins/jwtSecurity/README.md","filePath":"system/security/plugins/jwtSecurity/README.md","lastUpdated":1731340314000}'),a={name:"system/security/plugins/jwtSecurity/README.md"};function n(r,e,l,d,c,h){return s(),o("div",null,e[0]||(e[0]=[i(`

JWT Authorization Security Manager Plugin

The purpose of this plugin is to provide authentication and authorization capabilities for HPCC Systems users, with the credentials passed via valid JWT tokens.

The intention is to adhere as closely as possibly to the OpenID Connect (OIC) specification, which is a simple identity layer on top of the OAuth 2.0 protocol, while maintaining compatibility with the way HPCC Systems performs authentication and authorization today. More information about the OpenID Connect specification can be found at https://openid.net/specs/openid-connect-core-1_0.html.

One of the big advantages of OAuth 2.0 and OIC is that the service (in this case, HPCC Systems) never interacts with the user directly. Instead, authentication is performed by a trusted third party and the (successful) results are passed to the service in the form of a verifiable encoded token.

Unfortunately, HPCC Systems does not support the concept of third-party verification. It assumes that users -- really, any client application that operates as a user, including things like IDEs -- will submit username/password credentials for authentication. Until that is changed, HPCC Systems won't be able to fully adhere to the OIC specification.

We can, however, implement most of the specification. That is what this plugin does.

NOTE: This plugin is not available in a Windows build.

Code Documentation

Doxygen (https://www.doxygen.nl/index.html) can be used to create nice HTML documentation for the code. Call/caller graphs are also generated for functions if you have dot (https://www.graphviz.org/download/) installed and available on your path.

Assuming doxygen is on your path, you can build the documentation via:

cd system/security/plugins/jwtSecurity
+doxygen Doxyfile
+

The documentation can then be accessed via docs/html/index.html.

Theory of Operations

The plugin is called by the HPCC Systems esp process when a user needs to be authenticated. That call will contain the user's username and either a reference to a session token or a password. The session token is present only for already-authenticated users.

If the session token is not present, the plugin will call a JWT login service (also known as a JWT login endpoint) with the username and password, plus a nonce value for additional security.

That service authenticates the username/password credentials. If everything is good, the service constructs an OIC-compatible token that includes authorization information for that user and returns it to the plugin. The token is validated according to the OIC specification, including signature verification.

Note that token signature verification requires an additional piece of information. Tokens can be signed with a hash-based algorithm or with a public key-based algorithm (the actual algorithm used is determined by the JWT service). To verify either kind of algorithm, the plugin will need either the secret hash key or the public key that matches what the JWT service used. That key is read by the plugin from a file, and the file is determined by a configuration setting (see below). It is possible to change the contents of that file without restarting the esp process. Note, though, that the plugin may not notice that the file's contents have changed for several seconds (changes do not immediately take effect).

HPCC Systems uses a well-defined authorization scheme, originally designed around an LDAP implementation. That scheme is represented within the token as JWT claims. This plugin will unpack those claims and map to the authorization checks already in place within the HPCC Systems platform.

OIC includes the concept of refresh tokens. Refresh tokens enable a service to re-authorize an existing token without user intervention. Re-authorization typically happens due to a token expiring. Tokens should have a relatively short lifetime -- e.g. 15-30 minutes -- to promote good security and also give administrators the ability to modify a user's authorization while the user is logged in. This plugin fully supports refresh tokens by validating token lifetime at every authorization check and calling a JWT refresh service (also known as a JWT refresh endpoint) when needed. This largely follows the OIC specification.

Deviations From OIC Specification

  • Initial authentication: As stated above, the esp process will gather the username/password credentials instead of a third party, then send those credentials off to another service. In a true OIC configuration, the client process (the esp process) never sees user credentials and relies on an external service to gather them from the user.
  • The request made to the JWT login service is a POST HTTP or HTTPS call (depending on your configuration) containing four items in JSON format; example:
	{
+		"username": "my_username",
+		"password": "my_password",
+		"client_id": "https://myhpcccluster.com",
+		"nonce": "hf674DTRMd4Z1s"
+	}

Implications of Deviations

The most obvious outcome of this implementation is that a custom service/endpoint needs to be available. Or rather two services: One to handle the initial user login and one to handle token refreshes. Neither service precisely handles requests and replies in an OIC-compatible way, but the tokens themselves are OIC-compatible, which is good. That allows you to use third-party JWT libraries to construct and validate those tokens.

HPCC Systems Configuration Notes

Several items must be defined in the platform's configuration. Within configmgr, the jwtsecmgr Security Manager plugin must be added as a component and then modified according to your environment:

  • The URL or unique name of this HPCC Systems cluster, used as the client_id in token requests
  • Full URL to the JWT Login Endpoint (should be HTTPS, but not required)
  • Full URL to the JWT Refresh Endpoint (should be HTTPS, but not required)
  • Boolean indicating whether to accept self-signed certificates for those endpoints; defaults to false
  • Secrets vault key/name or subdirectory under /opt/HPCCSystems/secrets/esp in which the JWT key used for the chosen signature algorithm is stored; defaults to "jwt-security"
  • Default permission access level (either "Full" or "None"); defaults to "Full"
  • Default workunit scope access level (either "Full" or "None"); defaults to "Full"
  • Default file scope access level (either "Full" or "None"); defaults to "Full"

Only the first three items have no default values and must be supplied.

Once the jwtseccmgr component is added, you have to tell other parts of the system to use the plugin. For user authentication and permissions affecting features and workunit scopes, you need to add the plugin to the esp component. Instructions for doing so can be found in the HPCC Systems Administrator's Guide manual (though the manual uses the htpasswd plugin as an example, the process is the same).

If you intend to implement file scope permissions then you will also need provide Dali information about the JWT plugin. In configmgr, within the Dali Server component, select the LDAP tab. Change the authMethod entry to secmgrPlugin and enter "jwtsecmgr" as the authPluginType. Make sure checkScopeScans is set to true.

HPCC Systems Authorization and JWT Claims

This plugin supports all authorizations documented in the HPCC Systems® Administrator's Guide with the exception of "View Permissions". Loosely speaking, the permissions are divided into three groups: Feature, Workunit Scope, and File Scope.

Feature permissions are supported exactly as documented. A specific permission would exist as a JWT claim, by name, with the associated value being the name of the permission. For example, to grant read-only access to ECL Watch, use this claim:

	{ "SmcAccess": "Read" }

File and workunit scope permissions are handled the same way, but different from feature permissions. The claim is one of the Claim constants in the tables below, and the associated value is a matching pattern. A pattern can be simple string or it can use wildcards (specifically, Linux's file globbing wildcards). Wildcards are not typically needed.

Multiple patterns can be set for each claim.

Workunit Scope Permissions

MeaningClaimValue
User has view rights to workunit scopeAllowWorkunitScopeViewpattern
User has modify rights to workunit scopeAllowWorkunitScopeModifypattern
User has delete rights to workunit scopeAllowWorkunitScopeDeletepattern
User does not have view rights to workunit scopeDenyWorkunitScopeViewpattern
User does not have modify rights to workunit scopeDenyWorkunitScopeModifypattern
User does not have delete rights to workunit scopeDenyWorkunitScopeDeletepattern

File Scope Permissions

MeaningClaimValue
User has view rights to file scopeAllowFileScopeViewpattern
User has modify rights to file scopeAllowFileScopeModifypattern
User has delete rights to file scopeAllowFileScopeDeletepattern
User does not have view rights to file scopeDenyFileScopeViewpattern
User does not have modify rights to file scopeDenyFileScopeModifypattern
User does not have delete rights to file scopeDenyFileScopeDeletepattern
`,41)]))}const m=t(a,[["render",n]]);export{u as __pageData,m as default}; diff --git a/assets/testing_regress_cleanupReadme.md.BWXfHXn3.js b/assets/testing_regress_cleanupReadme.md.BWXfHXn3.js new file mode 100644 index 00000000000..bceac01f3a4 --- /dev/null +++ b/assets/testing_regress_cleanupReadme.md.BWXfHXn3.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as r,o as s}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Cleanup Parameter of Regression Suite run and query sub-command","description":"","frontmatter":{},"headers":[],"relativePath":"testing/regress/cleanupReadme.md","filePath":"testing/regress/cleanupReadme.md","lastUpdated":1731340314000}'),o={name:"testing/regress/cleanupReadme.md"};function i(n,e,l,u,d,c){return s(),a("div",null,e[0]||(e[0]=[r('

Cleanup Parameter of Regression Suite run and query sub-command

The cleanup parameter has been introduced to allow the user to automatically delete the workunits created by executing the Regression Suite on their local system.

It is an optional argument of the run and query sub-command.

A custom logging system also creates log files for each execution of the run and query sub-command that contains information about the workunit deletion.

Command:

./ecl-test run --cleanup [mode]

./ecl-test query --cleanup [mode]

Modes allowed are ‘workunits’, ‘passed’. Default is ‘none’.

  • workunits - all passed and failed workunits are deleted.

  • passed - only the passed workunits of the queries executed are deleted.

  • none - no workunits created during the current run command are deleted.

Result:

The sample terminal output for hthor target:

./ecl-test query ECL_query action1.ecl action2.ecl action4.ecl action5.ecl -t hthor --cleanup workunits

[Action] Suite: hthor
[Action] Queries: 4
[Action] 1. Test: action1.ecl
[Pass] 1. Pass action1.ecl - W20240526-094322 (2 sec)
[Pass] 1. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094322
[Action] 2. Test: action2.ecl
[Pass] 2. Pass action2.ecl - W20240526-094324 (2 sec)
[Pass] 2. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094324
[Action] 3. Test: action4.ecl
[Pass] 3. Pass action4.ecl - W20240526-094325 (2 sec)
[Pass] 3. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094325
[Action] 4. Test: action5.ecl
[Pass] 4. Pass action5.ecl - W20240526-094327 (2 sec)
[Pass] 4. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094327
[Action]
-------------------------------------------------
Result:
Passing: 4
Failure: 0
-------------------------------------------------
Log: /root/HPCCSystems-regression/log/hthor.24-05-26-09-43-22.log
-------------------------------------------------
Elapsed time: 11 sec (00:00:11)
-------------------------------------------------

[Action] Automatic Cleanup Routine

[Pass] 1. Workunit Wuid=W20240526-094322 deleted successfully.
[Pass] 2. Workunit Wuid=W20240526-094324 deleted successfully.
[Failure] 3. Failed to delete Wuid=W20240526-094325. URL: http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094325
Failed cannot open workunit Wuid=W20240526-094325.. Response status code: 200
[Pass] 4. Workunit Wuid=W20240526-094327 deleted successfully.
Suite destructor.

',15)]))}const h=t(o,[["render",i]]);export{m as __pageData,h as default}; diff --git a/assets/testing_regress_cleanupReadme.md.BWXfHXn3.lean.js b/assets/testing_regress_cleanupReadme.md.BWXfHXn3.lean.js new file mode 100644 index 00000000000..bceac01f3a4 --- /dev/null +++ b/assets/testing_regress_cleanupReadme.md.BWXfHXn3.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,a3 as r,o as s}from"./chunks/framework.DkhCEVKm.js";const m=JSON.parse('{"title":"Cleanup Parameter of Regression Suite run and query sub-command","description":"","frontmatter":{},"headers":[],"relativePath":"testing/regress/cleanupReadme.md","filePath":"testing/regress/cleanupReadme.md","lastUpdated":1731340314000}'),o={name:"testing/regress/cleanupReadme.md"};function i(n,e,l,u,d,c){return s(),a("div",null,e[0]||(e[0]=[r('

Cleanup Parameter of Regression Suite run and query sub-command

The cleanup parameter has been introduced to allow the user to automatically delete the workunits created by executing the Regression Suite on their local system.

It is an optional argument of the run and query sub-command.

A custom logging system also creates log files for each execution of the run and query sub-command that contains information about the workunit deletion.

Command:

./ecl-test run --cleanup [mode]

./ecl-test query --cleanup [mode]

Modes allowed are ‘workunits’, ‘passed’. Default is ‘none’.

  • workunits - all passed and failed workunits are deleted.

  • passed - only the passed workunits of the queries executed are deleted.

  • none - no workunits created during the current run command are deleted.

Result:

The sample terminal output for hthor target:

./ecl-test query ECL_query action1.ecl action2.ecl action4.ecl action5.ecl -t hthor --cleanup workunits

[Action] Suite: hthor
[Action] Queries: 4
[Action] 1. Test: action1.ecl
[Pass] 1. Pass action1.ecl - W20240526-094322 (2 sec)
[Pass] 1. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094322
[Action] 2. Test: action2.ecl
[Pass] 2. Pass action2.ecl - W20240526-094324 (2 sec)
[Pass] 2. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094324
[Action] 3. Test: action4.ecl
[Pass] 3. Pass action4.ecl - W20240526-094325 (2 sec)
[Pass] 3. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094325
[Action] 4. Test: action5.ecl
[Pass] 4. Pass action5.ecl - W20240526-094327 (2 sec)
[Pass] 4. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094327
[Action]
-------------------------------------------------
Result:
Passing: 4
Failure: 0
-------------------------------------------------
Log: /root/HPCCSystems-regression/log/hthor.24-05-26-09-43-22.log
-------------------------------------------------
Elapsed time: 11 sec (00:00:11)
-------------------------------------------------

[Action] Automatic Cleanup Routine

[Pass] 1. Workunit Wuid=W20240526-094322 deleted successfully.
[Pass] 2. Workunit Wuid=W20240526-094324 deleted successfully.
[Failure] 3. Failed to delete Wuid=W20240526-094325. URL: http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094325
Failed cannot open workunit Wuid=W20240526-094325.. Response status code: 200
[Pass] 4. Workunit Wuid=W20240526-094327 deleted successfully.
Suite destructor.

',15)]))}const h=t(o,[["render",i]]);export{m as __pageData,h as default}; diff --git a/assets/testing_regress_ecl_README.md.DWXau1FL.js b/assets/testing_regress_ecl_README.md.DWXau1FL.js new file mode 100644 index 00000000000..45314c1a929 --- /dev/null +++ b/assets/testing_regress_ecl_README.md.DWXau1FL.js @@ -0,0 +1,64 @@ +import{_ as e,c as a,a3 as n,o as t}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Test Suite for the Parquet Plugin","description":"","frontmatter":{},"headers":[],"relativePath":"testing/regress/ecl/README.md","filePath":"testing/regress/ecl/README.md","lastUpdated":1731340314000}'),i={name:"testing/regress/ecl/README.md"};function p(l,s,r,o,c,u){return t(),a("div",null,s[0]||(s[0]=[n(`

Test Suite for the Parquet Plugin

Running the Test Suite

The Parquet plugin test suite is a subset of tests in the HPCC Systems regression suite. To run the tests:

Change directory to HPCC Platform/testing/regress.

To run the entire Parquet test suite:

Note: Some Parquet tests require initialization of Parquet files. Use the ./ecl-test setupcommand to initialize these files before running the test suite.

These commands can be run on any cluster, including hthor or Roxie like the example below.

./ecl-test query --runclass parquet parquet*.ecl

To run a single test file:

./ecl-test query --runclass parquet <test_file_name>.ecl
+
+example below:
+
+/ecl-test query --target hthor --runclass parquet  parquet_schema.ecl

On the roxie cluster:

./ecl-test query --target roxie --runclass parquet parquet*.ecl

This is what you should see when you run the command above:

[Action] Suite: roxie
+[Action] Queries: 15
+[Action]   1. Test: parquet_compress.ecl ( version: compressionType='UNCOMPRESSED' )
+[Pass]   1. Pass parquet_compress.ecl ( version: compressionType='UNCOMPRESSED' ) - W20240815-111429 (4 sec)
+[Pass]   1. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111429
+[Action]   2. Test: parquet_compress.ecl ( version: compressionType='Snappy' )
+[Pass]   2. Pass parquet_compress.ecl ( version: compressionType='Snappy' ) - W20240815-111434 (3 sec)
+[Pass]   2. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111434
+[Action]   3. Test: parquet_compress.ecl ( version: compressionType='GZip' )
+[Pass]   3. Pass parquet_compress.ecl ( version: compressionType='GZip' ) - W20240815-111438 (4 sec)
+[Pass]   3. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111438
+[Action]   4. Test: parquet_compress.ecl ( version: compressionType='Brotli' )
+[Pass]   4. Pass parquet_compress.ecl ( version: compressionType='Brotli' ) - W20240815-111442 (4 sec)
+[Pass]   4. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111442
+[Action]   5. Test: parquet_compress.ecl ( version: compressionType='LZ4' )
+[Pass]   5. Pass parquet_compress.ecl ( version: compressionType='LZ4' ) - W20240815-111447 (3 sec)
+[Pass]   5. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111447
+[Action]   6. Test: parquet_compress.ecl ( version: compressionType='ZSTD' )
+[Pass]   6. Pass parquet_compress.ecl ( version: compressionType='ZSTD' ) - W20240815-111450 (2 sec)
+[Pass]   6. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111450
+[Action]   7. Test: parquet_corrupt.ecl
+[Pass]   7. Pass parquet_corrupt.ecl - W20240815-111453 (2 sec)
+[Pass]   7. Intentionally fails
+[Pass]   7. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111453
+[Action]   8. Test: parquet_empty.ecl
+[Pass]   8. Pass parquet_empty.ecl - W20240815-111455 (2 sec)
+[Pass]   8. Intentionally fails
+[Pass]   8. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111455
+[Action]   9. Test: parquet_overwrite.ecl
+[Pass]   9. Pass parquet_overwrite.ecl - W20240815-111457 (2 sec)
+[Pass]   9. Intentionally fails
+[Pass]   9. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111457
+[Action]  10. Test: parquet_partition.ecl
+[Pass]  10. Pass parquet_partition.ecl - W20240815-111459 (2 sec)
+[Pass]  10. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111459
+[Action]  11. Test: parquet_schema.ecl
+[Pass]  11. Pass parquet_schema.ecl - W20240815-111502 (1 sec)
+[Pass]  11. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111502
+[Action]  12. Test: parquet_size.ecl
+[Pass]  12. Pass parquet_size.ecl - W20240815-111504 (3 sec)
+[Pass]  12. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111504
+[Action]  13. Test: parquet_string.ecl
+[Pass]  13. Pass parquet_string.ecl - W20240815-111507 (1 sec)
+[Pass]  13. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111507
+[Action]  14. Test: parquet_types.ecl
+[Pass]  14. Pass parquet_types.ecl - W20240815-111509 (7 sec)
+[Pass]  14. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111509
+[Action]  15. Test: parquet_write.ecl
+[Pass]  15. Pass parquet_write.ecl - W20240815-111517 (2 sec)
+[Pass]  15. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111517
+[Action]
+    -------------------------------------------------
+    Result:
+    Passing: 15
+    Failure: 0
+    -------------------------------------------------
+    Log: /home/user/HPCCSystems-regression/log/roxie.24-08-15-11-14-29.log
+    -------------------------------------------------
+    Elapsed time: 52 sec  (00:00:52)
+    -------------------------------------------------

Project Description

This project focuses on the development of a comprehensive test suite for the recently integrated Parquet plugin within the HPCC Systems platform. The objective is to thoroughly evaluate the plugin's functionality, performance, and robustness across different scenarios and configurations. The key deliverables include defining and implementing various test cases, fixing any identified bugs, and providing extensive documentation.

The test suite will evaluate all data types supported by ECL and Arrow, as well as file operations, various compression formats, and schema handling. Additionally, the test suite will measure the plugin's performance across different HPCC components and hardware configurations, conduct stress tests to identify potential bottlenecks and bugs, and compare Parquet to other file formats used in the ecosystem, such as JSON, XML, and CSV.

Test Files

The test suite consists of 10 main test files that test different parquet functionality and operations:

  • parquet_types.ecl: Tests various ECL and Arrow data types

  • parquet_schema.ecl: Evaluates Parquet's handling of different schemas

  • parquet_compress.ecl: Tests different compression algorithms

  • parquet_write.ecl: Validates Parquet write operations

  • parquet_empty.ecl: Tests behavior with empty Parquet files

  • parquet_corrupt.ecl: Checks handling of corrupt Parquet data

  • parquet_size.ecl: Compares file sizes across formats

  • parquet_partition.ecl: Tests partitioning in Parquet files

  • parquet_overwrite.ecl: Validates overwrite operations

  • parquet-string.ecl: Focuses on string-related operations

Test Suite Overview

Type Testing

Covers 42 data types including ECL and Arrow types Examples: BOOLEAN, INTEGER, STRING, UNICODE, various numeric types, sets, and Arrow-specific types

Data Type Tests

The Parquet plugin test suite shows that the plugin supports all ECL types.

Arrow Types Supported by the Parquet Plugin

  • null
  • uint8
  • int8
  • uint16
  • int16
  • uint32
  • int32
  • uint64
  • int64
  • half_float
  • float
  • double
  • string
  • binary
  • fixed_size_binary
  • date32
  • date64
  • timestamp
  • time32
  • time64
  • interval_months
  • list
  • decimal
  • large_list
  • interval_day_time

Compression Testing

Tests all available Arrow compression types: Snappy, GZip, Brotli, LZ4, ZSTD, Uncompressed Compares performance and file sizes for different compression options

Parquet Read and Write Operations

Tests ParquetIO.Read for creating ECL datasets from Parquet files Tests ParquetIO.Write for writing ECL datasets to Parquet files

Additional Tests

Read and Write Speeds Comparison with Other File Types Schema Handling and Compatibility Behavior with Corrupt Data and Empty Parquet Files

Test Evaluation

The test suite generally uses key files located in HPCC-Platform/testing/regress/ecl/key, with a ".xml" extension, to evaluate test outcomes. These files store the expected results for comparison. However, some Parquet tests do not rely on key files, and alternative evaluation methods are used in those cases.

`,35)]))}const m=e(i,[["render",p]]);export{h as __pageData,m as default}; diff --git a/assets/testing_regress_ecl_README.md.DWXau1FL.lean.js b/assets/testing_regress_ecl_README.md.DWXau1FL.lean.js new file mode 100644 index 00000000000..45314c1a929 --- /dev/null +++ b/assets/testing_regress_ecl_README.md.DWXau1FL.lean.js @@ -0,0 +1,64 @@ +import{_ as e,c as a,a3 as n,o as t}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Test Suite for the Parquet Plugin","description":"","frontmatter":{},"headers":[],"relativePath":"testing/regress/ecl/README.md","filePath":"testing/regress/ecl/README.md","lastUpdated":1731340314000}'),i={name:"testing/regress/ecl/README.md"};function p(l,s,r,o,c,u){return t(),a("div",null,s[0]||(s[0]=[n(`

Test Suite for the Parquet Plugin

Running the Test Suite

The Parquet plugin test suite is a subset of tests in the HPCC Systems regression suite. To run the tests:

Change directory to HPCC Platform/testing/regress.

To run the entire Parquet test suite:

Note: Some Parquet tests require initialization of Parquet files. Use the ./ecl-test setupcommand to initialize these files before running the test suite.

These commands can be run on any cluster, including hthor or Roxie like the example below.

./ecl-test query --runclass parquet parquet*.ecl

To run a single test file:

./ecl-test query --runclass parquet <test_file_name>.ecl
+
+example below:
+
+/ecl-test query --target hthor --runclass parquet  parquet_schema.ecl

On the roxie cluster:

./ecl-test query --target roxie --runclass parquet parquet*.ecl

This is what you should see when you run the command above:

[Action] Suite: roxie
+[Action] Queries: 15
+[Action]   1. Test: parquet_compress.ecl ( version: compressionType='UNCOMPRESSED' )
+[Pass]   1. Pass parquet_compress.ecl ( version: compressionType='UNCOMPRESSED' ) - W20240815-111429 (4 sec)
+[Pass]   1. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111429
+[Action]   2. Test: parquet_compress.ecl ( version: compressionType='Snappy' )
+[Pass]   2. Pass parquet_compress.ecl ( version: compressionType='Snappy' ) - W20240815-111434 (3 sec)
+[Pass]   2. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111434
+[Action]   3. Test: parquet_compress.ecl ( version: compressionType='GZip' )
+[Pass]   3. Pass parquet_compress.ecl ( version: compressionType='GZip' ) - W20240815-111438 (4 sec)
+[Pass]   3. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111438
+[Action]   4. Test: parquet_compress.ecl ( version: compressionType='Brotli' )
+[Pass]   4. Pass parquet_compress.ecl ( version: compressionType='Brotli' ) - W20240815-111442 (4 sec)
+[Pass]   4. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111442
+[Action]   5. Test: parquet_compress.ecl ( version: compressionType='LZ4' )
+[Pass]   5. Pass parquet_compress.ecl ( version: compressionType='LZ4' ) - W20240815-111447 (3 sec)
+[Pass]   5. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111447
+[Action]   6. Test: parquet_compress.ecl ( version: compressionType='ZSTD' )
+[Pass]   6. Pass parquet_compress.ecl ( version: compressionType='ZSTD' ) - W20240815-111450 (2 sec)
+[Pass]   6. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111450
+[Action]   7. Test: parquet_corrupt.ecl
+[Pass]   7. Pass parquet_corrupt.ecl - W20240815-111453 (2 sec)
+[Pass]   7. Intentionally fails
+[Pass]   7. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111453
+[Action]   8. Test: parquet_empty.ecl
+[Pass]   8. Pass parquet_empty.ecl - W20240815-111455 (2 sec)
+[Pass]   8. Intentionally fails
+[Pass]   8. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111455
+[Action]   9. Test: parquet_overwrite.ecl
+[Pass]   9. Pass parquet_overwrite.ecl - W20240815-111457 (2 sec)
+[Pass]   9. Intentionally fails
+[Pass]   9. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111457
+[Action]  10. Test: parquet_partition.ecl
+[Pass]  10. Pass parquet_partition.ecl - W20240815-111459 (2 sec)
+[Pass]  10. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111459
+[Action]  11. Test: parquet_schema.ecl
+[Pass]  11. Pass parquet_schema.ecl - W20240815-111502 (1 sec)
+[Pass]  11. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111502
+[Action]  12. Test: parquet_size.ecl
+[Pass]  12. Pass parquet_size.ecl - W20240815-111504 (3 sec)
+[Pass]  12. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111504
+[Action]  13. Test: parquet_string.ecl
+[Pass]  13. Pass parquet_string.ecl - W20240815-111507 (1 sec)
+[Pass]  13. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111507
+[Action]  14. Test: parquet_types.ecl
+[Pass]  14. Pass parquet_types.ecl - W20240815-111509 (7 sec)
+[Pass]  14. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111509
+[Action]  15. Test: parquet_write.ecl
+[Pass]  15. Pass parquet_write.ecl - W20240815-111517 (2 sec)
+[Pass]  15. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111517
+[Action]
+    -------------------------------------------------
+    Result:
+    Passing: 15
+    Failure: 0
+    -------------------------------------------------
+    Log: /home/user/HPCCSystems-regression/log/roxie.24-08-15-11-14-29.log
+    -------------------------------------------------
+    Elapsed time: 52 sec  (00:00:52)
+    -------------------------------------------------

Project Description

This project focuses on the development of a comprehensive test suite for the recently integrated Parquet plugin within the HPCC Systems platform. The objective is to thoroughly evaluate the plugin's functionality, performance, and robustness across different scenarios and configurations. The key deliverables include defining and implementing various test cases, fixing any identified bugs, and providing extensive documentation.

The test suite will evaluate all data types supported by ECL and Arrow, as well as file operations, various compression formats, and schema handling. Additionally, the test suite will measure the plugin's performance across different HPCC components and hardware configurations, conduct stress tests to identify potential bottlenecks and bugs, and compare Parquet to other file formats used in the ecosystem, such as JSON, XML, and CSV.

Test Files

The test suite consists of 10 main test files that test different parquet functionality and operations:

  • parquet_types.ecl: Tests various ECL and Arrow data types

  • parquet_schema.ecl: Evaluates Parquet's handling of different schemas

  • parquet_compress.ecl: Tests different compression algorithms

  • parquet_write.ecl: Validates Parquet write operations

  • parquet_empty.ecl: Tests behavior with empty Parquet files

  • parquet_corrupt.ecl: Checks handling of corrupt Parquet data

  • parquet_size.ecl: Compares file sizes across formats

  • parquet_partition.ecl: Tests partitioning in Parquet files

  • parquet_overwrite.ecl: Validates overwrite operations

  • parquet-string.ecl: Focuses on string-related operations

Test Suite Overview

Type Testing

Covers 42 data types including ECL and Arrow types Examples: BOOLEAN, INTEGER, STRING, UNICODE, various numeric types, sets, and Arrow-specific types

Data Type Tests

The Parquet plugin test suite shows that the plugin supports all ECL types.

Arrow Types Supported by the Parquet Plugin

  • null
  • uint8
  • int8
  • uint16
  • int16
  • uint32
  • int32
  • uint64
  • int64
  • half_float
  • float
  • double
  • string
  • binary
  • fixed_size_binary
  • date32
  • date64
  • timestamp
  • time32
  • time64
  • interval_months
  • list
  • decimal
  • large_list
  • interval_day_time

Compression Testing

Tests all available Arrow compression types: Snappy, GZip, Brotli, LZ4, ZSTD, Uncompressed Compares performance and file sizes for different compression options

Parquet Read and Write Operations

Tests ParquetIO.Read for creating ECL datasets from Parquet files Tests ParquetIO.Write for writing ECL datasets to Parquet files

Additional Tests

Read and Write Speeds Comparison with Other File Types Schema Handling and Compatibility Behavior with Corrupt Data and Empty Parquet Files

Test Evaluation

The test suite generally uses key files located in HPCC-Platform/testing/regress/ecl/key, with a ".xml" extension, to evaluate test outcomes. These files store the expected results for comparison. However, some Parquet tests do not rely on key files, and alternative evaluation methods are used in those cases.

`,35)]))}const m=e(i,[["render",p]]);export{h as __pageData,m as default}; diff --git a/assets/testing_regress_ecl_analyzers_corporate_tmp_README.md.KZkrkEkE.js b/assets/testing_regress_ecl_analyzers_corporate_tmp_README.md.KZkrkEkE.js new file mode 100644 index 00000000000..aa07087ee84 --- /dev/null +++ b/assets/testing_regress_ecl_analyzers_corporate_tmp_README.md.KZkrkEkE.js @@ -0,0 +1 @@ +import{_ as t,c as r,j as s,o as a}from"./chunks/framework.DkhCEVKm.js";const f=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"testing/regress/ecl/analyzers/corporate/tmp/README.md","filePath":"testing/regress/ecl/analyzers/corporate/tmp/README.md","lastUpdated":1731340314000}'),o={name:"testing/regress/ecl/analyzers/corporate/tmp/README.md"};function n(i,e,p,c,l,m){return a(),r("div",null,e[0]||(e[0]=[s("p",null,"This directory is for the function kbdumptree. The engine should create this but sometimes permission problems arrise.",-1)]))}const u=t(o,[["render",n]]);export{f as __pageData,u as default}; diff --git a/assets/testing_regress_ecl_analyzers_corporate_tmp_README.md.KZkrkEkE.lean.js b/assets/testing_regress_ecl_analyzers_corporate_tmp_README.md.KZkrkEkE.lean.js new file mode 100644 index 00000000000..aa07087ee84 --- /dev/null +++ b/assets/testing_regress_ecl_analyzers_corporate_tmp_README.md.KZkrkEkE.lean.js @@ -0,0 +1 @@ +import{_ as t,c as r,j as s,o as a}from"./chunks/framework.DkhCEVKm.js";const f=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"testing/regress/ecl/analyzers/corporate/tmp/README.md","filePath":"testing/regress/ecl/analyzers/corporate/tmp/README.md","lastUpdated":1731340314000}'),o={name:"testing/regress/ecl/analyzers/corporate/tmp/README.md"};function n(i,e,p,c,l,m){return a(),r("div",null,e[0]||(e[0]=[s("p",null,"This directory is for the function kbdumptree. The engine should create this but sometimes permission problems arrise.",-1)]))}const u=t(o,[["render",n]]);export{f as __pageData,u as default}; diff --git a/assets/tools_esdlcmd_README.md.D3nMaAu6.js b/assets/tools_esdlcmd_README.md.D3nMaAu6.js new file mode 100644 index 00000000000..0ccc7f07d89 --- /dev/null +++ b/assets/tools_esdlcmd_README.md.D3nMaAu6.js @@ -0,0 +1,307 @@ +import{_ as i,c as t,a3 as a,o as n}from"./chunks/framework.DkhCEVKm.js";const E=JSON.parse('{"title":"esdl Utility","description":"","frontmatter":{},"headers":[],"relativePath":"tools/esdlcmd/README.md","filePath":"tools/esdlcmd/README.md","lastUpdated":1731340314000}'),e={name:"tools/esdlcmd/README.md"};function l(h,s,p,k,o,d){return n(),t("div",null,s[0]||(s[0]=[a(`

esdl Utility

The esdl utility tool aids with creating and managing ESDL-based and Dynamic ESDL services on an HPCC cluster. It consists of several different commands.

To generate output from an ESDL definition:

xml               Generate XML from ESDL definition.
+ecl               Generate ECL from ESDL definition.
+xsd               Generate XSD from ESDL definition.
+wsdl              Generate WSDL from ESDL definition.
+java              Generate Java code from ESDL definition.
+cpp               Generate C++ code from ESDL definition.
+monitor           Generate ECL code for result monitoring / differencing
+monitor-template  Generate a template for use with 'monitor' command
+

To manage ESDL and DESDL services:

publish               Publish ESDL Definition for ESP use.
+list-definitions      List all ESDL definitions.
+get-definition        Get ESDL definition.
+delete                Delete ESDL Definition.
+bind-service          Configure ESDL based service on target ESP (with existing ESP Binding).
+list-bindings         List all ESDL bindings.
+unbind-service        Remove ESDL based service binding on target ESP.
+bind-method           Configure method associated with existing ESDL binding.
+unbind-method         Remove method associated with existing ESDL binding.
+get-binding           Get ESDL binding.
+manifest              Build a service binding or bundle from a manifest file.
+bind-log-transform    Configure log transform associated with existing ESDL binding.
+unbind-log-transform  Remove log transform associated with existing ESDL binding.
+

The sections below cover the commands in more detail.

manifest

The manifest command creates an XML configuration file for an ESDL ESP from an input XML manifest file. The type of configuration output depends on the manifest file input and on command-line options.

Manifest File

A manifest file is an XML-formatted template combining elements in and outside of the manifest's urn:hpcc:esdl:manifest namespace. Recognized elements of this namespace control the tool while all other markup is copied to the output. The goal of using a manifest file with the tool is to make configuring and deploying services easier:

  1. The manifest file format abstracts some of the complexity of the actual configuration.
  2. By allowing you to include external files like ESDL Scripts and XSLTs into the ouput, you can store and maintain them separately in your repo.

The result of running the manifest tool on a manifest file is an XML artifact suitable for use with the ESDL ESP. Supported output includes:

  • binding: The output is an ESDL binding that may be published to dali.
  • bundle: The output is an ESDL bundle file that may be used to launch an ESP in application mode.

Example

A simplified example showing the format of a bundle manifest file:

xml
<em:Manifest xmlns:em="urn:hpcc:esdl:manifest">
+    <em:ServiceBinding esdlservice="WsFoobar" id="WsFoobar_desdl_binding" auth_feature="DEFERRED">
+        <Methods>
+            <em:Scripts>
+                <em:Include file="WsFoobar-request-prep.xml"/>
+                <em:Include file="WsFoobar-logging-prep.xml"/>
+            </em:Scripts>
+            <Method name="FoobarSearch" url="127.0.0.1:8888">
+                <em:Scripts>
+                    <em:Include file="FoobarSearch-scripts.xml"/>
+                </em:Scripts>
+            </Method>
+        </Methods>
+        <LoggingManager>
+            <LogAgent transformSource="local" name="main-logging">
+                <LogDataXPath>
+                    <LogInfo name="PreparedData" xsl="log-prep">
+                </LogDataXPath>
+                <XSL>
+                    <em:Transform name="log-prep">
+                        <em:Include file="log-prep.xslt">
+                    </em:Transform>
+                </XSL>
+            </LogAgent>
+        </LoggingManager>
+    </em:ServiceBinding>
+    <em:EsdlDefinition>
+        <em:Include file="WsFoobar.ecm"/>
+    </em:EsdlDefinition>
+</em:Manifest>

The tool is permissive and flexible, copying through most markup to the output. Recognized elements in the manifest namespace may be treated differently. They are only required in order to take advantage of the automated processing and simplified format of the manifest file. This example highlights the recommended usage of manifest elements to use the tool's capabilities. Although you could replace some of the elements below with verbatim bundle or binding output elements we won't cover that usage here.

  • <em:Manifest> is the required root element. By default the tool outputs a bundle, though you may explicitly override that on the command line or by providing an @outputType='binding' attribute.
  • <em:ServiceBinding> is valid for both bundle and binding output. It is necessary to enable recognition of <em:Scripts>, and <em:Transform> elements.
  • <em:EsdlDefinition> is relevant only for bundle output. It is necessary to enable element order preservation and recognition of <em:Include> as a descendant element.
  • <em:Include> causes external file contents to be inserted in place of the element. The processing of included files is context dependent; the parent of the <em:Include> element dictates how the file is handled. File inclusion facilitates code reuse in a configuration as code development environment.
  • <em:Scripts> and <em:Transform> trigger preservation of element order for all descendent elements and enable <em:Include> recognition.

XML element ordering is significant to proper ESDL script, XSLT, and ESXDL content processing. The platform's IPropertyTree implementation, used when loading an artifact, does not preserve order. Output configuration files must embed order-sensitive content as text as opposed to XML markup. The tool allows configuration authors to create and maintain files as XML markup, which is easy to read. It then automates the conversion of the XML markup into the embedded text required by an ESP.

Syntax

These elements may create artifact content, change the tool's behavior, or both. When used as intended, none of these elements will appear in the generated output:

Manifest

Required root element of all manifest files that create output:

  • bundle output is created by default. It can be made explicit by setting either/or:
    • command line option --output-type to bundle
    • manifest property @outputType to bundle.
  • binding output is created when either/or:
    • command line option --output-type is binding
    • manifest property @outputType is binding.
AttributeRequired?ValueDescription
@outputTypeNstringA hint informing the tool which type of output to generate. The command line option --output-type may supersede this value to produce a different output.
A bundle manifest is a superset of a binding manifest and may logically be used to create either output type. A binding manifest, as a subset of a bundle, cannot be used to create a valid bundle.
@xmlns[:prefix]YstringThe manifest namespace urn:hpcc:esdl:manifest must be declared. The default namespace prefix should not be used unless all other markup is fully qualified.

ServiceBinding

Recommended child of Manifest that creates ESDL binding content and applies ESDL binding-specific logic to descendent content:

  • A Binding output element is always created containing attributes of Manifest as required. See the sections below for details of the attributes referenced by the tool and how they're output.
  • A child of Binding named Definition is created with attributes @id and @esdlservice.
  • Recognition of <em:Scripts> elements is enabled.
  • Recognition of <em:Transform> elements is enabled.

While possible to omit the <em:ServiceBinding> element and instead embed a complete Binding tree in the manifest, it is discouraged because you lose the benefit of the processing described above.

There are three categories of attributes that can be defined on the <em:ServiceBinding> element:

  1. Standard attributes needed for setup
  2. Service-specific or binding-specific attributes
  3. Auxillary attributes recommended for read-only reference
Standard Attributes

First are the standard attributes used to setup and define the binding:

AttributeRequired?ValueUsage
@esdlserviceYesstring- Name of the ESDL service to which the binding is bound. Output on the Binding/Definition element.
- Also used to generate a value for Definition/@id for bundle type output.

Note that Definition/@id is not output by the tool for binding output as it would need to match the ESDLDefinitionId passed on command line esdl bind-service call, which may differ between environments.

Service-Specific Attributes

Second are binding-specific or service-specific attributes. This is an open-ended category where most future attributes will belong. Any attribute not in the other two categories is included here and output on the Binding/Definition element:

AttributeRequired?ValueUsage
@auth_featureNostringUsed declare authorization settings if they aren't present in the ESDL Definition, or override them if they are. Additional documentation on this attribute is forthcoming.
@returnSchemaLocationOnOKNoBooleanWhen true, a successful SOAP response (non SOAP-Fault) will include the schema location property. False by default.
@namespaceNostringString specifying the namespace for all methods in the binding. May contain variables that are replaced with their values by the ESP during runtime:
- \${service} : lowercase service name
- \${esdl-service} : service name, possibly mixed case
- \${method} : lowercase method name
- \${esdl-method} : method name, possibly mixed case
- \${optionals} : comma-delimited list of all optional URL parameters included in the method request, enclosed in parentheses
- \${version} : client version number
Auxillary Attributes

Finally are auxillary attributes. These should be thought of as read-only or for reference, and it is not recommended that you set these in the manifest. They are set by the system when publishing to dali using esdl bind-service, and they are present on binding configurations retrieved from the dali. If you set these attributes in the manifest, the values will be overwritten when running esdl bind-service. Alternately, if you run as an esdl application, these values aren't set by default and don't have a material effect on the binding, but they may appear in the trace log.

AttributeRequired?ValueUsage
@createdNostringTimestamp of binding creation
@espbindingNostringSet to match the id. Otherwise the value is unset and unused.
@espprocessNostringName of the ESP process this binding is running on
@idNostringRuntime name of the binding. When publishing to dali the value is [ESP Process].[port].[ESDL Service]. When not present in the manifest a default value is generated of the form [@esdlservice]_desdl_binding
@portNostringPort on the ESP Process listening for connections to the binding.
@publishedByNostringUserid of the person publishing the binding

EsdlDefinition

Recommended child of <em:Manifest> that enables ESDL definition-specific logic in the tool:

  • A Definitions element is output. All content is enclosed in a CDATA section.
  • The manifest <em:Include> element is recognized to import ESDL definitions:
    • An included .ecm file is transformed into XML.
    • An included .xml file is imported as-is.
AttributeRequired?ValueUsage
N/A

The element is not required since it is possible to embed a complete Definitions element hierarchy in the manifest.

Include

Optional element that imports the contents of another file into the output in place of itself. The outcome of the import depends on the context in which this element is used. See EsdlDefinition, Scripts, and Transform for more information.

AttributeRequired?ValueUsage
@fileYesfile pathFull or partial path to an external file to be imported. If a partial path is outside of the tool's working directory, the tool's command line must specify the appropriate root directory using either -I or --include-path.

Any XSLTs or ESDL Scripts written inline in a manifest file will have XML escaping applied where required to generate valid XML. If an XSLT contains any text content or markup that needs to be preserved as-is (no XML escaping applied) then be sure to use an <em:Include> operation. Included files are inserted into the output as-is, with the exception of encoding nested CDATA markup. If the included file will be inside a CDATA section on output, then any CDATA end markup in the file will be encoded as ]]]]><![CDATA[> to prevent nested CDATA sections or a prematurely ending a CDATA section.

For details on using XSLT to generate unescaped output, see this section of the specification: https://www.w3.org/TR/1999/REC-xslt-19991116#disable-output-escaping

Scripts

Optional repeatable element appearing within an <em:ServiceBinding> element that processes child elements and creates output expected for an ESDL binding:

  • The <em:Scripts> element is replaced on output with <Scripts>. Then all content is enclosed in a CDATA section after wrapping it with a new Scripts element. That new <Scripts> element contains namespaces declared by the input <em:Scripts> element. The input <em:Scripts foo="..." xmlns:bar="..."><!-- content --></em:Scripts> becomes <Scripts><![CDATA[<Scripts xmlns:bar="..."><!-- content --></Scripts>]]></Scripts>.
  • The manifest <em:Include> element is recognized to import scripts from external files. The entire file, minus leading and trailing whitespace, is imported. Refrain from including files that contain an XML declaration.

Transform

Optional repeatable element appearing within an <em:ServiceBinding> element that processes child elements and creates output expected for an ESDL binding:

  • All content is enclosed in a CDATA section. The input <em:Transform> <!-- content --> </em:Transform> becomes <Transform><![CDATA[<!-- content -->]]></Transform>.
  • The manifest <em:Include> element is recognized to import transforms from external files. The entire file, minus leading and trailing whitespace, is imported. Refrain from including files that contain an XML declaration.

Usage

Usage:
+
+esdl manifest <manifest-file> [options]
+
+Options:
+    -I | --include-path <path>
+                        Search path for external files included in the manifest.
+                        Use once for each path.
+    --outfile <filename>
+                        Path and name of the output file
+    --output-type <type>
+                        When specified this option overrides the value supplied
+                        in the manifest attribute Manifest/@outputType.
+                        Allowed values are 'binding' or 'bundle'.
+                        When not specified in either location the default is
+                        'bundle'
+    --help              Display usage information for the given command
+    -v,--verbose        Output additional tracing information
+    -tcat,--trace-category <flags>
+                        Control which debug messages are output; a case-insensitive
+                        comma-delimited combination of:
+                            dev: all output for the developer audience
+                            admin: all output for the operator audience
+                            user: all output for the user audience
+                            err: all error output
+                            warn: all warning output
+                            prog: all progress output
+                            info: all info output
+                        Errors and warnings are enabled by default if not verbose,
+                        and all are enabled when verbose. Use an empty <flags> value
+                        to disable all.
+

Output

The esdl manifest command reads the manifest, processes statements in the urn:hpcc:esdl:manifest namespace and generates an output XML file formatted to the requirements of the ESDL ESP. This includes wrapping included content in CDATA sections to ensure element order is maintained and replacing urn:hpcc:esdl:manifest elements as required.

An example output of each type -bundle and binding- is shown below. The examples use the sample manifest above as input plus these included files:

WsFoobar-request-prep.xml

xml
<es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+    <es:set-value target="RequestValue" value="'foobar'"/>
+</es:BackendRequest>

WsFoobar-logging-prep.xml

xml
<es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+    <es:set-value target="LogValue" value="23"/>
+</es:PreLogging>

FoobarSearch-scripts.xml

xml
<Scripts>
+    <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+        <es:if test="RequestOption>1">
+            <es:set-value target="HiddenOption" value="true()"/>
+        </es:if>
+    </es:BackendRequest>
+
+    <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+        <es:if test="RequestOption=1">
+            <es:set-value target="ProductPrice" value="10"/>
+        </es:if>
+    </es:PreLogging>
+</Scripts>

log-prep.xslt

xml
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="xml" omit-xml-declaration="yes"/>
+    <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+    <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+    <xsl:template match="/">
+        <Result>
+        <Dataset name='special-data'>
+            <Row>
+            <Records>
+                <Rec>
+                <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                <request_data>
+                    <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                    <xsl:copy-of select="$logContent/UserContent/Context"/>
+                    <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                </request_data>
+                <request_format>SPECIAL</request_format>
+                <type>23</type>
+                </Rec>
+            </Records>
+            </Row>
+        </Dataset>
+        </Result>
+    </xsl:template>
+</xsl:stylesheet>

WsFoobar.ecm

ESPrequest FoobarSearchRequest
+{
+    int RequestOption;
+    string RequestName;
+    [optional("hidden")] bool HiddenOption;
+};
+
+ESPresponse FoobarSearchResponse
+{
+    int FoundCount;
+    string FoundAddress;
+};
+
+ESPservice [
+    auth_feature("DEFERRED"),
+    version("1"),
+    default_client_version("1"),
+] WsFoobar
+{
+    ESPmethod FoobarSearch(FoobarSearchRequest, FoobarSearchResponse);
+};
+

Bundle

The bundle is suitable to configure a service on an ESP launched in esdl application mode.

xml
  <EsdlBundle>
+    <Binding id="WsFoobar_desdl_binding">
+      <Definition esdlservice="WsFoobar" id="WsFoobar.1">
+        <Methods>
+          <Scripts>
+            <![CDATA[
+              <Scripts>
+                <es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                    <es:set-value target="RequestValue" value="'foobar'"/>
+                </es:BackendRequest>
+                <es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                    <es:set-value target="LogValue" value="23"/>
+                </es:PreLogging>
+              </Scripts>
+            ]]>
+          </Scripts>
+          <Method name="FoobarSearch" url="127.0.0.1:8888">
+            <Scripts>
+              <![CDATA[
+                <Scripts>
+                  <Scripts>
+                      <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                          <es:if test="RequestOption>1">
+                              <es:set-value target="HiddenOption" value="true()"/>
+                          </es:if>
+                      </es:BackendRequest>
+
+                      <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                          <es:if test="RequestOption=1">
+                              <es:set-value target="ProductPrice" value="10"/>
+                          </es:if>
+                      </es:PreLogging>
+                  </Scripts>
+                </Scripts>
+              ]]>
+            </Scripts>
+          </Method>
+        </Methods>
+        <LoggingManager>
+          <LogAgent transformSource="local" name="main-logging">
+            <LogDataXPath>
+              <LogInfo name="PreparedData" xsl="log-prep"/>
+            </LogDataXPath>
+            <XSL>
+              <Transform name="log-prep">
+                <![CDATA[
+                  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+                      <xsl:output method="xml" omit-xml-declaration="yes"/>
+                      <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+                      <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+                      <xsl:template match="/">
+                          <Result>
+                          <Dataset name='special-data'>
+                              <Row>
+                              <Records>
+                                  <Rec>
+                                  <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                                  <request_data>
+                                      <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                                      <xsl:copy-of select="$logContent/UserContent/Context"/>
+                                      <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                                  </request_data>
+                                  <request_format>SPECIAL</request_format>
+                                  <type>23</type>
+                                  </Rec>
+                              </Records>
+                              </Row>
+                          </Dataset>
+                          </Result>
+                      </xsl:template>
+                  </xsl:stylesheet>
+                ]]>
+              </Transform>
+            </XSL>
+          </LogAgent>
+        </LoggingManager>
+      </Definition>
+    </Binding>
+    <Definitions>
+      <![CDATA[
+        <esxdl name="WsFoobar"><EsdlRequest name="FoobarSearchRequest"><EsdlElement  type="int" name="RequestOption"/><EsdlElement  type="string" name="RequestName"/><EsdlElement  optional="hidden" type="bool" name="HiddenOption"/></EsdlRequest>
+        <EsdlResponse name="FoobarSearchResponse"><EsdlElement  type="int" name="FoundCount"/><EsdlElement  type="string" name="FoundAddress"/></EsdlResponse>
+        <EsdlRequest name="WsFoobarPingRequest"></EsdlRequest>
+        <EsdlResponse name="WsFoobarPingResponse"></EsdlResponse>
+        <EsdlService version="1" auth_feature="DEFERRED" name="WsFoobar" default_client_version="1"><EsdlMethod response_type="FoobarSearchResponse" request_type="FoobarSearchRequest" name="FoobarSearch"/><EsdlMethod response_type="WsFoobarPingResponse" auth_feature="none" request_type="WsFoobarPingRequest" name="Ping"/></EsdlService>
+        </esxdl>
+      ]]>
+    </Definitions>
+  </EsdlBundle>

Binding

The binding can be used to configure a service for an ESP using a dali.

xml
<Binding id="WsFoobar_desdl_binding">
+  <Definition esdlservice="WsFoobar" id="WsFoobar.1">
+    <Methods>
+      <Scripts>
+        <![CDATA[
+          <Scripts>
+            <es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                <es:set-value target="RequestValue" value="'foobar'"/>
+            </es:BackendRequest>
+            <es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                <es:set-value target="LogValue" value="23"/>
+            </es:PreLogging>
+          </Scripts>
+        ]]>
+      </Scripts>
+      <Method name="FoobarSearch" url="127.0.0.1:8888">
+        <Scripts>
+          <![CDATA[
+            <Scripts>
+              <Scripts>
+                  <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                      <es:if test="RequestOption>1">
+                          <es:set-value target="HiddenOption" value="true()"/>
+                      </es:if>
+                  </es:BackendRequest>
+
+                  <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                      <es:if test="RequestOption=1">
+                          <es:set-value target="ProductPrice" value="10"/>
+                      </es:if>
+                  </es:PreLogging>
+              </Scripts>
+            </Scripts>
+          ]]>
+        </Scripts>
+      </Method>
+    </Methods>
+    <LoggingManager>
+      <LogAgent transformSource="local" name="main-logging">
+        <LogDataXPath>
+          <LogInfo name="PreparedData" xsl="log-prep"/>
+        </LogDataXPath>
+        <XSL>
+          <Transform name="log-prep">
+            <![CDATA[
+              <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+                  <xsl:output method="xml" omit-xml-declaration="yes"/>
+                  <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+                  <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+                  <xsl:template match="/">
+                      <Result>
+                      <Dataset name='special-data'>
+                          <Row>
+                          <Records>
+                              <Rec>
+                              <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                              <request_data>
+                                  <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                                  <xsl:copy-of select="$logContent/UserContent/Context"/>
+                                  <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                              </request_data>
+                              <request_format>SPECIAL</request_format>
+                              <type>23</type>
+                              </Rec>
+                          </Records>
+                          </Row>
+                      </Dataset>
+                      </Result>
+                  </xsl:template>
+              </xsl:stylesheet>
+            ]]>
+          </Transform>
+        </XSL>
+      </LogAgent>
+    </LoggingManager>
+  </Definition>
+</Binding>
`,78)]))}const g=i(e,[["render",l]]);export{E as __pageData,g as default}; diff --git a/assets/tools_esdlcmd_README.md.D3nMaAu6.lean.js b/assets/tools_esdlcmd_README.md.D3nMaAu6.lean.js new file mode 100644 index 00000000000..0ccc7f07d89 --- /dev/null +++ b/assets/tools_esdlcmd_README.md.D3nMaAu6.lean.js @@ -0,0 +1,307 @@ +import{_ as i,c as t,a3 as a,o as n}from"./chunks/framework.DkhCEVKm.js";const E=JSON.parse('{"title":"esdl Utility","description":"","frontmatter":{},"headers":[],"relativePath":"tools/esdlcmd/README.md","filePath":"tools/esdlcmd/README.md","lastUpdated":1731340314000}'),e={name:"tools/esdlcmd/README.md"};function l(h,s,p,k,o,d){return n(),t("div",null,s[0]||(s[0]=[a(`

esdl Utility

The esdl utility tool aids with creating and managing ESDL-based and Dynamic ESDL services on an HPCC cluster. It consists of several different commands.

To generate output from an ESDL definition:

xml               Generate XML from ESDL definition.
+ecl               Generate ECL from ESDL definition.
+xsd               Generate XSD from ESDL definition.
+wsdl              Generate WSDL from ESDL definition.
+java              Generate Java code from ESDL definition.
+cpp               Generate C++ code from ESDL definition.
+monitor           Generate ECL code for result monitoring / differencing
+monitor-template  Generate a template for use with 'monitor' command
+

To manage ESDL and DESDL services:

publish               Publish ESDL Definition for ESP use.
+list-definitions      List all ESDL definitions.
+get-definition        Get ESDL definition.
+delete                Delete ESDL Definition.
+bind-service          Configure ESDL based service on target ESP (with existing ESP Binding).
+list-bindings         List all ESDL bindings.
+unbind-service        Remove ESDL based service binding on target ESP.
+bind-method           Configure method associated with existing ESDL binding.
+unbind-method         Remove method associated with existing ESDL binding.
+get-binding           Get ESDL binding.
+manifest              Build a service binding or bundle from a manifest file.
+bind-log-transform    Configure log transform associated with existing ESDL binding.
+unbind-log-transform  Remove log transform associated with existing ESDL binding.
+

The sections below cover the commands in more detail.

manifest

The manifest command creates an XML configuration file for an ESDL ESP from an input XML manifest file. The type of configuration output depends on the manifest file input and on command-line options.

Manifest File

A manifest file is an XML-formatted template combining elements in and outside of the manifest's urn:hpcc:esdl:manifest namespace. Recognized elements of this namespace control the tool while all other markup is copied to the output. The goal of using a manifest file with the tool is to make configuring and deploying services easier:

  1. The manifest file format abstracts some of the complexity of the actual configuration.
  2. By allowing you to include external files like ESDL Scripts and XSLTs into the ouput, you can store and maintain them separately in your repo.

The result of running the manifest tool on a manifest file is an XML artifact suitable for use with the ESDL ESP. Supported output includes:

  • binding: The output is an ESDL binding that may be published to dali.
  • bundle: The output is an ESDL bundle file that may be used to launch an ESP in application mode.

Example

A simplified example showing the format of a bundle manifest file:

xml
<em:Manifest xmlns:em="urn:hpcc:esdl:manifest">
+    <em:ServiceBinding esdlservice="WsFoobar" id="WsFoobar_desdl_binding" auth_feature="DEFERRED">
+        <Methods>
+            <em:Scripts>
+                <em:Include file="WsFoobar-request-prep.xml"/>
+                <em:Include file="WsFoobar-logging-prep.xml"/>
+            </em:Scripts>
+            <Method name="FoobarSearch" url="127.0.0.1:8888">
+                <em:Scripts>
+                    <em:Include file="FoobarSearch-scripts.xml"/>
+                </em:Scripts>
+            </Method>
+        </Methods>
+        <LoggingManager>
+            <LogAgent transformSource="local" name="main-logging">
+                <LogDataXPath>
+                    <LogInfo name="PreparedData" xsl="log-prep">
+                </LogDataXPath>
+                <XSL>
+                    <em:Transform name="log-prep">
+                        <em:Include file="log-prep.xslt">
+                    </em:Transform>
+                </XSL>
+            </LogAgent>
+        </LoggingManager>
+    </em:ServiceBinding>
+    <em:EsdlDefinition>
+        <em:Include file="WsFoobar.ecm"/>
+    </em:EsdlDefinition>
+</em:Manifest>

The tool is permissive and flexible, copying through most markup to the output. Recognized elements in the manifest namespace may be treated differently. They are only required in order to take advantage of the automated processing and simplified format of the manifest file. This example highlights the recommended usage of manifest elements to use the tool's capabilities. Although you could replace some of the elements below with verbatim bundle or binding output elements we won't cover that usage here.

  • <em:Manifest> is the required root element. By default the tool outputs a bundle, though you may explicitly override that on the command line or by providing an @outputType='binding' attribute.
  • <em:ServiceBinding> is valid for both bundle and binding output. It is necessary to enable recognition of <em:Scripts>, and <em:Transform> elements.
  • <em:EsdlDefinition> is relevant only for bundle output. It is necessary to enable element order preservation and recognition of <em:Include> as a descendant element.
  • <em:Include> causes external file contents to be inserted in place of the element. The processing of included files is context dependent; the parent of the <em:Include> element dictates how the file is handled. File inclusion facilitates code reuse in a configuration as code development environment.
  • <em:Scripts> and <em:Transform> trigger preservation of element order for all descendent elements and enable <em:Include> recognition.

XML element ordering is significant to proper ESDL script, XSLT, and ESXDL content processing. The platform's IPropertyTree implementation, used when loading an artifact, does not preserve order. Output configuration files must embed order-sensitive content as text as opposed to XML markup. The tool allows configuration authors to create and maintain files as XML markup, which is easy to read. It then automates the conversion of the XML markup into the embedded text required by an ESP.

Syntax

These elements may create artifact content, change the tool's behavior, or both. When used as intended, none of these elements will appear in the generated output:

Manifest

Required root element of all manifest files that create output:

  • bundle output is created by default. It can be made explicit by setting either/or:
    • command line option --output-type to bundle
    • manifest property @outputType to bundle.
  • binding output is created when either/or:
    • command line option --output-type is binding
    • manifest property @outputType is binding.
AttributeRequired?ValueDescription
@outputTypeNstringA hint informing the tool which type of output to generate. The command line option --output-type may supersede this value to produce a different output.
A bundle manifest is a superset of a binding manifest and may logically be used to create either output type. A binding manifest, as a subset of a bundle, cannot be used to create a valid bundle.
@xmlns[:prefix]YstringThe manifest namespace urn:hpcc:esdl:manifest must be declared. The default namespace prefix should not be used unless all other markup is fully qualified.

ServiceBinding

Recommended child of Manifest that creates ESDL binding content and applies ESDL binding-specific logic to descendent content:

  • A Binding output element is always created containing attributes of Manifest as required. See the sections below for details of the attributes referenced by the tool and how they're output.
  • A child of Binding named Definition is created with attributes @id and @esdlservice.
  • Recognition of <em:Scripts> elements is enabled.
  • Recognition of <em:Transform> elements is enabled.

While possible to omit the <em:ServiceBinding> element and instead embed a complete Binding tree in the manifest, it is discouraged because you lose the benefit of the processing described above.

There are three categories of attributes that can be defined on the <em:ServiceBinding> element:

  1. Standard attributes needed for setup
  2. Service-specific or binding-specific attributes
  3. Auxillary attributes recommended for read-only reference
Standard Attributes

First are the standard attributes used to setup and define the binding:

AttributeRequired?ValueUsage
@esdlserviceYesstring- Name of the ESDL service to which the binding is bound. Output on the Binding/Definition element.
- Also used to generate a value for Definition/@id for bundle type output.

Note that Definition/@id is not output by the tool for binding output as it would need to match the ESDLDefinitionId passed on command line esdl bind-service call, which may differ between environments.

Service-Specific Attributes

Second are binding-specific or service-specific attributes. This is an open-ended category where most future attributes will belong. Any attribute not in the other two categories is included here and output on the Binding/Definition element:

AttributeRequired?ValueUsage
@auth_featureNostringUsed declare authorization settings if they aren't present in the ESDL Definition, or override them if they are. Additional documentation on this attribute is forthcoming.
@returnSchemaLocationOnOKNoBooleanWhen true, a successful SOAP response (non SOAP-Fault) will include the schema location property. False by default.
@namespaceNostringString specifying the namespace for all methods in the binding. May contain variables that are replaced with their values by the ESP during runtime:
- \${service} : lowercase service name
- \${esdl-service} : service name, possibly mixed case
- \${method} : lowercase method name
- \${esdl-method} : method name, possibly mixed case
- \${optionals} : comma-delimited list of all optional URL parameters included in the method request, enclosed in parentheses
- \${version} : client version number
Auxillary Attributes

Finally are auxillary attributes. These should be thought of as read-only or for reference, and it is not recommended that you set these in the manifest. They are set by the system when publishing to dali using esdl bind-service, and they are present on binding configurations retrieved from the dali. If you set these attributes in the manifest, the values will be overwritten when running esdl bind-service. Alternately, if you run as an esdl application, these values aren't set by default and don't have a material effect on the binding, but they may appear in the trace log.

AttributeRequired?ValueUsage
@createdNostringTimestamp of binding creation
@espbindingNostringSet to match the id. Otherwise the value is unset and unused.
@espprocessNostringName of the ESP process this binding is running on
@idNostringRuntime name of the binding. When publishing to dali the value is [ESP Process].[port].[ESDL Service]. When not present in the manifest a default value is generated of the form [@esdlservice]_desdl_binding
@portNostringPort on the ESP Process listening for connections to the binding.
@publishedByNostringUserid of the person publishing the binding

EsdlDefinition

Recommended child of <em:Manifest> that enables ESDL definition-specific logic in the tool:

  • A Definitions element is output. All content is enclosed in a CDATA section.
  • The manifest <em:Include> element is recognized to import ESDL definitions:
    • An included .ecm file is transformed into XML.
    • An included .xml file is imported as-is.
AttributeRequired?ValueUsage
N/A

The element is not required since it is possible to embed a complete Definitions element hierarchy in the manifest.

Include

Optional element that imports the contents of another file into the output in place of itself. The outcome of the import depends on the context in which this element is used. See EsdlDefinition, Scripts, and Transform for more information.

AttributeRequired?ValueUsage
@fileYesfile pathFull or partial path to an external file to be imported. If a partial path is outside of the tool's working directory, the tool's command line must specify the appropriate root directory using either -I or --include-path.

Any XSLTs or ESDL Scripts written inline in a manifest file will have XML escaping applied where required to generate valid XML. If an XSLT contains any text content or markup that needs to be preserved as-is (no XML escaping applied) then be sure to use an <em:Include> operation. Included files are inserted into the output as-is, with the exception of encoding nested CDATA markup. If the included file will be inside a CDATA section on output, then any CDATA end markup in the file will be encoded as ]]]]><![CDATA[> to prevent nested CDATA sections or a prematurely ending a CDATA section.

For details on using XSLT to generate unescaped output, see this section of the specification: https://www.w3.org/TR/1999/REC-xslt-19991116#disable-output-escaping

Scripts

Optional repeatable element appearing within an <em:ServiceBinding> element that processes child elements and creates output expected for an ESDL binding:

  • The <em:Scripts> element is replaced on output with <Scripts>. Then all content is enclosed in a CDATA section after wrapping it with a new Scripts element. That new <Scripts> element contains namespaces declared by the input <em:Scripts> element. The input <em:Scripts foo="..." xmlns:bar="..."><!-- content --></em:Scripts> becomes <Scripts><![CDATA[<Scripts xmlns:bar="..."><!-- content --></Scripts>]]></Scripts>.
  • The manifest <em:Include> element is recognized to import scripts from external files. The entire file, minus leading and trailing whitespace, is imported. Refrain from including files that contain an XML declaration.

Transform

Optional repeatable element appearing within an <em:ServiceBinding> element that processes child elements and creates output expected for an ESDL binding:

  • All content is enclosed in a CDATA section. The input <em:Transform> <!-- content --> </em:Transform> becomes <Transform><![CDATA[<!-- content -->]]></Transform>.
  • The manifest <em:Include> element is recognized to import transforms from external files. The entire file, minus leading and trailing whitespace, is imported. Refrain from including files that contain an XML declaration.

Usage

Usage:
+
+esdl manifest <manifest-file> [options]
+
+Options:
+    -I | --include-path <path>
+                        Search path for external files included in the manifest.
+                        Use once for each path.
+    --outfile <filename>
+                        Path and name of the output file
+    --output-type <type>
+                        When specified this option overrides the value supplied
+                        in the manifest attribute Manifest/@outputType.
+                        Allowed values are 'binding' or 'bundle'.
+                        When not specified in either location the default is
+                        'bundle'
+    --help              Display usage information for the given command
+    -v,--verbose        Output additional tracing information
+    -tcat,--trace-category <flags>
+                        Control which debug messages are output; a case-insensitive
+                        comma-delimited combination of:
+                            dev: all output for the developer audience
+                            admin: all output for the operator audience
+                            user: all output for the user audience
+                            err: all error output
+                            warn: all warning output
+                            prog: all progress output
+                            info: all info output
+                        Errors and warnings are enabled by default if not verbose,
+                        and all are enabled when verbose. Use an empty <flags> value
+                        to disable all.
+

Output

The esdl manifest command reads the manifest, processes statements in the urn:hpcc:esdl:manifest namespace and generates an output XML file formatted to the requirements of the ESDL ESP. This includes wrapping included content in CDATA sections to ensure element order is maintained and replacing urn:hpcc:esdl:manifest elements as required.

An example output of each type -bundle and binding- is shown below. The examples use the sample manifest above as input plus these included files:

WsFoobar-request-prep.xml

xml
<es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+    <es:set-value target="RequestValue" value="'foobar'"/>
+</es:BackendRequest>

WsFoobar-logging-prep.xml

xml
<es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+    <es:set-value target="LogValue" value="23"/>
+</es:PreLogging>

FoobarSearch-scripts.xml

xml
<Scripts>
+    <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+        <es:if test="RequestOption>1">
+            <es:set-value target="HiddenOption" value="true()"/>
+        </es:if>
+    </es:BackendRequest>
+
+    <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+        <es:if test="RequestOption=1">
+            <es:set-value target="ProductPrice" value="10"/>
+        </es:if>
+    </es:PreLogging>
+</Scripts>

log-prep.xslt

xml
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="xml" omit-xml-declaration="yes"/>
+    <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+    <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+    <xsl:template match="/">
+        <Result>
+        <Dataset name='special-data'>
+            <Row>
+            <Records>
+                <Rec>
+                <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                <request_data>
+                    <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                    <xsl:copy-of select="$logContent/UserContent/Context"/>
+                    <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                </request_data>
+                <request_format>SPECIAL</request_format>
+                <type>23</type>
+                </Rec>
+            </Records>
+            </Row>
+        </Dataset>
+        </Result>
+    </xsl:template>
+</xsl:stylesheet>

WsFoobar.ecm

ESPrequest FoobarSearchRequest
+{
+    int RequestOption;
+    string RequestName;
+    [optional("hidden")] bool HiddenOption;
+};
+
+ESPresponse FoobarSearchResponse
+{
+    int FoundCount;
+    string FoundAddress;
+};
+
+ESPservice [
+    auth_feature("DEFERRED"),
+    version("1"),
+    default_client_version("1"),
+] WsFoobar
+{
+    ESPmethod FoobarSearch(FoobarSearchRequest, FoobarSearchResponse);
+};
+

Bundle

The bundle is suitable to configure a service on an ESP launched in esdl application mode.

xml
  <EsdlBundle>
+    <Binding id="WsFoobar_desdl_binding">
+      <Definition esdlservice="WsFoobar" id="WsFoobar.1">
+        <Methods>
+          <Scripts>
+            <![CDATA[
+              <Scripts>
+                <es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                    <es:set-value target="RequestValue" value="'foobar'"/>
+                </es:BackendRequest>
+                <es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                    <es:set-value target="LogValue" value="23"/>
+                </es:PreLogging>
+              </Scripts>
+            ]]>
+          </Scripts>
+          <Method name="FoobarSearch" url="127.0.0.1:8888">
+            <Scripts>
+              <![CDATA[
+                <Scripts>
+                  <Scripts>
+                      <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                          <es:if test="RequestOption>1">
+                              <es:set-value target="HiddenOption" value="true()"/>
+                          </es:if>
+                      </es:BackendRequest>
+
+                      <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                          <es:if test="RequestOption=1">
+                              <es:set-value target="ProductPrice" value="10"/>
+                          </es:if>
+                      </es:PreLogging>
+                  </Scripts>
+                </Scripts>
+              ]]>
+            </Scripts>
+          </Method>
+        </Methods>
+        <LoggingManager>
+          <LogAgent transformSource="local" name="main-logging">
+            <LogDataXPath>
+              <LogInfo name="PreparedData" xsl="log-prep"/>
+            </LogDataXPath>
+            <XSL>
+              <Transform name="log-prep">
+                <![CDATA[
+                  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+                      <xsl:output method="xml" omit-xml-declaration="yes"/>
+                      <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+                      <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+                      <xsl:template match="/">
+                          <Result>
+                          <Dataset name='special-data'>
+                              <Row>
+                              <Records>
+                                  <Rec>
+                                  <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                                  <request_data>
+                                      <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                                      <xsl:copy-of select="$logContent/UserContent/Context"/>
+                                      <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                                  </request_data>
+                                  <request_format>SPECIAL</request_format>
+                                  <type>23</type>
+                                  </Rec>
+                              </Records>
+                              </Row>
+                          </Dataset>
+                          </Result>
+                      </xsl:template>
+                  </xsl:stylesheet>
+                ]]>
+              </Transform>
+            </XSL>
+          </LogAgent>
+        </LoggingManager>
+      </Definition>
+    </Binding>
+    <Definitions>
+      <![CDATA[
+        <esxdl name="WsFoobar"><EsdlRequest name="FoobarSearchRequest"><EsdlElement  type="int" name="RequestOption"/><EsdlElement  type="string" name="RequestName"/><EsdlElement  optional="hidden" type="bool" name="HiddenOption"/></EsdlRequest>
+        <EsdlResponse name="FoobarSearchResponse"><EsdlElement  type="int" name="FoundCount"/><EsdlElement  type="string" name="FoundAddress"/></EsdlResponse>
+        <EsdlRequest name="WsFoobarPingRequest"></EsdlRequest>
+        <EsdlResponse name="WsFoobarPingResponse"></EsdlResponse>
+        <EsdlService version="1" auth_feature="DEFERRED" name="WsFoobar" default_client_version="1"><EsdlMethod response_type="FoobarSearchResponse" request_type="FoobarSearchRequest" name="FoobarSearch"/><EsdlMethod response_type="WsFoobarPingResponse" auth_feature="none" request_type="WsFoobarPingRequest" name="Ping"/></EsdlService>
+        </esxdl>
+      ]]>
+    </Definitions>
+  </EsdlBundle>

Binding

The binding can be used to configure a service for an ESP using a dali.

xml
<Binding id="WsFoobar_desdl_binding">
+  <Definition esdlservice="WsFoobar" id="WsFoobar.1">
+    <Methods>
+      <Scripts>
+        <![CDATA[
+          <Scripts>
+            <es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                <es:set-value target="RequestValue" value="'foobar'"/>
+            </es:BackendRequest>
+            <es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                <es:set-value target="LogValue" value="23"/>
+            </es:PreLogging>
+          </Scripts>
+        ]]>
+      </Scripts>
+      <Method name="FoobarSearch" url="127.0.0.1:8888">
+        <Scripts>
+          <![CDATA[
+            <Scripts>
+              <Scripts>
+                  <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                      <es:if test="RequestOption>1">
+                          <es:set-value target="HiddenOption" value="true()"/>
+                      </es:if>
+                  </es:BackendRequest>
+
+                  <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                      <es:if test="RequestOption=1">
+                          <es:set-value target="ProductPrice" value="10"/>
+                      </es:if>
+                  </es:PreLogging>
+              </Scripts>
+            </Scripts>
+          ]]>
+        </Scripts>
+      </Method>
+    </Methods>
+    <LoggingManager>
+      <LogAgent transformSource="local" name="main-logging">
+        <LogDataXPath>
+          <LogInfo name="PreparedData" xsl="log-prep"/>
+        </LogDataXPath>
+        <XSL>
+          <Transform name="log-prep">
+            <![CDATA[
+              <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+                  <xsl:output method="xml" omit-xml-declaration="yes"/>
+                  <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+                  <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+                  <xsl:template match="/">
+                      <Result>
+                      <Dataset name='special-data'>
+                          <Row>
+                          <Records>
+                              <Rec>
+                              <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                              <request_data>
+                                  <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                                  <xsl:copy-of select="$logContent/UserContent/Context"/>
+                                  <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                              </request_data>
+                              <request_format>SPECIAL</request_format>
+                              <type>23</type>
+                              </Rec>
+                          </Records>
+                          </Row>
+                      </Dataset>
+                      </Result>
+                  </xsl:template>
+              </xsl:stylesheet>
+            ]]>
+          </Transform>
+        </XSL>
+      </LogAgent>
+    </LoggingManager>
+  </Definition>
+</Binding>
`,78)]))}const g=i(e,[["render",l]]);export{E as __pageData,g as default}; diff --git a/assets/tools_esp-api_README.md.WcTRZBDR.js b/assets/tools_esp-api_README.md.WcTRZBDR.js new file mode 100644 index 00000000000..5cc0960cf54 --- /dev/null +++ b/assets/tools_esp-api_README.md.WcTRZBDR.js @@ -0,0 +1 @@ +import{_ as o,c as t,a3 as s,o as r}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Developer README for ESP API Command Line Tool","description":"","frontmatter":{},"headers":[],"relativePath":"tools/esp-api/README.md","filePath":"tools/esp-api/README.md","lastUpdated":1731340314000}'),a={name:"tools/esp-api/README.md"};function i(n,e,l,d,p,c){return r(),t("div",null,e[0]||(e[0]=[s('

Developer README for ESP API Command Line Tool

Overview

This tool is designed to interact with HPCC ESP services, providing four commands: describe, test, list-services, and list-methods.

  • describe: Allows users to explore available services, methods, and their request-response structures.

  • test: Enables sending requests in various formats (XML, JSON, or form query strings) to the ESP services.

  • list-services: Invoked by the auto-complete script, this command provides a list of names of all ESP services.

  • list-methods: Invoked by the auto-complete script, this command provides a list of names of all methods within an ESP service.

Usage Notes

  • ESDL Directory Location: The tool gathers the directory of the ESDL files from an environment configuration variable; gets the install path from jutil library and appends /componentfiles/esdl_files/.

  • Server and Port Defaults: When using the test command, if the server and port are not specified, the tool defaults to interacting with an ESP instance located at http://127.0.0.1:8010.

Ideas for Expansion

  • Custom ESDL Directory Argument: An additional argument could be introduced to allow users to specify the directory of the ESDL files directly in the command.

  • Template Request Generation: A feature could be added to generate template XML or JSON requests. This would simplify the process of filling out requests by providing a pre-structured template.

  • Credential Prompts: The tool could be expanded to prompt for the username and password upon a 401 Unauthorized response.

  • Selective Response Extraction: Another potential feature is to allow extraction of specific tags from the response using XPath expressions. This would make it easier to parse and analyze responses.

',8)]))}const u=o(a,[["render",i]]);export{h as __pageData,u as default}; diff --git a/assets/tools_esp-api_README.md.WcTRZBDR.lean.js b/assets/tools_esp-api_README.md.WcTRZBDR.lean.js new file mode 100644 index 00000000000..5cc0960cf54 --- /dev/null +++ b/assets/tools_esp-api_README.md.WcTRZBDR.lean.js @@ -0,0 +1 @@ +import{_ as o,c as t,a3 as s,o as r}from"./chunks/framework.DkhCEVKm.js";const h=JSON.parse('{"title":"Developer README for ESP API Command Line Tool","description":"","frontmatter":{},"headers":[],"relativePath":"tools/esp-api/README.md","filePath":"tools/esp-api/README.md","lastUpdated":1731340314000}'),a={name:"tools/esp-api/README.md"};function i(n,e,l,d,p,c){return r(),t("div",null,e[0]||(e[0]=[s('

Developer README for ESP API Command Line Tool

Overview

This tool is designed to interact with HPCC ESP services, providing four commands: describe, test, list-services, and list-methods.

  • describe: Allows users to explore available services, methods, and their request-response structures.

  • test: Enables sending requests in various formats (XML, JSON, or form query strings) to the ESP services.

  • list-services: Invoked by the auto-complete script, this command provides a list of names of all ESP services.

  • list-methods: Invoked by the auto-complete script, this command provides a list of names of all methods within an ESP service.

Usage Notes

  • ESDL Directory Location: The tool gathers the directory of the ESDL files from an environment configuration variable; gets the install path from jutil library and appends /componentfiles/esdl_files/.

  • Server and Port Defaults: When using the test command, if the server and port are not specified, the tool defaults to interacting with an ESP instance located at http://127.0.0.1:8010.

Ideas for Expansion

  • Custom ESDL Directory Argument: An additional argument could be introduced to allow users to specify the directory of the ESDL files directly in the command.

  • Template Request Generation: A feature could be added to generate template XML or JSON requests. This would simplify the process of filling out requests by providing a pre-structured template.

  • Credential Prompts: The tool could be expanded to prompt for the username and password upon a 401 Unauthorized response.

  • Selective Response Extraction: Another potential feature is to allow extraction of specific tags from the response using XPath expressions. This would make it easier to parse and analyze responses.

',8)]))}const u=o(a,[["render",i]]);export{h as __pageData,u as default}; diff --git a/assets/tools_tagging_README.md.CdzS0CPr.js b/assets/tools_tagging_README.md.CdzS0CPr.js new file mode 100644 index 00000000000..75904aa1764 --- /dev/null +++ b/assets/tools_tagging_README.md.CdzS0CPr.js @@ -0,0 +1,13 @@ +import{_ as a,c as s,a3 as n,o as i}from"./chunks/framework.DkhCEVKm.js";const d=JSON.parse('{"title":"Tagging new versions","description":"","frontmatter":{},"headers":[],"relativePath":"tools/tagging/README.md","filePath":"tools/tagging/README.md","lastUpdated":1731340314000}'),t={name:"tools/tagging/README.md"};function o(l,e,p,r,c,g){return i(),s("div",null,e[0]||(e[0]=[n(`

Tagging new versions

General

The file tools/git/aliases.sh contains various git aliases which are useful when using git, and may be used by the merge scripts.

The file env.sh.example contains some example environment variable settings. Copy that locally to env.sh and modify it to match your local setup.

Before running any of the other scripts, process the contents of that file as a source file

. env.sh

to initialize the common environment variables.

Pre-requisites

The following tools are required:

  • git
  • helm

The following repositories should be checked out in a directory reserved for merging and tagging (default for scripts is ~/git):

git clone git@github.com:hpcc-systems/eclide.git
+git clone git@github.com:hpcc-systems/hpcc4j.git HPCC-JAPIs
+git clone git@github.com:hpcc-systems/Spark-HPCC.git
+git clone git@github.com:hpcc-systems/LN.git ln
+git clone git@github.com:hpcc-systems/HPCC-Platform.git hpcc
+git clone git@github.com:hpcc-systems/helm-chart.git

The following are required for builds prior to 8.12.x

git clone git@github.com:hpcc-systems/nagios-monitoring.git
+git clone git@github.com:hpcc-systems/ganglia-monitoring.git

The files git-fixversion and git-unupmerge can copied so they are on your default path, and then they will be available as git commands.

Tagging new versions

The following process should be followed when tagging a new set of versions.

  1. Upmerge all changes between candidate branches for the different versions

You can set the all environment variable to a subset of the projects (e.g. export all=hpcc) if there are no changes in the other repositories. The only effect for projects that are upmerged with no changes will be that they gain an empty merge transaction. If multiple people are merging PRs to different repositories it may be safer to upmerge all projects.

For example:

./upmerge A.a.x candidate-A.b.x
+./upmerge A.b.x candidate-A.c.x
+./upmerge A.b.x candidate-B.0.x
+./upmerge B.0.x master
  1. Create new point-release candidate branches:
./gorc.sh A.a.x
+./gorc.sh A.b.x
+./gorc.sh A.c.x

Taking a build gold:

Go gold with each of the explicit versions

./gogold.sh 7.8.76
+./gogold.sh 7.10.50

If you have merged changes onto a point-release branch you would normally create a new rc before going gold. If the change was trivial (e.g. removing an unwanted file) then you can use the --ignore option to skip that step.

Creating a new rc for an existing point release:

This normally happens after cherry-picking a late fix for a particular version, which has already been merged into the .x candidate branch.

./gorc.sh A.a.<n>

Create a new minor/major branch

A new minor branch is created from the current master...

./gominor.sh
`,33)]))}const u=a(t,[["render",o]]);export{d as __pageData,u as default}; diff --git a/assets/tools_tagging_README.md.CdzS0CPr.lean.js b/assets/tools_tagging_README.md.CdzS0CPr.lean.js new file mode 100644 index 00000000000..75904aa1764 --- /dev/null +++ b/assets/tools_tagging_README.md.CdzS0CPr.lean.js @@ -0,0 +1,13 @@ +import{_ as a,c as s,a3 as n,o as i}from"./chunks/framework.DkhCEVKm.js";const d=JSON.parse('{"title":"Tagging new versions","description":"","frontmatter":{},"headers":[],"relativePath":"tools/tagging/README.md","filePath":"tools/tagging/README.md","lastUpdated":1731340314000}'),t={name:"tools/tagging/README.md"};function o(l,e,p,r,c,g){return i(),s("div",null,e[0]||(e[0]=[n(`

Tagging new versions

General

The file tools/git/aliases.sh contains various git aliases which are useful when using git, and may be used by the merge scripts.

The file env.sh.example contains some example environment variable settings. Copy that locally to env.sh and modify it to match your local setup.

Before running any of the other scripts, process the contents of that file as a source file

. env.sh

to initialize the common environment variables.

Pre-requisites

The following tools are required:

  • git
  • helm

The following repositories should be checked out in a directory reserved for merging and tagging (default for scripts is ~/git):

git clone git@github.com:hpcc-systems/eclide.git
+git clone git@github.com:hpcc-systems/hpcc4j.git HPCC-JAPIs
+git clone git@github.com:hpcc-systems/Spark-HPCC.git
+git clone git@github.com:hpcc-systems/LN.git ln
+git clone git@github.com:hpcc-systems/HPCC-Platform.git hpcc
+git clone git@github.com:hpcc-systems/helm-chart.git

The following are required for builds prior to 8.12.x

git clone git@github.com:hpcc-systems/nagios-monitoring.git
+git clone git@github.com:hpcc-systems/ganglia-monitoring.git

The files git-fixversion and git-unupmerge can copied so they are on your default path, and then they will be available as git commands.

Tagging new versions

The following process should be followed when tagging a new set of versions.

  1. Upmerge all changes between candidate branches for the different versions

You can set the all environment variable to a subset of the projects (e.g. export all=hpcc) if there are no changes in the other repositories. The only effect for projects that are upmerged with no changes will be that they gain an empty merge transaction. If multiple people are merging PRs to different repositories it may be safer to upmerge all projects.

For example:

./upmerge A.a.x candidate-A.b.x
+./upmerge A.b.x candidate-A.c.x
+./upmerge A.b.x candidate-B.0.x
+./upmerge B.0.x master
  1. Create new point-release candidate branches:
./gorc.sh A.a.x
+./gorc.sh A.b.x
+./gorc.sh A.c.x

Taking a build gold:

Go gold with each of the explicit versions

./gogold.sh 7.8.76
+./gogold.sh 7.10.50

If you have merged changes onto a point-release branch you would normally create a new rc before going gold. If the change was trivial (e.g. removing an unwanted file) then you can use the --ignore option to skip that step.

Creating a new rc for an existing point release:

This normally happens after cherry-picking a late fix for a particular version, which has already been merged into the .x candidate branch.

./gorc.sh A.a.<n>

Create a new minor/major branch

A new minor branch is created from the current master...

./gominor.sh
`,33)]))}const u=a(t,[["render",o]]);export{d as __pageData,u as default}; diff --git a/cmake_modules/DOCUMENTATION.html b/cmake_modules/DOCUMENTATION.html new file mode 100644 index 00000000000..939f3b224eb --- /dev/null +++ b/cmake_modules/DOCUMENTATION.html @@ -0,0 +1,75 @@ + + + + + + CMake files structure and usage | HPCC Platform + + + + + + + + + + + + + +
Skip to content

CMake files structure and usage

Directory structure of CMake files

- /

: - CMakeLists.txt - Root CMake file - version.cmake - common cmake file where version variables are set - build-config.h.cmake - cmake generation template for build-config.h

\- cmake\_modules/ - Directory storing modules and configurations for CMake
+
+:   -   FindXXXXX.cmake - CMake find files used to locate libraries,
+        headers, and binaries
+    -   commonSetup.cmake - common configuration settings for the
+        entire project (contains configure time options)
+    -   docMacros.cmake - common documentation macros used for
+        generating fop and pdf files
+    -   optionDefaults.cmake - contains common variables for the
+        platform build
+    -   distrocheck.sh - script that determines if the OS uses DEB
+        or RPM
+    -   getpackagerevisionarch.sh - script that returns OS version
+        and arch in format used for packaging
+
+    \- dependencies/ - Directory storing dependency files used for package dependencies
+
+    :   -   \<OS\>.cmake - File containing either DEB or RPM
+            dependencies for the given OS
+
+\- build-utils/ - Directory for build related utilities
+
+:   -   cleanDeb.sh - script that unpacks a deb file and rebuilds
+        with fakeroot to clean up lintain errors/warnings
+

Common Macros

  • MACRO_ENSURE_OUT_OF_SOURCE_BUILD - prevents building from with in source tree
  • HPCC_ADD_EXECUTABLE - simple wrapper around add_executable
  • HPCC_ADD_LIBRARY - simple wrapper around add_library
  • PARSE_ARGUMENTS - macro that can be used by other macros and functions for arg list parsing
  • HPCC_ADD_SUBDIRECTORY - argument controlled add subdirectory wrapper
  • HPCC_ADD_SUBDIRECTORY(test t1 t2 t3) - Will add the subdirectory test if t1,t2, or t3 are set to any value other then False/OFF/0
  • LOG_PLUGIN - used to log any code based plugin for the platform
  • ADD_PLUGIN - adds a plugin with optional build dependencies to the build if dependencies are found

Documentation Macros

  • RUN_XSLTPROC - Runs xsltproc using given args
  • RUN_FOP - Runs fop using given args
  • CLEAN_REL_BOOK - Uses a custom xsl and xsltproc to clean relative book paths from given xml file
  • CLEAN_REL_SET - Uses a custom xsl and xsltproc to clean relative set paths from given xml file
  • DOCBOOK_TO_PDF - Master macro used to generate pdf, uses above macros

Initfiles macro

  • GENERATE_BASH - used to run processor program on given files to replace ###<REPLACE>### with given variables FindXXXXX.cmake

Some standard techniques used in Cmake project files

Common looping

Use FOREACH:

FOREACH( oITEMS
+  item1
+  item2
+  item3
+  item4
+  item5
+)
+  Actions on each item here.
+ENDFOREACH ( oITEMS )
+

Common installs over just install

  • install ( FILES ... ) - installs item with 664 permissions
  • install ( PROGRAMS ... ) - installs runable item with 755 permissions
  • install ( TARGETS ... ) - installs built target with 755 permissions
  • install ( DIRECTORY ... ) - installs directory with 777 permissions

Common settings for generated source files

  • set_source_files_properties(<file> PROPERTIES GENERATED TRUE) - Must be set on generated source files or dependency generation fails and increases build time.

Using custom commands between multiple cmake files

  • GET_TARGET_PROPERTY(<VAR from other cmake file> <var for this file> LOCATION)
  • GET_TARGET_PROPERTY(ESDL_EXE esdl LOCATION) - will get from the top level cache the ESDL_EXE value and set it in esdl for your current cmake file

USE add_custom_command only when 100% needed.

All directories in a cmake project should have a CMakeLists.txt file and be called from the upper level project with an add_subdirectory or HPCC_ADD_SUBDIRECTORY

When you have a property that will be shared between cmake projects use define_property to set it in the top level cache.

  • define_property(GLOBAL PROPERTY TEST_TARGET BRIEF_DOCS "test doc" FULL_DOCS "Full test doc")
  • mark_as_advanced(TEST_TARGET) - this is required to force the property into the top level cache.CMake Layout:

FindXXXXX.cmake format

All of our Find scripts use the following format:

NOT XXXXX_FOUND
+  Externals set
+    define needed vars for finding external based libraries/headers
+
+    Use Native set
+      use FIND_PATH to locate headers
+      use FIND_LIBRARY to find libs
+
+Include Cmake macros file for package handling
+define package handling args for find return  (This will set XXXXX_FOUND)
+
+XXXXX_FOUND
+  perform any modifications you feel is needed for the find
+
+Mark defined variables used in package handling args as advanced for return
+

Will define when done:

XXXXX_FOUND
+XXXXX_INCLUDE_DIR
+XXXXX_LIBRARIES
+

(more can be defined, but must be at min the previous unless looking for only a binary)

For an example, see FindAPR.cmake

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/CodeGenerator.html b/devdoc/CodeGenerator.html new file mode 100644 index 00000000000..10798fa75b1 --- /dev/null +++ b/devdoc/CodeGenerator.html @@ -0,0 +1,36 @@ + + + + + + Eclcc/Code generator | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Introduction

Purpose

The primary purpose of the code generator is to take an ECL query and convert it into a work unit that is suitable for running by one of the engines.

Aims

The code generator has to do its job accurately. If the code generator does not correctly map the ECL to the workunit it can lead to corrupt data and invalid results. Problems like that can often be very hard and frustrating for the ECL users to track down.

There is also a strong emphasis on generating output that is as good as possible. Eclcc contains many different optimization stages, and is extensible to allow others to be easily added.

Eclcc needs to be able to cope with reasonably large jobs. Queries that contain several megabytes of ECL, and generate tens of thousands of activities, and 10s of Mb of C++ are routine. These queries need to be processed relatively quickly.

Key ideas

Nearly all the processing of ECL is done using an expression graph. The representation of the expression graph has some particular characteristics:

  • Once the nodes in the expression graph have been created they are NEVER modified.
  • Nodes in the expression graph are ALWAYS commoned up if they are identical.
  • Each node in the expression graph is link counted (see below) to track its lifetime.
  • If a modified graph is required a new graph is created (sharing nodes from the old one)

The ECL language is a declarative language, and in general is assumed to be pure - i.e. there are no side-effects, expressions can be evaluated lazily and re-evaluating an expression causes no problems. This allows eclcc to transform the graph in lots of interesting ways. (Life is never that simple so there are mechanisms for handling the exceptions.)

From declarative to imperative

One of the main challenges with eclcc is converting the declarative ECL code into imperative C++ code. One key problem is it needs to try to ensure that code is only evaluated when it is required, but that it is also only evaluated once. It isn't always possible to satisfy both constraints - for example a global dataset expression used within a child query. Should it be evaluated once before the activity containing the child query is called, or each time the child query is called? If it is called on demand then it may not be evaluated as efficiently...

This issue complicates many of the optimizations and transformations that are done to the queries. Long term the plan is to allow the engines to support more delayed lazy-evaluation, so that whether something is evaluated is more dynamic rather than static.

Flow of processing

The idealised view of the processing within eclcc follows the following stages:

  • Parse the ECL into an expression graph.
  • Expand out function calls.
  • Normalize the expression graph so it is in a consistent format.
  • Normalize the references to fields within datasets to tie them up with their scopes.
  • Apply various global optimizations.
  • Translate logical operations into the activities that will implement them.
  • Resource and generate the global graph
  • For each activity, resource, optimize and generate its child graphs.

In practice the progression is not so clear cut. There tends to be some overlap between the different stages, and some of them may occur in slightly different orders. However the order broadly holds.

Working on the code generator

The regression suite

Before any change is accepted for the code generator it is always run against several regression suites to ensure that it doesn't introduce any problems, and that the change has the desired effect. There are several different regression suites:

  • testing/regress/ecl - The run time regression suite.
  • ecl/regress - a compiler regression suite. This contains tests that cannot run and error tests.
  • LN private suite - This contains a large selection (>10Gb) of archived queries. The contain proprietary code so unfortunately cannot be released as open source.

The ecl/regress directory contains a script 'regress.sh' that is used for running the regression tests. It should be executed in the directory containing the ecl files. The script generates the c++ code (and workunits) for each of the source files to a target directory, and then executes a comparison program to compare the new results with a previous "golden" reference set.

Before making any changes to the compiler, a reference set should be created by running the regression script and copying the generated files to the reference directory.

Here is a sample command line

~/dev/hpcc/ecl/regress/regress.sh -t /regress/hpcc -e /home/<user>/buildr/Release/bin/eclcc -I /home/<user>/dev/hpcc/ecl/regress/modules -I /home/<user>/dev/hpcc/plugins/javaembed -I /home/<user>/dev/hpcc/plugins/v8embed -c /regress/hpcc.master -d bcompare

(A version of this command resides in a shell script in each of my regression suite directories, with the -t and -c options adapted for each suite.)

For a full list of options execute the script with no parameters, or take a look at the script itself. A couple of useful options are:

  • The script can be run on a single file by using the -q option.
  • The (-e) option selects the path of the eclcc. This is particularly useful when running from the build directory (see below), or using multiple build directories to compare behaviour between different versions.

We strongly recommend using a comparison program which allows rules to be defined to ignore certain differences (e.g., beyond compare).

Running directly from the build directory

It is much quicker to run eclcc directly from the build directory, rather than deploying a system and running eclcc from there. To do this you need to configure some options that eclcc requires, e.g. where the include files are found. The options can be set by either setting environment variables or by specifiying options in an eclcc.ini file. The following are the names of the different options:


Environment flag Ini file option


CL_PATH compilerPath

ECLCC_LIBRARY_PATH libraryPath

ECLCC_INCLUDE_PATH includePath

ECLCC_PLUGIN_PATH plugins

HPCC_FILEHOOKS_PATH filehooks

ECLCC_TPL_PATH templatePath

ECLCC_ECLLIBRARY_PATH eclLibrariesPath

ECLCC_ECLBUNDLE_PATH eclBundlesPath

The eclcc.ini can either be a file in the local directory, or specified on the eclcc command line with -specs. Including the settings in a local eclcc.ini file also it easy to debug eclcc directly from the build directory within the eclipse environment.

Hints and tips

  • Logging

    There is an option for eclcc to output a logging file, and another to specify the level of detail in that logging file. If the detail level is above 500 then the expresssion tree for the query is output to the logging file after each of the code transformations. The tracing is very useful for tracking down at which stage inconsistencies are introduced in the expression graph, and also for learning how each transformation affects the query.

    The output format defaults to ECL - which is regenerated from the expression tree. (This ECL cannot generally be compiled without editing - partly because it contains extra annoations.) Use either of the following:

    eclcc myfile.ecl --logfile myfile.log --logdetail 999

    regress.sh -q myfile.ecl -l myfile.log

  • -ftraceIR

    There is a debug option (-ftraceIR) that generates an intermediate representation of the expression graph rather than regenerating ECL. The output tends to be less compact and harder to read quickly, but has the advantage of being better structured, and contains more details of the internal representation. ecl/hql/hqlir.cpp contains more details of the format.

  • Adding extra logging into the source code

    If you want to add tracing of expressions at any point in the code generation then adding either of the following calls will include the expression details in the log file:

    dbglogExpr(expr); // regenerate the ecl for an expression. See other functions in ecl/hql/hqlthql.hpp

    EclIR::dbglogIR(expr); // regenerate the IR for an expression. See other functions in ecl/hql/hqlir.hpp

  • Logging while debugging

    If you are debugging inside gdb it is often useful to be able to dump out details of an expression. Calling EclIR:dump_ir(expr); will generate the IR to stdout.

    p EclIR::dump_ir(expr)

    The function can also be used with multiple parameters. Each expression will be dumped out, but common child nodes will only be generated once. This can be very useful when trying to determine the difference between two expressions. The quickest way is to call EclIR::dump_ir(expr1, expr2). The first difference between the expressions will be the expression that follows the first "return".

  • Expression sequence ids.

    Sometimes it can be hard to determine where a particular IHqlExpression node was created. If that is the case, then defining DEBUG_TRACK_INSTANCEID (in ecl/hql/hqlexpr.ipp) will add a unique sequence number to each IHqlExpression that is created. There is also a function checkSeqId() at the start of ecl/hql/hqlexpr.cpp which is called whenever an expression is created, linked, released etc.. Setting a breakpoint in that function can allow you to trace back exactly when and why a particular node was created.

Expressions

Expression Graph representation

The key data structure within eclcc is the graph representation. The design has some key elements.

  • Once a node is created it is never modified.

    Some derived information (e.g., sort order, number of records, unique hash, ...) might be calculated and stored in the class after it has been created, but that doesn't change what the node represents in any way. Some nodes are created in stages - e.g., records, modules. These nodes are marked as fully completed when closeExpr() is called, after which they cannot be modified.

  • Nodes are always commoned up.

    If the same operator has the same arguments and type then there will be a unique IHqlExpression to represent it. This helps ensure that graphs stay as graphs and don't get converted to trees. It also helps with optimizations, and allows code duplicated in two different contexts to be brought together.

  • The nodes are link counted.

    Link counts are used to control the lifetime of the expression objects. Whenever a reference to an expression node is held, its link count is increased, and decreased when no longer required. The node is freed when there are no more references. (This generally works well, but does give us problems with forward references.)

  • The access to the graph is through interfaces.

    The main interfaces are IHqlExpression, IHqlDataset and IHqlScope. They are all defined in hqlexpr.hpp. The aim of the interfaces is to hide the implementation of the expression nodes so they can be restructured and changed without affecting any other code.

  • The expression classes use interfaces and a type field rather than polymorphism. This could be argued to be bad object design...but.

    There are more than 500 different possible operators. If a class was created for each of them the system would quickly become unwieldy. Instead there are several different classes which model the different types of expression (dataset/expression/scope).

    The interfaces contain everything needed to create and interrogate an expression tree, but they do not contain functionality for directly processing the graph.

    To avoid some of the shortcomings of type fields there are various mechanisms for accessing derived attributes which avoid interrogating the type field.

  • Memory consumption is critical.

It is not unusual to have 10M or even 100M nodes in memory as a query is being processed. At that scale the memory consumption of each node matters - so great care should be taken when considering increasing the size of the objects. The node classes contain a class hierarchy which is there purely to reduce the memory consumption - not to reflect the functionality. With no memory constraints they wouldn't be there, but removing a single pointer per node can save 1Gb of memory usage for very complex queries.

IHqlExpression

This is the interface that is used to walk and interrogate the expression graph once it has been created. Some of the main functions are: getOperator() What does this node represent? It returns a member of the node_operator enumerated type. numChildren() How many arguments does node have? queryChild(unsigned n) What is the nth child? If the argument is out of range it returns NULL. queryType() The type of this node. queryBody() Used to skip annotations (see below) queryProperty() Does this node have a child which is an attribute that matches a given name. (see below for more about attributes). queryValue() For a no_constant return the value of the constant. It returns NULL otherwise.

The nodes in the expression graph are created through factory functions. Some of the expression types have specialised functions - e.g., createDataset, createRow, createDictionary, but scalar expressions and actions are normally created with createValue().

Note: Generally ownership of the arguments to the createX() functions are assumed to be taken over by the newly created node.

The values of the enumeration constants in node_operator are used to calculate "crcs" which are used to check if the ECL for a query matches, and if disk and index record formats match. It contains quite a few legacy entries no_unusedXXX which can be used for new operators (otherwise new operators must be added to the end).

IHqlSimpleScope

This interface is implemented by records, and is used to map names to the fields within the records. If a record contains IFBLOCKs then each of the fields in the ifblock is defined in the IHqlSimpleScope for the containing record.

IHqlScope

Normally obtained by calling IHqlExpression::queryScope(). It is primarily used in the parser to resolve fields from within modules.

The ECL is parsed on demand so as the symbol is looked up it may cause a cascade of ECL to be compiled. The lookup context (HqlLookupContext ) is passed to IHqlScope::lookupSymbol() for several reasons:

  • It contains information about the active repository - the source of the ECL which will be dynamically parsed.
  • It contains caches of expanded functions - to avoid repeating expansion transforms.
  • Some members are used for tracking definitions that are read to build dependency graphs, or archives of submitted queries.

The interface IHqlScope currently has some members that are used for creation; this should be refactored and placed in a different interface.

IHqlDataset

This is normally obtained by calling IHqlExpression::queryDataset(). It has shrunk in size over time, and could quite possibly be folded into IHqlExpression with little pain.

There is a distinction in the code generator between "tables" and "datasets". A table (IHqlDataset::queryTable()) is a dataset operation that defines a new output record. Any operation that has a transform or record that defines an output record (e.g., PROJECT,TABLE) is a table, whilst those that don't (e.g., a filter, dedup) are not. There are a few apparent exceptions -e.g., IF (This is controlled by definesColumnList() which returns true the operator is a table.)

Properties and attributes

There are two related by slightly different concepts. An attribute refers to the explicit flags that are added to operators (e.g., , LOCAL, KEEP(n) etc. specified in the ECL or some internal attributes added by the code generator). There are a couple of different functions for creating attributes. createExtraAttribute() should be used by default. createAttribute() is reserved for an attribute that never has any arguments, or in unusual situations where it is important that the arguments are never transformed. They are tested using queryAttribute()/hasAttribute() and represented by nodes of kind no_attr/no_expr_attr.

The term "property" refers to computed information (e.g., record counts) that can be derived from the operator, its arguments and attributes. They are covered in more detail below.

Field references

Fields can be selected from active rows of a dataset in three main ways:

  • Some operators define LEFT/RIGHT to represent an input or processed dataset. Fields from these active rows are referenced with LEFT.<field-name>. Here LEFT or RIGHT is the "selector".

  • Other operators use the input dataset as the selector. E.g., myFile(myFile.id != 0). Here the input dataset is the "selector".

  • Often when the input dataset is used as the selector it can be omitted. E.g., myFile(id != 0). This is implicitly expanded by the PARSER to the second form. A reference to a field is always represented in the expression graph as a node of kind no_select (with createSelectExpr). The first child is the selector, and the second is the field. Needless to say there are some complications...

  • LEFT/RIGHT.

    The problem is that the different uses of LEFT/RIGHT need to be disambiguated since there may be several different uses of LEFT in a query. This is especially true when operations are executed in child queries. LEFT is represented by a node no_left(record, selSeq). Often the record is sufficient to disambiguate the uses, but there are situations where it isn't enough. So in addition no_left has a child which is a selSeq (selector sequence) which is added as a child attribute of the PROJECT or other operator. At parse time it is a function of the input dataset that is later normalized to a unique id to reduce the transformation work.

  • Active datasets. It is slightly more complicated - because the dataset used as the selector can be any upstream dataset up to the nearest table. So the following ECL code is legal:

    x := DATASET(...)
    +y := x(x.id != 0);
    +z := y(x.id != 100);
    +

Here the reference to x.id in the definition of z is referring to a field in the input dataset.

Because of these semantics the selector in a normalized tree is actually inputDataset->queryNormalizedSelector() rather than inputDatset. This function currently returns the table expression node (but it may change in the future see below).

Attribute "new"

In some situations ECL allows child datasets to be treated as a dataset without an explicit NORMALIZE. E.g., EXISTS(myDataset.ChildDataset);

This is primarily to enable efficient aggregates on disk files to be generated, but it adds some complications with an expression of the form dataset.childdataset.grandchild. E.g.,:

EXISTS(dataset(EXISTS(dataset.childdataset.grandchild))
+

Or:

EXISTS(dataset.childdataset(EXISTS(dataset.childdataset.grandchild))
+

In the first example dataset.childdataset within the dataset.childdataset.grandchild is a reference to a dataset that doesn't have an active cursor and needs to be iterated), whilst in the second it refers to an active cursor.

To differentiate between the two, all references to fields within datasets/rows that don't have active selectors have an additional attribute("new") as a child of the select. So a no_select with a "new" attribute requires the dataset to be created, one without is a member of an active dataset cursor.

If you have a nested row, the new attribute is added to the selection from the dataset, rather than the selection from the nested row. The functions queryDatasetCursor() and querySelectorDataset()) are used to help interpret the meaning.

(An alternative would be to use a different node from no_select - possibly this should be considered - it would be more space efficient.)

The expression graph generated by the ECL parser doesn't contain any new attributes. These are added as one of the first stages of normalizing the expression graph. Any code that works on normalized expressions needs to take care to interpret no_selects correctly.

Transforming selects

When an expression graph is transformed and none of the records are changed, the representation of LEFT/RIGHT remains the same. This means any no_select nodes in the expression tree will also stay the same.

However, if the transform modifies a table (highly likely) it means that the selector for the second form of field selector will also change. Unfortunately this means that transforms often cannot be short-circuited.

It could significantly reduce the extent of the graph that needs traversing, and the number of nodes replaced in a transformed graph if this could be avoided. One possibility is to use a different value for dataset->queryNormalizedSelector() using a unique id associated with the table. I think it would be a good long term change, but it would require unique ids (similar to the selSeq) to be added to all table expressions, and correctly preserved by any optimization.

Annotations

Sometimes it is useful to add information into the expression graph (e.g., symbol names, position information) that doesn't change the meaning, but should be preserved. Annotations allow information to be added in this way.

An annotation's implementation of IHqlExpression generally delegates the majority of the methods through to the annotated expression. This means that most code that interrogates the expression graph can ignore their presence, which simplifies the caller significantly. However transforms need to be careful (see below).

Information about the annotation can be obtained by calling IHqlExpression:: getAnnotationKind() and IHqlExpression:: queryAnnotation().

Associated side-effects

In legacy ECL you will see code like the following::

EXPORT a(x) := FUNCTION
+   Y := F(x);
+   OUTPUT(Y);
+   RETURN G(Y);
+END;
+

The assumption is that whenever a(x) is evaluated the value of Y will be output. However that doesn't particularly fit in with a declarative expression graph. The code generator creates a special node (no_compound) with child(0) as the output action, and child(1) as the value to be evaluated (g(Y)).

If the expression ends up being included in the final query then the action will also be included (via the no_compound). At a later stage the action is migrated to a position in the graph where actions are normally evaluated.

Derived properties

There are many pieces of information that it is useful to know about a node in the expression graph - many of which would be expensive to recomputed each time there were required. Eclcc has several mechanisms for caching derived information so it is available efficiently.

  • Boolean flags - getInfoFlags()/getInfoFlags2().

    There are many Boolean attributes of an expression that are useful to know - e.g., is it constant, does it have side-effects, does it reference any fields from a dataset etc. etc. The bulk of these are calculated and stored in a couple of members of the expression class. They are normally retrieved via accessor functions e.g., containsAssertKeyed(IHqlExpression*).

  • Active datasets - gatherTablesUsed().

    It is very common to want to know which datasets an expression references. This information is calculated and cached on demand and accessed via the IHqlExpression::gatherTablesUsed() functions. There are a couple of other functions IHqlExpression::isIndependentOfScope() and IHqlExpression::usesSelector() which provide efficient functions for common uses.

  • Information stored in the type.

    Currently datasets contain information about sort order, distribution and grouping as part of the expression type. This information should be accessed through the accessor functions applied to the expression (e.g., isGrouped(expr)). At some point in the future it is planned to move this information as a general derived property (see next).

  • Other derived property.

    There is a mechanism (in hqlattr) for calculating and caching an arbitrary derived property of an expression. It is currently used for number of rows, location-independent representation, maximum record size etc. . There are typically accessor functions to access the cached information (rather than calling the underlying IHqlExpression::queryAttribute() function).

  • Helper functions.

    Some information doesn't need to be cached because it isn't expensive to calculate, but rather than duplicating the code, a helper function is provided. E.g., queryOriginalRecord() and hasUnknownTransform(). They are not part of the interface because the number would make the interface unwieldy and they can be completely calculated from the public functions.

    However, it can be very hard to find the function you are looking for, and they would greatly benefit from being grouped e.g., into namespaces.

Transformations

One of the key processes in eclcc is walking and transforming the expression graphs. Both of these are covered by the term transformations. One of the key things to bear in mind is that you need to walk the expression graph as a graph, not as a tree. If you have already examined a node once you shouldn't repeat the work - otherwise the execution time may be exponential with node depth.

Other things to bear in mind

  • If a node isn't modified don't create a new one - return a link to the old one.
  • You generally need to walk the graph and gather some information before creating a modified graph. Sometimes creating a new graph can be short-circuited if no changes will be required.
  • Sometimes you can be tempted to try and short-circuit transforming part of a graph (e.g., the arguments to a dataset activity), but because of the way references to fields within dataset work that often doesn't work.
  • If an expression is moved to another place in the graph, you need to be very careful to check if the original context was conditional and that the new context is not.
  • The meaning of expressions can be context dependent. E.g., References to active datasets can be ambiguous.
  • Never walk the expressions as a tree, always as a graph!
  • Be careful with annotations.

It is essential that an expression that is used in different contexts with different annotations (e.g., two different named symbols) is consistently transformed. Otherwise it is possible for a graph to be converted into a tree. E.g.,:

A := x; B := x; C = A + B;
+

must not be converted to:

A' := x'; B' := X'';  C' := A' + B';
+

For this reason most transformers will check if expr->queryBody() matches expr, and if not will transform the body (the unannotated expression), and then clone any annotations.

Some examples of the work done by transformations are:

  • Constant folding.
  • Expanding function calls.
  • Walking the graph and reporting warnings.
  • Optimizing the order and removing redundant activities.
  • Reducing the fields flowing through the generated graph.
  • Spotting common sub expressions.
  • Calculating the best location to evaluate an expression (e.g., globally instead of in a child query).
  • Many, many others.

Some more details on the individual transforms are given below..

Key Stages

Parsing

The first job of eclcc is to parse the ECL into an expression graph. The source for the ECL can come from various different sources (archive, source files, remote repository). The details are hidden behind the IEclSource/IEclSourceCollection interfaces. The createRepository() function is then used to resolve and parse the various source files on demand.

Several things occur while the ECL is being parsed:

  • Function definitions are expanded inline.

    A slightly unusual behaviour. It means that the expression tree is a fully expanded expression -which is better suited to processing and optimizing.

  • Some limited constant folding occurs.

    When a function is expanded, often it means that some of the test conditions are always true/false. To reduce the transformations the condition may be folded early on.

  • When a symbol is referenced from another module this will recursively cause the ECL for that module (or definition within that module) to be parsed.

  • Currently the semantic checking is done as the ECL is parsed.

    If we are going to fully support template functions and delayed expansion of functions this will probably have to change so that a syntax tree is built first, and then the semantic checking is done later.

Normalizing

There are various problems with the expression graph that comes out of the parser:

  • Records can have values as children (e.g., { myField := infield.value} ), but it causes chaos if record definitions can change while other transformations are going on. So the normalization removes values from fields.

  • Some activities use records to define the values that output records should contain (e.g., TABLE). These are now converted to another form (e.g., no_newusertable).

  • Sometimes expressions have multiple definition names. Symbols and annotations are rationalized and commoned up to aid commoning up other expressions.

  • Some PATTERN definitions are recursive by name. They are resolved to a form that works if all symbols are removed.

  • The CASE/MAP representation for a dataset/action is awkward for the transforms to process. They are converted to nested Ifs.

    (At some point a different representation might be a good idea.)

  • EVALUATE is a weird syntax. Instances are replaced with equivalent code which is much easier to subsequently process.

  • The datasets used in index definitions are primarily there to provide details of the fields. The dataset itself may be very complex and may not actually be used. The dataset input to an index is replaced with a dummy "null" dataset to avoid unnecessary graph transforming, and avoid introducing any additional incorrect dependencies.

Scope checking

Generally if you use LEFT/RIGHT then the input rows are going to be available wherever they are used. However if they are passed into a function, and that function uses them inside a definition marked as global then that is invalid (since by definition global expressions don't have any context).

Similarly if you use syntax <dataset>.<field>, its validity and meaning depends on whether <dataset> is active. The scope transformer ensures that all references to fields are legal, and adds a "new" attribute to any no_selects where it is necessary.

Constant folding: foldHqlExpression

This transform simplifies the expression tree. Its aim is to simplify scalar expressions, and dataset expressions that are valid whether or not the nodes are shared. Some examples are:

  • 1 + 2 => 3 and any other operation on scalar constants.
  • IF(true, x, y) => x
  • COUNT(<empty-dataset>) => 0
  • IF (a = b, 'c', 'd') = 'd' => IF(a=b, false, true) => a != b
  • Simplifying sorts, projects filters on empty datasets

Most of the optimizations are fairly standard, but a few have been added to cover more esoteric examples which have occurred in queries over the years.

This transform also supports the option to percolate constants through the graph. E.g., if a project assigns the value 3 to a field, it can substitute the value 3 wherever that field is used in subsequent activities. This can often lead to further opportunities for constant folding (and removing fields in the implicit project).

Expression optimizer: optimizeHqlExpression

This transformer is used to simplify, combine and reorder dataset expressions. The transformer takes care to count the number of times each expression is used to ensure that none of the transformations cause duplication. E.g., swapping a filter with a sort is a good idea, but if there are two filters of the same sort and they are both swapped you will now be duplicating the sort.

Some examples of the optimizations include:

  • COUNT(SORT(x)) => COUNT(x)
  • Moving filters over projects, joins, sorts.
  • Combining adjacent projects, projects and joins.
  • Removing redundant sorts or distributes
  • Moving filters from JOINs to their inputs.
  • Combining activities e.g., CHOOSEN(SORT(x)) => TOPN(x)
  • Sometimes moving filters into IFs
  • Expanding out a field selected from a single row dataset.
  • Combine filters and projects into compound disk read operations.

Implicit project: insertImplicitProjects

ECL tends to be written as general purpose definitions which can then be combined. This can lead to potential inefficiencies - e.g., one definition may summarise some data in 20 different ways, this is then used by another definition which only uses a subset of those results. The implicit project transformer tracks the data flow at each point through the expression graph, and removes any fields that are not required.

This often works in combination with the other optimizations. For instance the constant percolation can remove the need for fields, and removing fields can sometimes allow a left outer join to be converted to a project.

Workunits

is this the correct term? Should it be a query? This should really be independent of this document...)

The code generator ultimately creates workunits. A workunit completely describes a generated query. It consists of two parts. There is an xml component - this contains the workflow information, the various execution graphs, and information about options. It also describes which inputs can be supplied to the query and what results are generated. The other part is the generated shared object compiled from the generated C++. This contains functions and classes that are used by the engines to execute the queries. Often the xml is compressed and stored as a resource within the shared object -so the shared object contains a complete workunit.

Workflow

The actions in a workunit are divided up into individual workflow items. Details of when each workflow item is executed, what its dependencies are stored in the <Workflow> section of the xml. The generated code also contains a class definition, with a method perform() which is used to execute the actions associated with a particular workflow item. (The class instances are created by calling the exported createProcess() factory function).

The generated code for an individual workflow item will typically call back into the engine at some point to execute a graph.

Graph

The activity graphs are stored in the xml. The graph contains details of which activities are required, how those activities link together, what dependencies there are between the activities. For each activity it might contain the following information:

  • A unique id.
  • The "kind" of the activity (from enum ThorActivityKind in eclhelper.hpp)
  • The ECL that created the activity.
  • Name of the original definition
  • Location (e.g., file, line number) of the original ECL.
  • Information about the record size, number of rows, sort order etc.
  • Hints which control options for a particular activity (e.g,, the number of threads to use while sorting).
  • Record counts and stats once the job has executed.

Each activity in a graph also has a corresponding helper class instance in the generated code. (The name of the class is cAc followed by the activity number, and the exported factory method is fAc followed by the activity number.) These classes implement the interfaces defined in eclhelper.hpp.

The engine uses the information from the xml to produce a graph of activities that need to be executed. It has a general purpose implementation of each activity kind, and it uses the class instance to tailor that general activity to the specific use e.g., what is the filter condition, what fields are set up, what is the sort order?

Inputs and Results

The workunit xml contains details of what inputs can be supplied when that workunit is run. These correspond to STORED definitions in the ECL. The result xml also contains the schema for the results that the workunit will generate.

Once an instance of the workunit has been run, the values of the results may be written back into dali's copy of the workunit so they can be retrieved and displayed.

Generated code

Aims for the generated C++ code:

  • Minimal include dependencies.

    Compile time is an issue - especially for small on-demand queries. To help reduce compile times (and dependencies with the rest of the system) the number of header files included by the generated code is kept to a minimum. In particular references to jlib, boost and icu are kept within the implementation of the runtime functions, and are not included in the public dependencies.

  • Thread-safe.

    It should be possible to use the members of an activity helper from multiple threads without issue. The helpers may contain some context dependent state, so different instances of the helpers are needed for concurrent use from different contexts (e.g., expansions of a graph.)

  • Concise.

    The code should be broadly readable, but the variable names etc. are chosen to generate compact code.

  • Functional.

    Generally the generated code assigns to a variable once, and doesn't modify it afterwards. Some assignments may be conditional, but once the variable is evaluated it isn't updated. (There are of course a few exceptions - e.g., dataset iterators)

Implementation details

First a few pointers to help understand the code within eclcc:

  • It makes extensive use of link counting. You need understand that concept to get very far.

  • If something is done more than once then that is generally split into a helper function.

    The helper functions aren't generally added to the corresponding interface (e.g., IHqlExpression) because the interface would become bloated. Instead they are added as global functions. The big disadvantage of this approach is they can be hard to find. Even better would be for them to be rationalised and organised into namespaces.

  • The code is generally thread-safe unless there would be a significant performance implication. In generally all the code used by the parser for creating expressions is thread safe. Expression graph transforms are thread-safe, and can execute in parallel if a constant (NUM_PARALLEL_TRANSFORMS) is increased. The data structures used to represent the generated code are NOT thread-safe.

  • Much of the code generation is structured fairly procedurally, with classes used to process the stages within it.

  • There is a giant "God" class HqlCppTranslator - which could really do with refactoring.

Parser

The eclcc parser uses the standard tools bison and flex to process the ECL and convert it to a

: expression graph. There are a couple of idiosyncrasies with the way it is implemented.

  • Macros with fully qualified scope.

    Slightly unusually macros are defined in the same way that other definitions are - in particular to can have references to macros in other modules. This means that there are references to macros within the grammar file (instead of being purely handled by a pre-processor). It also means the lexer keeps an active stack of macros being processed.

  • Attributes on operators.

    Many of the operators have optional attributes (e.g., KEEP, INNER, LOCAL, ...). If these were all reserved words it would remove a significant number of keywords from use as symbols, and could also mean that when a new attribute was added it broke existing code. To avoid this the lexer looks ahead in the parser tables (by following the potential reductions) to see if the token really could come next. If it can't then it isn't reserved as a symbol.

Generated code

As the workunit is created the code generator builds up the generated code and the xml for the workunit. Most of the xml generation is encapsulated within the IWorkUnit interface. The xml for the graphs is created in an IPropertyTree, and added to the workunit as a block.

C++ Output structures

The C++ generation is ultimately controlled by some template files (thortpl.cpp). The templates are plain text and contain references to allow named sections of code to be expanded at particular points.

The code generator builds up some structures in memory for each of those named sections. Once the generation is complete some peephole optimization is applied to the code. This structure is walked to expand each named section of code as required.

The BuildCtx class provides a cursor into that generated C++. It will either be created for a given named section, or more typically from another BuildCtx. It has methods for adding the different types of statements. Some are simple (e.g., addExpr()), whilst some create a compound statement (e.g., addFilter). The compound statements change the active selector so any new statements are added within that compound statement.

As well as building up a tree of expressions, this data structure also maintains a tree of associations. For instance when a value is evaluated and assigned to a temporary variable, the logical value is associated with that temporary. If the same expression is required later, the association is matched, and the temporary value is used instead of recalculating it. The associations are also used to track the active datasets, classes generated for row-meta information, activity classes etc. etc.

Activity Helper

Each activity in an expression graph will have an associated class generated in the C++. Each different activity kind expects a helper that implements a particular IHThorArg interface. E.g., a sort activity of kind TAKsort requires a helper that implements IHThorSortArg. The associated factory function is used to create instances of the helper class.

The generated class might take one of two forms:

  • A parameterised version of a library class. These are generated for simple helpers that don't have many variations (e.g., CLibrarySplitArg for TAKsplit), or for special cases that occur very frequently (CLibraryWorkUnitReadArg for internal results).
  • A class derived from a skeleton implementation of that helper (typically CThorXYZ implementing interface IHThorXYZ). The base class has default implementations of some of the functions, and any exceptions are implemented in the derived class.

Meta helper

This is a class that is used by the engines to encapsulate all the information about a single row -e.g., the format that each activity generates. It is an implementation of the IOutputMeta interface. It includes functions to

  • Return the size of the row.
  • Serialize and deserialize from disk.
  • Destroy and clean up row instances.
  • Convert to xml.
  • Provide information about the contained fields.

Building expressions

The same expression nodes are used for representing expressions in the generated C++ as the original ECL expression graph. It is important to keep track of whether an expression represents untranslated ECL, or the "translated" C++. For instance ECL has 1 based indexes, while C++ is zero based. If you processed the expression x[1] it might get translated to x[0] in C++. Translating it again would incorrectly refer to x[-1].

There are two key classes used while building the C++ for an ECL expression:

CHqlBoundExpr.

This represents a value that has been converted to C++. Depending on the type, one or more of the fields will be filled in.

CHqlBoundTarget.

This represents the target of an assignment -C++ variable(s) that are going to be assigned the result of evaluating an expression. It is almost always passed as a const parameter to a function because the target is well-defined and the function needs to update that target.

A C++ expression is sometimes converted back to an ECL pseudo-expression by calling getTranslatedExpr(). This creates an expression node of kind no_translated to indicate the child expression has already been converted.

Scalar expressions

The generation code for expressions has a hierarchy of calls. Each function is there to allow optimal code to be generated - e.g., not creating a temporary variable if none are required. A typical flow might be:

  • buildExpr(ctx, expr, bound).

    Evaluate the ecl expression "expr" and save the C++ representation in the class bound. This might then call through to...

  • buildTempExpr(ctx, expr, bound);

    Create a temporary variable, and evaluate expr and assign it to that temporary variable.... Which then calls.

  • buildExprAssign(ctx, target, expr);

    evaluate the expression, and ensure it is assigned to the C++ target "target".

    The default implementation might be to call buildExpr....

An operator must either be implemented in buildExpr() (calling a function called doBuildExprXXX) or in buildExprAssign() (calling a function called doBuildAssignXXX). Some operators are implemented in both places if there are different implementations that would be more efficient in each context.

Similarly there are several different assignment functions:

  • buildAssign(ctx, <ecl-target>, <ecl-value>);
  • buildExprAssign(ctx, <c++-target>, <ecl-value>);
  • assign(ctx, <C++target>, <c++source>)

The different varieties are there depending on whether the source value or targets have already been translated. (The names could be rationalised!)

Datasets

Most dataset operations are only implemented as activities (e.g., PARSE, DEDUP). If these are used within a transform/filter then eclcc will generate a call to a child query. An activity helper for the appropriate operation will then be generated.

However a subset of the dataset operations can also be evaluated inline without calling a child query. Some examples are filters, projects, and simple aggregation. It removes the overhead of the child query call in the simple cases, and often generates more concise code.

When datasets are evaluated inline there is a similar hierarchy of function calls:

  • buildDatasetAssign(ctx, target, expr);

    Evaluate the dataset expression, and assign it to the target (a builder interface). This may then call....

  • buildIterate(ctx, expr)

    Iterate through each of the rows in the dataset expression in turn. Which may then call...

  • buildDataset(ctx, expr, target, format)

    Build the entire dataset, and return it as a single value.

Some of the operations (e.g., aggregating a filtered dataset) can be done more efficiently by summing and filtering an iterator, than forcing the filtered dataset to be evaluated first.

Dataset cursors

The interface IHqlCppDatasetCursor allows the code generator to iterate through a dataset, or select a particular element from a dataset. It is used to hide the different representation of datasets, e.g.,

  • Blocked - the rows are in a contiguous block of memory appended one after another.
  • Array - the dataset is represented by an array of pointers to the individual rows.
  • Link counted - similar to array, but each element is also link counted.
  • Nested. Sometimes the cursor may iterate through multiple levels of child datasets.

Generally rows that are serialized (e.g., on disk) are in blocked format, and they are stored as link counted rows in memory.

Field access classes

The IReferenceSelector interface and the classes in hqltcppc[2] provide an interface for getting and setting values within a row of a dataset. They hide the details of the layout - e.g., csv/xml/raw data, and the details of exactly how each type is represented in the row.

Key filepos weirdness

The current implementation of keys in HPCC uses a format which uses a separate 8 byte integer field which was historically used to store the file position in the original file. Other complications are that the integer fields are stored big-endian, and signed integer values are biased.

This introduces some complication in the way indexes are handled. You will often find that the logical index definition is replaced with a physical index definition, followed by a project to convert it to the logical view. A similar process occurs for disk files to support VIRTUAL(FILEPOSITION) etc.

Source code

The following are the main directories used by the ecl compiler.


Directory Contents


rtl/eclrtpl Template text files used to generate the C++ code

rtl/include Headers that declare interfaces implemented by the generated code

common/deftype Interfaces and classes for scalar types and values.

common/workunit Code for managing the representation of a work unit.

ecl/hql Classes and interfaces for parsing and representing an ecl expression graph

ecl/hqlcpp Classes for converting an expression graph to a work unit (and C++)

ecl/eclcc The executable which ties everything together.

Challenges

From declarative to imperative

As mentioned at the start of this document, one of the main challenges with eclcc is converting the declarative ECL code into imperative C++ code. The direction we are heading in is to allow the engines to support more lazy-evaluation so possibly in this instance to evaluate it the first time it is used (although that may potentially be much less efficient). This will allow the code generator to relax some of its current assumptions.

There are several example queries which are already producing pathological behaviour from eclcc, causing it to generate C++ functions which are many thousands of lines long.

The parser

Currently the grammar for the parser is too specialised. In particular the separate productions for expression, datasets, actions cause problems - e.g., it is impossible to properly allow sets of datasets to be treated in the same way as other sets.

The semantic checking (and probably semantic interpretation) is done too early. Really the parser should build up a syntax tree, and then disambiguate it and perform the semantic checks on the syntax tree.

The function calls should probably be expanded later than they are. I have tried in the past and hit problems, but I can't remember all the details. Some are related to the semantic checking.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/CodeReviews.html b/devdoc/CodeReviews.html new file mode 100644 index 00000000000..859d5706f12 --- /dev/null +++ b/devdoc/CodeReviews.html @@ -0,0 +1,24 @@ + + + + + + Code Review Guidelines | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Code Review Guidelines

The Code Submissions document is aimed at developers that are submitting PRs. This document describes some of the goals and expectations for code reviewers.

Review Goals

Code reviews have a few different goals:

  • Catch architectural or design problems.
    These should have been caught earlier, but better later than never...
  • Catch bugs early (incorrect behaviour, inefficiencies, security issues)
  • Ensure the code is readable and maintainable.
    This includes following the project coding standards (see Style Guide).
  • A opportunity for training/passing on information.
    For example providing information about how the current system works, functionality that is already available or suggestions of other approaches the developer may not have thought of.

It is NOT a goal to change the submission until it matches how the reviewer would have coded it.

General comments

Some general comments on code reviews:

  • Code reviewers should be explicit and clearly describe the problem.
    This should include what change is expected if not obvious. Don’t assume the contributor has same understanding/view as reviewer.
  • If a comment is not clear the contributor should ask for clarification.
    ...rather than wasting time trying to second-guess the reviewer.
  • Contributors should feel free to push back if they consider comments are too picky.
    The reviewer can either agree, or provide reasons why they consider it to be an issue.
  • The reviewer should not extend the scope of the original change.
    If the change could be extended, or only partially solves the issue, a new JIRA should be created for the extra work. If the change will introduce regressions, or fundamentally fails to solve the problem then this does not apply!
  • Clearly indicate if a review is incomplete.
    Sometimes a significant design problem means the rest of the code has not been reviewed in detail. Other times an initial review has picked up a set of issues, but the reviewer needs to go back and check other aspects in detail. If this is the case it should be explicitly noted.
  • Repeated issues.
    The reviewer is free to comment on every instance of a repeated issue, but a simple annotation should alert contributor to address appropriately eg: [Please address all instances of this issue]
  • Contributers should provide feedback to the reviewer.
    The contributor should respond to a comment if it isn't obvious where/how they have been addressed (but no need to acknowledge typo/indentation/etc)
  • Only the reviewer should mark issues as resolved using the Github resolve conversation button.
  • Code reviews should be a priority.
    Both reviewers and contributors should respond in a timely manner - don't leave it for days. It destroys the flow of thought and conversation.
  • Check all review comments have been addressed.
    If they have not been addressed you are guaranteed another review/submit cycle. In particular watch out for collapsed conversations. If there are large numbers of comments GitHub will collapse them, which can make comments easy to miss.
  • Sometimes PRs need to be restarted.
    If there are large number of comments > 100, it can be hard to track all the comments and GitHub can become unresponsive. It may be better to close the PR and open a new one.
  • Submit any changes as extra commits. This makes it clear to the reviewer what has changed, and avoids them having to re-review everything. Please do not squash them until the reviewers approve the PR. The few exceptions to this are if the PR is only a couple of lines, or the PR is completely rewritten in response to the review.
  • Reviewers use GitHub's features
    Making use of the "viewed" button can make it easier to track what has changed - or quickly remove trivial changes from view. Ignoring whitespace can often simplify comparisons - especially when code has been refactored or extra conditions or try/catch bocks have been introduced.

Strictness

All code reviews don't need to be equally strict. The "strictness" of the review should reflect the importance and location of the change. Some examples:

  • If it is closely associated with an existing file, then the indentation, style should match the surrounding code - a mixture of styles makes it much harder to read. If it is in a new, independent source file or project this is less of an issue.
  • If the code is in a core library then efficiency and edge cases will be more important.
  • If it is a core part of the system then security is key. If it is a developer only tool then edge cases are less significant.
  • Reviews of draft pull requests are likely to concentrate on the overall approach, rather than the details. They are likely to be more informal (e.g. not always using comments tags).

Checklist

What are some examples of checks to bear in mind when reviewing code?

General:

  • Is the commit title in the correct format, and understandable in a change log?
  • Is the target correct?
  • Is the size appropriate. Could it have been split up?
  • Does the jira contain details of the change, especially the reason?
  • Does it duplicate other functionality?
  • Does the style match the context and the style guide?
  • Is the design encapsulated at the right level? Too abstract or too concrete?

Content:

  • Silly mistakes - indent, typos, commented outcode, spurious changes.
  • Does it introduce any memory leaks? E.g. Correct use of linking? Are exceptions released?
  • Thread safety
    • critical sections or atomic variables if accessed by more than one thread
    • race conditions
    • deadlock
  • authorization. Should it be checked, does it fail by default?
  • Any potential for overflow or DOS? Are all user inputs validated and all lengths protected?
  • Are all secrets stored and passed securely?
  • Comments explaining why for any code that is complex or counter-intuitive.
  • Backward compatibility.
    Could this possibly cause problems if data produced with this change is used in earlier/later versions? Could there be problems if it was used in a mixed-version environment?

Comment tags

When reading comments in a review it can sometimes be hard to know why the reviewer made a comment, or what response is expected. If there is any doubt the contributor should ask. However to make it clearer we are aiming to always add a tag to the front of each review comment. The tag will give an indication of why the comment is being made, its severity and what kind of response is expected. Here is a provisional table of tags:

TagWhatWhyExpected response
design:An architectural or design issueThe reviewer considers the PR has a significant problem which will affect its functionality or future extensibilityreviewer/developer redesign expected before any further changes
scope:The scope of the PR does not match the JiraIf the scope of the fix is too large it can be hard to review, and take much longer to resolve all the issues before the PR is accepted.Discussion. Split the PR into multiple simpler PRs.
function:Incorrect/unexpected functionality implementedThe function doesn't match the description in the jira, or doesn't solve the original problemdeveloper expected to address issue (or discuss)
security:Something in the code introduces a security problemThe reviewer has spotted potential security issues e.g. injection attacksdeveloper expected to discuss the issue (and then address)
bug:A coding issue that will cause incorrect behaviourLikely to cause confusion, invalid results or crashes.developer expected to address issue
efficiency:The code works, but may have scaling or other efficiency issues.Inefficiency can cause problem in some key functions and areasdeveloper addressing the problem (or discuss)
discuss:Reviewer has thought of a potential problem, but not sure if it appliesReviewer has a concern it may be an issue, and wants to check the developer has thought about and addressed the issueDiscussion - either in the PR or offline.
style:Reviewer points out non-conforming code styleMakes the code hard to readDeveloper to fix
indent:A fairly obvious indentation issueMakes the code hard to readDeveloper to fix.
format:Any other unusual formattingMakes the code hard to readDeveloper to fix.
typo:Minor typing errorMakes something (code/message/comment) harder to readDeveloper to fix.
minor:A minor issue that could be improved.Education (the suggestion is better for a particular reason), or something simple to clean up at the same time as other changesDeveloper recommended to fix, but unlikely to stop a merge
picky:A very minor issue that could be improved, but is barely worth commenting onEducation, or something to clean up at the same time as other changesDeveloper discretion to fix, wouldn't stop a merge
future:An additional feature or functionality that fits in but should be done as a separate PR.Ensure that missing functionality is tracked, but PRs are not held up by additional requirements.Contributor to create Jira (unless trivial) and number noted in response.
question:Review has a question that they are not sure of the answer toReviewer would like clarification to help understand the code or design. The answer may lead to further comments.An answer to the question.
note:Reviewer wants to pass on some information to the contributor which they may not knowPassing on knowledge/backgroundcontributor should consider the note, but no change expected/required
personal:Reviewer has an observation based on personal experienceReviewer has comments that would improve the code, but not part of the style guide or required. E.g. patterns for guard conditionsReflect on the suggestion, but no change expected.
documentation:This change may have an impact on documentationMake sure changes can be usedContributor to create Jira describing the impact created, and number noted in response.

The comments should always be constructive. The reviewer should have a reason for each of them, and be able to articulate the reason in the comment or when asked. "I wouldn't have done it like that" is not a good enough on its own!

Similarly there is a difference in opinion within the team on some style issues - e.g. standard libraries or jlib, inline or out of line functions, nested or non-nested classes. Reviews should try and avoid commenting on these unless there is a clear reason why they are significant (functionality, efficiency, compile time) and if so spell it out. Code reviewers should discuss any style issues that they consider should be universally adopted that are not in the style guide.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/CodeSubmissions.html b/devdoc/CodeSubmissions.html new file mode 100644 index 00000000000..373fcd1cfbe --- /dev/null +++ b/devdoc/CodeSubmissions.html @@ -0,0 +1,24 @@ + + + + + + Code Submission Guidelines | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Code Submission Guidelines

We welcome submissions to the platform especially in the form of pull requests into the HPCC-Systems github repository. The following describes some of processes for merging PRs.

Pull requests

There are a few things that should be considered when creating a PR to increase the likelihood that they can be accepted quickly.

  • Write a good commit message
    The format should be HPCC-XXXXX (where XXXXX is the bug number) followed by a description of the issue. The text should make sense in a change log by itself - without reference to the jira or the contents of the PR. We should aim to increase the information that is included as part of the commit message - not rely on on the jira.
  • Ensure the reviewer has enough information to review the change.
    The code reviewer only has the JIRA and the PR to go on. The JIRA (or associated documentation) should contain enough details to review the PR - e.g. the purpose, main aim, why the change was made etc.. If the scope of the jira has changed then the jira should be updated to reflect that.
    If the submission requires changes to the documentation then the JIRA should contain all the details needed to document it, and the PR should either contain the documentation changes, or a documentation JIRA should be created.
  • Fill in the checklist
    The check boxes are there to remind you to consider different aspects of the PR. Not all of them apply to every submission, but if you tick a box and have not really thought about the item then prepare to be embarrassed!
  • Prefer small submissions
    It isn't always possible, but several smaller PRs are much easier to review than one large change. If your submission includes semi-automatic/mechanical changes (e.g. renaming large numbers of function calls, or adding an extra parameter) please keep it as a separate commit. This makes it much easier to review the PR - since the reviewer will be looking for different errors in the different types of changes.
  • Check for silly mistakes
    Review your own code in github, after creating the PR to check for silly mistakes. It doesn't take long, and often catches trivial issues. It may avoid the need for a cycle of code-review/fixes. It may be helpful to add some notes to specific changes e.g. "this change is mainly or solely refactoring method A into method B and C. ". Some common examples of trivial issues to look for include:
    • Inconsistent indentation, or using tabs rather than spaces to indent.
    • Lines of tracing left in.
    • Lines of code commented out that should be deleted.
    • TBD reminders of work that need implementing or removing.
    • Unrelated files that have been accidentally modified.
    • Accidental changes to submodule versions.
    • Typos in error messages, tracing or comments, or in the commit message.
    • Incomplete edits when copy and pasting code.
    • Check subtractions are the right way around, and potential for overflow.
    • New files with the wrong copyright date
  • Check the target branch (see below)
  • Request one or more reviews. For relatively simple changes a single reviewer is normally enough.

Reviewers

All pull requests should be reviewed by someone who is not the author before merging. Complex changes, changes that require input from multiple experts, or that have implications throughout the system should be reviewed by multiple reviewers. This should include someone who is responsible for merging changes for that part of the system. (Unless it is a simple change written by someone with review rights.)

Contributors should use the github reviewers section on the PR to request reviews. After a contributor has pushed a set of changes in response to a review, they should refresh the github review status, so the users are notified it is ready for re-review. When the review is complete, a person with responsibility for merging changes to that part of the system should be added as a reviewer (or refreshed), with a comment that it is ready to merge.

Reviewers should check for PRs that are ready for their review via github's webpage (filter "review-requested:<reviewer-id>") or via the github CLI (e.g. gh pr status). Contributors should similarly ensure they stay up to date with any comments on requests for change on their submissions.

Target branch

The Version support document contains details of the different versions that are supported, and which version should be targetted for different kinds of changes. Occasionally earlier branches will be chosen, (e.g. security fixes to even older versions) but they should always be carefully discussed (and documented).

Changes will always be upmerged into the next point release for all the more recent major and minor versions (and master).

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/DevDocs.html b/devdoc/DevDocs.html new file mode 100644 index 00000000000..b1d2b0f764c --- /dev/null +++ b/devdoc/DevDocs.html @@ -0,0 +1,31 @@ + + + + + + Working with developer documentation | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Working with developer documentation

Some basic guidlines to ensure your documentation works well with VitePress

Documentation location

Documents can be located anywhere in the repository folder structure. If it makes sense to have documentation "close" to specific components, then it can be located in the same folder as the component. For example, any developer documentation for specific plugins can be located in those folders. If this isn't appropriate then the documentation can be located in the devdoc or subfolders of devdoc.

WARNING

There is an exclusion list in the devdoc/.vitepress/config.js file that prevents certain folders from being included in the documentation. If you add a new document to a folder that is excluded, then it will not be included in the documentation. If you need to add a new document to an excluded folder, then you will need to update the exclusion list in the devdoc/.vitepress/config.js file.

Documentation format

Documentation is written in Markdown. This is a simple format that is easy to read and write. It is also easy to convert to other formats, such as HTML, PDF, and Word. Markdown is supported by many editors, including Visual Studio Code, and is supported by VitePress.

TIP

VitePress extends Markdown with some additional features, such as custom containers, it is recommended that you refer to the VitePress documentation for more details.

Rendering documentation locally with VitePress

To assist with the writing of documentation, VitePress can be used to render the documentation locally. This allows you to see how the documentation will look when it is published. To start the local development server you need to type the following commands in the root HPCC-Platform folder:

sh
npm install
+npm run docs-dev

This will start a local development server and display the URL that you can use to view the documentation. The default URL is http://localhost:5173/HPCC-Platform, but it may be different on your machine. The server will automatically reload when you make changes to the documentation.

WARNING

The first time you start the VitePress server it will take a while to complete. This is because it is locating all the markdown files in the repository and creating the html pages. Once it has completed this step once, it will be much faster to start the server again.

Adding a new document

To add a new document, you need to add a new markdown file to the repository. The file should be named appropriately and have the .md file extension. Once the file exists, you can view it by navigating to the appropriate URL. For example, if you add a new file called MyNewDocument.md to the devdoc folder, then you can view it by navigating to http://localhost:5173/HPCC-Platform/devdoc/MyNewDocument.html.

Adding a new document to the sidebar

To add a new document to the sidebar, you need to add an entry to the devdoc/.vitepress/config.js file. The entry should be added to the sidebar section. For example, to add a new document called MyNewDocument.md to the devdoc folder, you would add the following entry to the sidebar section:

js
sidebar: [
+    ...
+    {
+        text: 'My New Document',
+        link: '/devdoc/MyNewDocument'
+    }
+    ...

TIP

You can find more information on the config.js file in the VitePress documentation.

Editing the main landing page

The conent of the main landing page is located in index.md in the root folder. Its structure uses the VitePress "home" layout.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/Development.html b/devdoc/Development.html new file mode 100644 index 00000000000..6bbb2bc3093 --- /dev/null +++ b/devdoc/Development.html @@ -0,0 +1,26 @@ + + + + + + Development Guide | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Development Guide

HPCC Source

The most upto date details of building the system are found on the HPCC Wiki.

Getting the sources

The HPCC Platform sources are hosted on GitHub. You can download a snapshot of any branch using the download button there, or you can set up a git clone of the repository. If you are planning to contribute changes to the system, see the CONTRIBUTORS document for information about how to set up a GitHub fork of the project through which pull-requests can be made.

Building the system from sources

Requirements

The HPCC platform requires a number of third party tools and libraries in order to build. The HPCC Wiki contains the details of the dependencies that are required for different distributions.

For building any documentation, the following are also required:

bash
sudo apt-get install docbook
+sudo apt-get install xsltproc
+sudo apt-get install fop

NOTE: Installing the above via alternative methods (i.e. from source) may place installations outside of searched paths.

Building the system

The HPCC system is built using the cross-platform build tool cmake, which is available for Windows, virtually all flavors of Linux, FreeBSD, and other platforms. You should install cmake version 2.8.3 or later before building the sources.

On some distros you will need to build cmake from sources if the version of cmake in the standard repositories for that distro is not modern enough. It is good practice in cmake to separate the build directory where objects and executables are made from the source directory, and the HPCC cmake scripts will enforce this.

To build the sources, create a directory where the built files should be located, and from that directory, run:

bash
cmake <source directory>

Depending on your operating system and the compilers installed on it, this will create a makefile, Visual Studio .sln file, or other build script for building the system. If cmake was configured to create a makefile, then you can build simply by typing:

bash
make

If a Visual Studio solution file was created, you can load it simply by typing the name: hpccsystems-platform.sln

This will load the solution in Visual Studio where you can build in the usual way.

Packaging

To make an installation package on a supported linux system, use the command:

bash
make package

This will first do a make to ensure everything is up to date, then will create the appropriate package for your operating system, Currently supported package formats are rpm (for RedHat/Centos) and .deb (for Debian and Ubuntu). If the operating system is not one of the above, or is not recognized, make package will create a tarball.

The package installation does not start the service on the machine, so if you want to give it a go or test it (see below), make sure to start the service manually and wait until all services are up (mainly wait for EclWatch to come up on port 8010).

Testing the system

After compiling, installing the package and starting the services, you can test the HPCC platform on a single-node setup.

Unit Tests

Some components have their own unit-tests. Once you have compiled (no need to start the services), you can already run them. Supposing you build a Debug version, from the build directory you can run:

bash
./Debug/bin/roxie -selftest

and:

bash
./Debug/bin/eclagent -selftest

You can also run the Dali regression self-tests:

bash
./Debug/bin/daregress localhost

Regression Tests

MORE Completely out of date - needs rewriting.

Compiler Tests

The ECLCC compiler tests rely on two distinct runs: a known good one and your test build. For normal development, you can safely assume that the OSS/master branch in github is good. For overnight testing, golden directories need to be maintained according to the test infrastructure. There are Bash (Linux) and Batch (Windows) scripts to run the regressions:

The basic idea behind this tests is to compare the output files (logs and XML files) between runs. The log files should change slightly (the comparison should be good enough to filter most irrelevant differences), but the XML files should be identical if nothing has changed. You should only see differences in the XML where you have changed in the code, or new tests were added as part of your development.

On Linux, there are two steps:

Step 1: Check-out OSS/master, compile and run the regressions to populate the 'golden' directory:

bash
./regress.sh -t golden -e buildDir/Debug/bin/eclcc

This will run the regressions in parallel, using as many CPUs as you have, and using your just-compiled ECLCC, assuming you compiled for Debug version.

Step 2: Make your changes (or check-out your branch), compile and run again, this time output to a new directory and compare to the 'golden' repo.:

bash
./regress.sh -t my_branch -c golden -e buildDir/Debug/bin/eclcc

This will run the regressions in the same way, output to 'my_branch' dir and compare it to the golden version, highlighting the differences.

NOTE: If you changed the headers that the compiled binaries will use, you must re-install the package (or provide -i option to the script to the new headers).

Step 3: Step 2 only listed the differences, now you need to see what they are. For that, re-run the regressing script omitting the compiler, since the only thing we'll do is to compare verbosely.:

bash
./regress.sh -t my_branch -c golden

This will show you all differences, using the same ignore filters as before, between your two branches. Once you're happy with the differences, commit and issue a pull-request.

TODO: Describe compiler tests on Windows.

Debugging the system

On linux systems, the makefile generated by cmake will build a specific version (debug or release) of the system depending on the options selected when cmake is first run in that directory. The default is to build a release system. In order to build a debug system instead, use command:

bash
cmake -DCMAKE_BUILD_TYPE=Debug <source directory>

You can then run make or make package in the usual way to build the system.

On a Windows system, cmake always generates s solution file with both debug and release target platforms in it, so you can select which one to build within Visual Studio.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/GitAuthenticate.html b/devdoc/GitAuthenticate.html new file mode 100644 index 00000000000..085a9bc6f85 --- /dev/null +++ b/devdoc/GitAuthenticate.html @@ -0,0 +1,35 @@ + + + + + + HPCC git support | HPCC Platform + + + + + + + + + + + + + +
Skip to content

HPCC git support

Version 8.4 of the HPCC platform allows package files to define dependencies between git repositories and also allows you to compile directly from a git repository.

E.g.

ecl run hthor --main demo.main@ghalliday/gch-ecldemo-d#version1 --server=...

There are no futher requirements if the repositories are public, but private repositories have the additional complication of supplying authentication information. Git provides various methods for providing the credentials...

Credentials for local development

The following are the recommended approaches configuring the credentials on a local development system interacting with github:

  1. ssh key.

In this scenario, the ssh key associated with the local developer machine is registered with the github account. For more details see https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh

This is used when the github reference is of the form ssh://github.com. The sshkey can be protected with a passcode, and there are various options to avoid having to enter the passcode each time.

It is preferrable to use the https:// protocol instead of ssh:// for links in package-lock.json files. If ssh:// is used it requires any machine that processes the dependency to have access to a registered ssh key.

  1. github authentication

Download the GitHub command line tool (https://github.com/cli/cli). You can then use it to authenticate all git access with

gh auth login

Probably the simplest option if you are using github. More details are found at https://cli.github.com/manual/gh_auth_login

  1. Use a personal access token

These are similar to a password, but with additional restrictions on their lifetime and the resources that can be accessed.

Details on how to to create them are found : https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token

These can then be used with the various git credential caching options. E.g. see https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage

Configuring eclccserver

All of the options above are likely to involve some user interaction - passphrases for ssh keys, web interaction with github authentication, and initial entry for cached access tokens. This is problematic for eclccserver - which cannot support user interaction, and it is preferrable not to pass credentials around.

The solution is to use a personal access token securely stored as a secret. (This would generally be associated with a special service account.) This avoids the need to pass credentials and allows the keys to be rotated.

The following describes the support in the different versions:

Kubernetes

In Kubernetes you need to take the following steps:

a) add the gitUsername property to the eclccserver component in the value.yaml file:

eclccserver:
+- name: myeclccserver
+  gitUsername: ghalliday

b) add a secret to the values.yaml file, with a key that matches the username:

secrets:
+  git:
+    ghalliday: my-git-secret

note: this cannot currently use a vault - probably need to rethink that. (Possibly extract from secret and supply as an optional environment variable to be picked up by the bash script.)

c) add a secret to Kubernetes containing the personal access token:

apiVersion: v1
+kind: Secret
+metadata:
+  name: my-git-secret
+type: Opaque
+stringData:
+  password: ghp_eZLHeuoHxxxxxxxxxxxxxxxxxxxxol3986sS=
kubectl apply -f ~/dev/hpcc/helm/secrets/my-git-secret

When a query is submitted to eclccserver, any git repositories are accessed using the user name and password.

Bare-metal

Bare-metal require some similar configuration steps:

a) Define the environment variable HPCC_GIT_USERNAME

export HPCC_GIT_USERNAME=ghalliday

b) Store the access token in /opt/HPCCSystems/secrets/git/$HPCC_GIT_USERNAME/password

E.g.

$cat /opt/HPCCSystems/secrets/git/ghalliday/password
+ghp_eZLHeuoHxxxxxxxxxxxxxxxxxxxxol3986sS=

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/LDAPSecurityManager.html b/devdoc/LDAPSecurityManager.html new file mode 100644 index 00000000000..236ff959c07 --- /dev/null +++ b/devdoc/LDAPSecurityManager.html @@ -0,0 +1,24 @@ + + + + + + LDAP Security Manager Init | HPCC Platform + + + + + + + + + + + + + +
Skip to content

LDAP Security Manager Init

This document covers the main steps taken by the LDAP Security Manager during initialization. It is important to note that the LDAP Security Manager uses the LDAP protocol to access an Active Directory, AD. The AD is the store for users, groups, permissions, resources, and more. The term LDAP is generally overused to refer to both.

LDAP Instances

Each service and/or component using the LDAP security manager gets its own instance of the security manager. This includes a unique connection pool (see below). All operations described apply to each LDAP instance.

Initialization Steps

The following sections cover the main steps taken during initialization

Load Configuration

The following items are loaded from the configuration:

AD Hosts

The LDAP Security Manager supports using multiple ADs. The FQDN or IP address of each AD host is read from configuration data and stored internally. The source is a comma separated list stored in the ldapAddress config value. Each entry is added to a pool of ADs.

Note that all ADs are expected to use the same credentials and have the same configuration

AD Credentials

AD credentials consist of a username and password. The LDAP security manager users these to perform all operations on behalf of users and components in the cluster. There are three potential sources for credentials.

  1. A Kubernetes secret. If the ldapAdminSecretKey config value is set, but ldapAdminVaultId is not (see 2) then the AD credentials are retrieved as Kubernetes secrets.
  2. If both ldapAdminSecretKey and ldapAdminVaultId config values are present, the AD credentials are retrieved from the vault.
  3. Hardcoded values from the systemCommonName and systemPassword config values stored in the environment.xml file.

As stated above, when multiple ADs are in use, the configuration of each must be the same. This includes credentials.

Retrieve Server Information from the AD

During initialization, the security manager begins incrementing through the set of defined ADs until it is able to connect and retrieve information from an AD. Once retrieved, the information is used for all ADs (see statement above about all ADs being the same). The accessed AD is marked as the current AD and no other ADs are accessed during initialization.

The retrieved information is used to verify the AD type so the security manager adjusts for variations between types. Additionally, defined DNs may be adjusted to match AD type requirements.

Connections

The manager handles connections to an AD in order to perform required operations. It is possible that values such as permissions and resources may be cached to improve performance.

Connection Pool

The LDAP security manager maintains a pool of LDAP connections. The pool is limited in size to maxConnections from the configuration. The connection pool starts empty. As connections are created, each is added to the pool until the max allowed is reached.

The following process is used when an LDAP connection is needed.

First, the connection pool is searched for a free connection. If found and valid, the connection is returned. A connection is considered free if no one is using it and valid if the AD can be accessed. If no valid free connections are found, a new uninitialized connection is created.

For a new connection, an attempt is made to connect to each AD starting with the current. See Handling AD Hosts below for how ADs are cycled when a connection fails. For each AD, as it cycles through, connection attempts are retried with a short delay between each. If unable to connect, the AD host is marked rejected and the next is attempted. Once a new connection has been established, if the max number of connections has not been reached yet, the connection is added to the pool.

It is important to note that if the pool has reached its max size, new connections will continue to be made, but are not saved in the pool. This allows the pool to maintain a steady working state, but allow for higher demand. Connections not saved to the pool are deleted once no longer in use.

Handling AD Hosts

The manager keeps a list of AD hosts and the index of the current host. The current host is used for all AD operations until there is a failure. At that time the manager marks the host as "rejected" and moves to the next host using a round-robin scheme.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/MemoryManager.html b/devdoc/MemoryManager.html new file mode 100644 index 00000000000..9c20aaedeac --- /dev/null +++ b/devdoc/MemoryManager.html @@ -0,0 +1,24 @@ + + + + + + The Roxie Memory Manager | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Introduction

This memory manager started life as the memory manager which was only used for the Roxie engine. It had several original design goals:

  • Support link counted rows. (When the last reference is released the row is freed.)
  • Be as fast as possible on allocate and deallocate of small rows.
  • Allow rows serialized from slaves to be used directly without being cloned first.
  • Allow the memory used by a single query, or by all queries combined, to be limited, with graceful recovery.
  • Isolate roxie queries from one another, so that one query can't bring down all the rest by allocating too much memory.
  • Guarantee all the memory used by a query is freed when the query finishes, reducing the possibility of memory leaks.
  • Predictable behaviour with no pathogenic cases.

(Note that efficient usage of memory does not appear on that list - the expectation when the memory manager was first designed was that Roxie queries would use minimal amounts of memory and speed was more important. Some subsequent changes e.g., Packed heaps, and configurable bucket sizes help mitigate that.)

Main Structure

The basic design is to reserve (but not commit) a single large block of memory in the virtual address space. This memory is subdivided into "pages". (These are not the same as the os virtual memory pages. The memory manager pages are currently defined as 1Mb in size.)

The page bitmap

The system uses a bitmap to indicate whether each page from the global memory has been allocated. All active IRowManager instances allocate pages from the same global memory space. To help reduce fragmentation allocations for single pages are fulfilled from one end of the address space, while allocations for multiple pages are fulfilled from the other.

IRowManager

This provides the primary interface for allocating memory. The size of a requested allocation is rounded up to the next "bucket" size, and the allocation is then satisfied by the heap associated with that bucket size. Different engines can specify different bucket sizes - an optional list is provided to setTotalMemoryLimit. Roxie tends to use fewer buckets to help reduce the number of active heaps. Thor uses larger numbers since it is more important to minimize the memory wasted.

Roxie uses a separate instance of IRowManager for each query. This provides the mechanism for limiting how much memory a query uses. Thor uses a single instance of an IRowManager for each slave/master.

Heaps

Memory is allocated from a set of "heaps - where each heap allocates blocks of memory of a single size. The heap exclusively owns a set of heaplet (each 1 page in size), which are held in a doubly linked list, and sub allocates memory from those heaplets.

Information about each heaplet is stored in the base of the page (using a class with virtual functions) and the address of an allocated row is masked to determine which heap object it belongs to, and how it should be linked/released etc. Any pointer not in the allocated virtual address (e.g., constant data) can be linked/released with no effect.

Each heaplet contains a high water mark of the address within the page that has already been allocated (freeBase), and a lockless singly-linked list of rows which have been released (r_block). Releasing a row is non-blocking and does not involve any spin locks or critical sections. However, this means that empty pages need to be returned to the global memory pool at another time. (This is done in releaseEmptyPages()).

When the last row in a page is released a flag (possibleEmptyPages) is set in its associated heap. * This is checked before trying to free pages from a particular heap, avoiding waiting on a lock and traversing a candidate list.

Any page which might contain some spare memory is added to a lockless spare memory linked list. * Items are popped from this list when a heap fails to allocate memory from the current heaplet. Each item is checked in turn if it has space before allocating a new heaplet. * The list is also walked when checking to see which pages can be returned to the global memory. The doubly linked heaplet list allows efficient freeing.

Each allocation has a link count and an allocator id associated with it. The allocator id represents the type of the row, and is used to determine what destructor needs to be called when the row is destroyed. (The count for a row also contains a flag in the top bit to indicate if it is fully constructed, and therefore valid for the destructor to be called.)

Huge Heap

A specialized heap is used to manage all allocations that require more than one page of memory. These allocations are not held on a free list when they are released, but each is returned directly to the global memory pool. Allocations in the huge heap can be expanded and shrunk using the resizeRow() functions - see below.

Specialised Heaps:

Packed

By default a fixed size heaps rounds the requested allocation size up to the next bucket size. A packed heap changes this behaviour and it is rounded up to the next 4 byte boundary instead. This reduces the amount of memory wasted for each row, but potentially increases the number of distinct heaps.

Unique

By default all fixed size heaps of the same size are shared. This reduces the memory consumption, but if the heap is used by multiple threads it can cause significant contention. If a unique heap is specified then it will not be shared with any other requests. Unique heaps store information about the type of each row in the heaplet header, rather than per row - which reduces the allocation overhead for each row. (Note to self: Is there ever any advantage having a heap that is unique but not packed??)

Blocked

Blocked is an option on createFixedRowHeap() to allocate multiple rows from the heaplet, and then return the additional rows on subsequent calls. It is likely to reduce the average number of atomic operations required for each row being allocated, but the heap that is returned can only be used from a single thread because it is not thread safe.

Scanning

By default the heaplets use a lock free singly linked list to keep track of rows that have been freed. This requires an atomic operation for each allocation and for each free. The scanning allocator uses an alternative approach. When a row is freed the row header is marked, and a row is allocated by scanning through the heaplet for rows that have been marked as free. Scanning uses atomic store and get, rather than more expensive synchronized atomic operations, so is generally faster than the linked list - provided a free row is found fairly quickly.

The scanning heaps have better best-case performance, but worse worse-case performance (if large numbers of rows need to be scanned before a free row is found). The best-case tends to be true if only one thread/activity is accessing a particular heap, and the worse-case if multiple activities are accessing a heap, particularly if the rows are being buffered. It is the default for thor which tends to have few active allocators, but not for roxie, which tends to have large numbers of allocators.

Delay Release

This is another varation on the scanning allocator, which further reduces the number of atomic operations. Usually when a row is allocated the link count on the heaplet is increased, and when it is freed the link count is decremented. This option delays decrementing the link count when the row is released, by marking the row with a different free flag. If it is subsequently reallocated there is no need to increment the link count. The downside is that it is more expensive to check whether a heaplet is completely empty (since you can no longer rely on the heaplet linkcount alone).

Dynamic Spilling

Thor has additional requirements to roxie. In roxie, if a query exceeds its memory requirements then it is terminated. Thor needs to be able to spill rows and other memory to disk and continue. This is achieved by allowing any process that stores buffered rows to register a callback with the memory manager. When more memory is required these callbacks are called to free up memory, and allow the job to continue.

Each callback can specify a priority - lower priority callbacks are called first since they are assumed to have a lower cost associated with spilling. When more memory is required the callbacks are called in priority order until one of them succeeds. The can also be passed a flag to indicate it is critical to force them to free up as much memory as possible.

Complications

There are several different complications involved with the memory spilling:

  • There will be many different threads allocating rows.
  • Callbacks could be triggered at any time.
  • There is a large scope for deadlock between the callbacks and allocations.
  • It may be better to not resize a large array if rows had to be evicted to resize it.
  • Filtered record streams can cause significant wasted space in the memory blocks.
  • Resizing a multi-page allocation is non trivial.

Callback Rules

Some rules to follow when implementing callbacks:

  • A callback cannot allocate any memory from the memory manager. If it does it is likely to deadlock.

  • You cannot allocate memory while holding a lock if that lock is also required by a callback.

    Again this will cause deadlock. If it proves impossible you can use a try-lock primitive in the callback, but it means you won't be able to spill those rows.

  • If the heaps are fragmented it may be more efficient to repack the heaps than spill to disk.

  • If you're resizing a potentially big block of memory use the resize function with the callback.

Resizing Large memory blocks

Some of the memory allocations cover more than one "page" - e.g., arrays used to store blocks of rows. (These are called huge pages internally, not to be confused with operating system support for huge pages...) When one of these memory blocks needs to be expanded you need to be careful:

  • Allocating a new page, copying, updating the pointer (within a cs) and then freeing is safe. Unfortunately it may involve copying a large chunk of memory. It may also fail if there isn't memory for the new and old block, even if the existing block could have been expanded into an adjacent block.
  • You can't lock, call a resize routine and update the pointer because the resize routine may need to allocate a new memory block- that may trigger a callback, which could in turn deadlock trying to gain the lock. (The callback may be from another thread...)
  • Therefore the memory manager contains a call which allows you to resize a block, but with a callback which is used to atomically update the pointer so it always remains thread safe.

Compacting heaps

Occasionally you have processes which read a large number of rows and then filter them so only a few are still held in memory. Rows tend to be allocated in sequence through the heap pages, which can mean those few remaining rows are scattered over many pages. If they could all be moved to a single page it would free up a significant amount of memory.

The memory manager contains a function to pack a set of rows into a smaller number of pages: IRowManager->compactRows().

This works by iterating through each of the rows in a list. If the row belongs to a heap that could be compacted, and isn't part of a full heaplet, then the row is moved. Since subsequent rows tend to be allocated from the same heaplet this has the effect of compacting the rows.

Shared Memory

Much of the time Thor doesn't uses full memory available to it. If you are running multiple Thor processes on the same machine you may want to configure the system so that each Thor has a private block of memory, but there is also a shared block of memory which can be used by whichever process needs it.

The ILargeMemCallback provides a mechanism to dynamically allocate more memory to a process as it requires it. This could potentially be done in stages rather than all or nothing.

(Currently unused as far as I know... the main problem is that borrowing memory needs to be coordinated.)

Huge pages

When OS processes use a large amount of memory, mapping virtual addresses to physical addresses can begin to take a significant proportion of the execution time. This is especially true once the TLB is not large enough to store all the mappings. Huge pages can significantly help with this problem by reducing the number of TLB entries needed to cover the virtual address space. The memory manager supports huge pages in two different ways:

Huge pages can be preallocated (e.g., with hugeadm) for exclusive use as huge pages. If huge pages are enabled for a particular engine, and sufficient huge pages are available to supply the memory for the memory manager, then they will be used.

Linux kernels from 2.6.38 onward have support for transparent huge pages. These do not need to be preallocated, instead the operating system tries to use them behind the scenes. HPCC version 5.2 and following takes advantage of this feature to significantly speed memory access up when large amounts of memory are used by each process.

Preallocated huge pages tend to be more efficient, but they have the disadvantage that the operating system currently does not reuse unused huge pages for other purposes e.g., disk cache.

There is also a memory manager option to not return the memory to the operating system when it is no longer required. This has the advantage of not clearing the memory whenever it is required again, but the same disadvantage as preallocated huge pages that the unused memory cannot be used for disk cache. We recommend this option is selected when preallocated huge pages are in use - until the kernel allows them to be reused.

Global memory and channels

Changes in 6.x allow Thor to run multiple channels within the same process. This allows data that is constant for all channels to be shared between all slave channels - a prime example is the rhs of a lookup join. For the queries to run efficiently the memory manager needs to ensure that each slave channel has the same amount of memory - especially when memory is being used that is shared between them.

createGlobalRowManager() allows a single global row manager to be created which also provides slave row managers for the different channels via the querySlaveRowManager(unsigned slave) method.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/Metrics.html b/devdoc/Metrics.html new file mode 100644 index 00000000000..2b178d8b0b4 --- /dev/null +++ b/devdoc/Metrics.html @@ -0,0 +1,40 @@ + + + + + + Metrics Framework Design | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Metrics Framework Design

Introduction

This document describes the design of a metrics framework that allows HPCC Systems components to implement a metric collection strategy. Metrics provide the following functionality:

  • Alerts and monitoring

    An important DevOps function is to monitor the cluster and providing alerts when problems are detected. Aggregated metric values from multiple sources provide the necessary data to build a complete picture of cluster health that drives monitoring and alerts.

  • Scaling

    As described above, aggregated metric data is also used to dynamically respond to changing cluster demands and load. Metrics provide the monitoring capability to react and take action

  • Fault diagnosis and resource monitoring

    Metrics provide historical data useful in diagnosing problems by profiling how demand and usage patterns may change prior to a fault. Predictive analysis can also be applied.

  • Analysis of jobs/workunits and profiling

    With proper instrumentation, a robust dynamic metric strategy can track workunit processing. Internal problems with queries should be diagnosed from deep drill down logging.

The document consists of several sections in order to provide requirements as well as the design of framework components.

Definitions

Some definitions are useful.

Metric

: A measurement defined by a component that represents an internal state that is useful in a system reliability engineering function. In the context of the framework, a metric is an object representing the above.

Metric Value

: The current value of a metric.

Metric Updating

: The component task of updating metric state.

Collection

: A framework process of selecting relevant metrics based on configuration and then retrieving their values.

Reporting

: A framework process of converting values obtained during a collection into a format suitable for ingestion by a collection system.

Trigger

: What causes the collection of metric values.

Collection System

: The store for metric values generated during the reporting framework process.

Use Scenarios

This section describes how components expect to use the framework. It is not a complete list of all requirements but rather a sample.

Roxie

Roxie desires to keep a count of many different internal values. Some examples are

  • Disk type operations such as seeks and reads

  • Execution totals

    Need to track items such as total numbers of items such as success and failures as well as breaking some counts into individual reasons. For example, failures may need be categorized such as as

    • Busy
    • Timeout
    • Bad input

    Or even by priority (high, low, sla, etc.)

  • Current operational levels such as the length of internal queues

  • The latency of operations such as queue results, agent responses, and gateway responses

Roxie also has the need to track internal memory usage beyond the pod/system level capabilities. Tracking the state of its large fixed memory pool is necessary.

The Roxie buddy system also must track how often and who is completing requests. The "I Beat You To It" set of metrics must be collected and exposed in order to detect pending node failure. While specific action on these counts is not known up front, it appears clear that these values are useful and should be collected.

There does not appear to be a need for creating and destroying metrics dynamically. The set of metrics is most likely to be created at startup and remain active through the life of the Roxie. If, however, stats collection seeps into the metrics framework, dynamic creation and destruction of stats metrics is a likely requirement.

ESP

There are some interesting decisions with respect to ESP and collection of metrics. Different applications within ESP present different use cases for collection. Ownership of a given task drives some of these use cases. Take workunit queues. If ownership of the task, with respect to metrics, is WsWorkunits, then use cases are centric to that component. However, if agents listening on the queue are to report metrics, then a different set of use cases emerge. It is clear that additional work is needed to generate clear ownership of metrics gathered by ESP and/or the tasks it performs.

ESP needs to report the activeTransactions value from the TxSummary class(es). This gives an indication of how busy the ESP is in terms of client requests.

Direct measurement of response time in requests may not be useful since the type of request causes different execution paths within ESP that are expected to take widely varying amounts of time. Creation of metrics for each method is not recommended. However, two possible solutions are to a) create a metric for request types, or b) use a histogram to measure response time ranges. Another option mentioned redefines the meaning of a bucket in a histogram. Instead of a numeric distribution, each bucket represents a unique subtask within an overall "metric" representing a measured operation. This should be explored whether for operational or developmental purposes.

For tracking specific queries and their health, the feeling is that logging can accomplish this better than metrics since the list of queries to monitor will vary between clusters. Additionally, operational metrics solving the cases mentioned above will give a view into the overall health of ESP which will affect the execution of queries. Depending on actions taken by these metrics, scaling may solve overload conditions to keep cluster responsiveness acceptable.

For Roxie a workunit operates as a service. Measuring service performance using a histogram to capture response times as a distribution may be appropriate. Extracting the 95th percentile of response time may be useful as well.

There are currently no use cases requiring consistency between values of different metrics.

At this time the only concrete metric identified is the number of requests received. As the framework design progresses and ESP is instrumented, the list will grow.

Dali Use Cases

From information gathered, Dali plans to keep counts and rates for many of the items it manages.

Framework Design

This section covers the design and architecture of the framework. It discusses the main areas of the design, the interactions between each area, and an overall process model of how the framework operates.

The framework consists of three major areas: metrics, sinks, and the glue logic. These areas work together with the platform and the component to provide a reusable metrics collection function.

Metrics represent the quantifiable component state measurements used to track and assess the status of the component. Metrics are typically scalar values that are easily aggregated by a collection system. Aggregated values provide the necessary input to take component and cluster actions such as scaling up and down. The component is responsible for creating metrics and instrumenting the code. The framework provides the support for collecting and reporting the values. Metrics provide the following:

  • Simple methods for the component to update the metric
  • Simple methods for the framework to retrieve metric value(s)
  • Handling of all synchronization between updating and retrieving metric values

In addition, the framework provides the support for retrieving values so that the component does not participate in metric reporting. The component simply creates the metrics it needs, then instruments the component to update the metric whenever its state changes. For example, the component may create a metric that counts the total number of requests received. Then, wherever the component receives a request, a corresponding update to the count is added. Nowhere in the component is any code added to retrieve the count as that is handled by the framework.

Sinks provide a pluggable interface to hide the specifics of collection systems so that the metrics framework is independent of those dependencies. Sinks:

  • Operate independently of other sinks in the system
  • Convert metric native values into collection system specific measurements and reports
  • Drive the collection and reporting processes

The third area of the framework is the glue logic, referred to as the MetricsManager. It manages the metrics system for the component. It provides the following:

  • Handles framework initialization
  • Loads sinks as required
  • Manages the list of metrics for the component
  • Handles collection and reporting with a set of convenience methods used by sinks

The framework is designed to be instantiated into a component as part of its process and address space. All objects instantiated as part of the framework are owned by the component and are not shareable with any other component whether local or remote. Any coordination or consistency requirements that may arise in the implementation of a sink shall be the sole responsibility of the sink.

Framework Implementation

The framework is implemented within jlib. The following sections describe each area of the framework.

Metrics

Components use metrics to measure their internal state. Metrics can represent everything from the number of requests received to the average length some value remains cached. Components are responsible for creating and updating metrics for each measured state. The framework shall provide a set of metrics designed to cover the majority of component measurement requirements. All metrics share a common interface to allow the framework to manage them in a common way.

To meet the requirement to manage metrics independent of the underlying metric state, all metrics implement a common interface. All metrics then add their specific methods to update and retrieve internal state. Generally the component uses the update method(s) to update state and the framework uses retrieval methods to get current state when reporting. The metric insures synchronized access.

For components that already have an implementation that tracks a metric, the framework provides a way to instantiate a custom metric. The custom metric allows the component to leverage the existing implementation and give the framework access to the metric value for collection and reporting. Note that custom metrics only support simple scalar metrics such as a counter or a gauge.

Sinks

The framework defines a sink interface to support the different requirements of collection systems. Examples of collection systems are Prometheus, Datadog, and Elasticsearch. Each has different requirements for how and when measurements are ingested. The following are examples of different collection system requirements:

  • Polled vs Periodic
  • Single measurement vs multiple reports
  • Report format (JSON, text, etc.)
  • Push vs Pull

Sinks are responsible for two main functions: initiating a collection and reporting measurements to the collection system. The Metrics Reporter provides the support to complete these functions.

The sink encapsulates all of the collection system requirements providing a pluggable architecture that isolates components from these differences. The framework supports multiple sinks concurrently, each operating independently.

Instrumented components are not aware of the sink or sinks in use. Sinks can be changed without requiring changes to a component. Therefore, components are independent of the collection system(s) in use.

Metrics Reporter

The metrics reporter class provides all of the common functions to bind together the component, the metrics it creates, and the sinks to which measurements are reported. It is responsible for the following:

  • Initialization of the framework
  • Managing the metrics created by the component
  • Handling collection and reporting as directed by configured sinks

Metrics Implementations

The sections that follow discuss metric implementations.

Counter Metric

A counter metric is a monotonically increasing value that "counts" the total occurrences of some event. Examples include the number of requests received, or the number of cache misses. Once created, the component instruments the code with updates to the count whenever appropriate.

Gauge Metric

A gauge metric is a continuously updated value representing the current state of an interesting value in the component. For example, the amount of memory used in an internal buffer, or the number of requests waiting on a queue. A gauge metric may increase or decrease in value as needed. Reading the value of a gauge is a stateless operation in that there are no dependencies on the previous reading. The value returned shall always be the current state.

Once created, the component shall update the gauge anytime the state of what is measured is updated. The metric shall provide methods to increase and decrease the value. The sink reads the value during collection and reporting.

Custom Metric

A custom metric is a class that allows a component to leverage existing metrics. The component creates an instance of a custom metric (a templated class) and passes a reference to the underlying metric value. When collection is performed, the custom metric simply reads the value of the metric using the reference provided during construction. The component maintains full responsibility for updating the metric value as the custom metric class provides no update methods. The component is also responsible for ensuring atomic access to the value if necessary.

Histogram Metric

Records counts of measurements according to defined bucket limits. When created, the caller defines as set of bucket limits. During event recording, the component records measurements. The metric separates each recorded measurement into its bucket by testing the measurement value against each bucket limit using a less than or equal test. Each bucket contains a count of measurements meeting that criteria. Additionally, the metric maintains a default bucket for measurements outside of the maximum bucket limit. This is sometimes known as the "inf" bucket.

Some storage systems, such as Prometheus, require each bucket to accumulate its measurements with the previous bucket(s). It is the responsibility of the sink to accumulate values as needed.

Scaled Histogram Metric

A histogram metric that allows setting the bucket limit units in one domain, but take measurements in another domain. For example, the bucket limits may represent millisecond durations, yet it is more effecient to use execution cycles to take the measurements. A scaled histogram converts from the the measurement domain (cycles) to the limit units domain using a scale factor provided at initialization. All conversions are encapsulated in the scaled histogram class such that no external scaling is required by any consumer such as a sink.

Configuration

This section discusses configuration. Since Helm charts are capable of combining configuration data at a global level into a component's specific configuration, The combined configuration takes the form as shown below. Note that as the design progresses it is expected that there will be additions.

yaml
    component:
+      metrics:
+        sinks:
+        - type: <sink_type>
+          name: <sink name>
+          settings:
+            sink_setting1: sink_setting_value1
+            sink_setting2: sink_setting_value2

Where (based on being a child of the current component):

metrics

: Metrics configuration for the component

metrics.sinks

: List of sinks defined for the component (may have been combined with global config)

metrics.sinks[].type

: The type for the sink. The type is substituted into the following pattern to determine the lib to load: libhpccmetrics<type><shared_object_extension>

metrics.sinks[].name

: A name for the sink.

metrics.sinks[].settings

: A set of key/value pairs passed to the sink when initialized. It should contain information necessary for the operation of the sink. Nested YML is supported. Example settings are the prometheus server name, or the collection period for a periodic sink.

Metric Naming

Metric names shall follow a convention as outlined in this section. Because different collection systems have different requirements for how metric value reports are generated, naming is split into two parts.

First, each metric is given a base name that describes what the underlying value is. Second, meta data is assigned to each metric to further qualify the value. For example, a set of metrics may count the number of requests a component has received. Each metric would have the same base name, but meta data would separate types of request (GET vs POST), or disposition such as pass or fail.

Base Name

The following convention defines how metric names are formed:

  • Names consist of parts separated by a period (.)

  • Each part shall use snake case (allows for compound names in each part)

  • Each name shall begin with a prefix representing the scop of the metric

  • Names for metric types shall be named as follows (followed by examples):

    Gauges: <scope>.<plural-noun>.<state> esp.requests.waiting, esp.status_requests.waiting

    Counters: <scope>.<plural-noun>.<past-tense-verb> thor.requests.failed, esp.gateway_requests.queued

    Time: <scope>.<singular-noun>.<state or active-verb>.time dali.request.blocked.time, dali.request.process.time

Meta Data

Meta data further qualifies a metric value. This allows metrics to have the same name, but different scopes or categories. Generally, meta data is only used to furher qualify metrics that would have the same base name, but need further distinction. An example best describes a use case for meta data. Consider a component that accepts HTTP requests, but needs to track GET and POST requests separately. Instead of defining metrics with names post_requests.received and get_requests.received, the component creates two metrics with the base name requests.received and attaches meta data describing the request type of POST to one and GET to the other.

Use of meta data allows aggregating both types of requests into a single combined count of received requests while allowing a breakdown by type.

Meta data is represented as a key/value pair and is attached to the metric by the component during metric creation. The sink is responsible for converting meta data into useful information for the collection system during reporting.

The Component Instrumentation section covers how meta data is added to a metric.

Component Instrumentation

In order to instrument a component for metrics using the framework, a component must include the metrics header from jlib (jmetrics.hpp) and add jlib as a dependent lib (if not already doing so).

The general steps for instrumentation are

  1. Create a metrics reporter object
  2. Create metric objects for each internal state to measure and add each to the reporter
  3. Add updates to each metric throughout the component wherever metric state changes

The metrics reporter is a singleton created using the platform defined singleton pattern template. The component must obtain a reference to the reporter. Use the following example:

cpp
using namespace hpccMetrics;
+MetricsManager &metricsManager = queryMetricsManager();

Metrics are wrapped by a standard C++ shared pointer. The component is responsible for maintaining a reference to each shared pointer during the lifetime of the metric. The framework keeps a weak pointer to each metric and thus does not maintain a reference. The following is an example of creating a counter metric and adding it to the reporter. The using namespace eliminates the need to prefix all metrics types with hpccMetrics. Its use is assumed for all code examples that follow.

cpp
std::shared_ptr<CounterMetric> pCounter = std::make_shared<CounterMetric>("metricName", "description");
+metricsManager.add(pCounter);

Note the metric type for both the shared pointer variable and in the make_shared template that creates the metric and returns a shared pointer. Simply substitute other metric types and handle any differences in the constructor arguments as needed.

Once created, add updates to the metric state throughout the component code where required. Using the above example, the following line of code increments the counter metric by 1.

cpp
pCounter->inc(1);

Note that only a single line of code is required to update the metric.

That's it! There are no component requirements related to collection or reporting of metric values. That is handled by the framework and loaded sinks.

For convenience, there are function templates that handle creating the reporter, creating a metric, and adding the metric to the reporter. For example, the above three lines of code that created the reporter, a metric, and added it, can be replaced by the following:

auto pCount = createMetricAndAddToManager<CounterMetric>("metricName", "description");
+

For convenience a similar function template exists for creating custom metrics. For a custom metric the framework must know the metric type and have a reference to the underlying state variable. The following template function handles creating a custom metric and adding it to the reporter (which is created if needed as well):

auto pCustomMetric = createCustomMetricAndAddToManager("customName", "description", metricType, value);
+

Where:

  • metricType

    A defined metric type as defined by the MetricType enum.

  • value

    A reference to the underlying event state which must be a scalar value convertable to a 64bit unsigned integer (__uint64)

Adding Metric Meta Data

A component, depending on requirements, may attach meta data to further qualify created metrics. Meta data takes the form of key value pairs. The base metric class MetricBase constructor defines a parameter for a vector of meta data. Metric subclasses also define meta data as a constructor parameter, however an empty vector is the default. The IMetric interface defines a method for retrieving the meta data.

Meta data is order dependent.

Below are two examples of constructing a metric with meta data. One creates the vector and passes it as a parameter, the other constructs the vector in place.

cpp
MetricMetaData metaData1{{"key1", "value1"}};
+std::shared_ptr<CounterMetric> pCounter1 =
+    std::make_shared<CounterMetric>("requests.completed", "description", SMeasureCount, metaData1);
+
+std::shared_ptr<CounterMetric> pCounter2 =
+    std::make_shared<CounterMetric>("requests.completed", "description", SMeasureCount, MetricMetaData{{"key1", "value2"}});

Metric Units

Metric units are treated separately from the base name and meta data. The reason is to allow the sink to translate based on collection system requirements. The base framework provides a convenience method for converting units into a string. However, the sink is free to do any conversions, both actual units and the string representation, as needed.

Metric units are defined using a subset of the StaticsMeasure enumeration values defined in jstatscodes.h. The current values are used:

  • SMeasureTimeNs - A time measurement in nanoseconds
  • SMeasureCount - A count of events
  • SMeasureSize - Size in bytes

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/NewFileProcessing.html b/devdoc/NewFileProcessing.html new file mode 100644 index 00000000000..565ffff3ad8 --- /dev/null +++ b/devdoc/NewFileProcessing.html @@ -0,0 +1,37 @@ + + + + + + Storage planes | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Documentation about the new file work.

YAML files. The following are the YAML definitions which are used to serialize file information from dali/external store to the engines and if necessary to the worker nodes.

Storage planes

This is already covered in the deployed helm charts. It has been extended and rationalized slightly.

storage:

: hostGroups: - name: <required> hosts: [ .... ] - name: <required> hostGroup: <name> count: <unsigned:#hosts> # how many hosts within the host group are used ?(default is number of hosts) offset: <unsigned:0> # index of first host included in the derived group delta: <unsigned:0> # first host within the range[offset..offset+count-1] in the derived group

planes:
+
+:   name: \<required\> prefix: \<path\> \# Root directory for
+    accessing the plane (if pvc defined), or url to access plane.
+    numDevices: 1 \# number of devices that are part of the plane
+    hostGroup: \<name\> \# Name of the host group for bare metal
+    hosts: \[ host-names \] \# A list of host names for bare metal
+    secret: \<secret-id\> \# what secret is required to access the
+    files. options: \# not sure if it is needed
+

Changes: * The replication information has been removed from the storage plane. It will now be specified on the thor instance indicating where (if anywhere) files are replicated. * The hash character (#) in a prefix or a secret name will be substituted with the device number. This replaces the old includeDeviceInPath property. This allows more flexible device substition for both local mounts and storage accounts. The number of hashes provides the default padding for the device number. (Existing Helm charts will need to be updated to follow these new rules.) * Neither thor or roxie replication is special cased. They are represented as multiple locations that the file lives (see examples below). Existing baremetal environments would be mapped to this new representation with implicit replication planes. (It is worth checking the mapping to roxie is fine.)

Files

file: - name: <logical-file-name> format: <type> # e.g. flat, csv, xml, key, parquet meta: <binary> # (opt) format of the file, (serialized hpcc field information). metaCrc: <unsigned> # hash of the meta numParts # How many file parts. singlePartNoSuffix: <boolean> # Does a single part file include .part_1_of_1? numRows: # total number of rows in the file (if known) rawSize: # total uncompressed size diskSize # is this useful? when binary copying? planes: [] # list of storage planes that the file is stored on. tlk: # ???Should the tlk be stored in the meta and returned? splitType: <split-format> # Are there associated split points, and if so what format? (And if so what variant?)

#options relating to the format of the input file:

: grouped: <boolean> # is the file grouped? compressed: <boolean> blockCompressed: <boolean> formatOptions: # Any options that relate to the file format e.g. csvTerminator. These are nested because they can be completely free format recordSize: # if a fixed size record. Not really sure it is useful

part: \# optional information about each of the file parts (Cannot
+implement virtual file position without this) - numRows: \<count\>
+\# number of rows in the file part rawSize: \<size\> \# uncompressed
+size of the file part diskSize: \<size\> \# size of the part on disk
+

# extra fields that are used to return information from the file lookup service

missing: <boolean> # true if the file could not be found external: <boolean> # filename of the form external:: or plane:

If the information needs to be signed to be passed to dafilesrv for example, the entire structure of (storage, files) is serialized, and compressed, and that then signed.

Functions

Logically executed on the engine, and retrived from dali or in future versions from an esp service (even if for remote reads).

GetFileInfomation(<logical-filename>, <options>)

The logical-filename can be any logical name - including a super file, or an implicit superfile.

options include: * Are compressed sizes needed? * Are signatures required? * Is virtual fileposition (non-local) required? * name of the user

This returns a structure that provides information about a list of files

meta:

: hostGroups: storage: files: secrets: #The secret names are known, how do we know which keys are required for those secrets?

Some key questions: * Should the TLK be in the dali meta information? [Possibly, but not in first phase. ] * Should the split points be in the dali meta information? [Probably not, but the meta should indicate whether they exist, and if so what format they are. ] * Super files (implicit or explicit) can contain the same file information more than once. Should it be duplicated, or have a flag to indicate a repeat. [I suspect this is fairly uncommon, so duplication would be fine for the first version.] * What storage plane information is serialized back? [ all is simplest. Can optimize later. ]

NOTE: This doesn't address the question of writing to a disk file...


Local class for interpreting the results. Logically executed on the manager, and may gather extra information that will be serialized to all workers. The aim is that the same class implementations are used by all the engines (and fileview in esp).

MasterFileCollection : RemoteFileCollection : FileCollection(eclReadOptions, eclFormatOptions, wuid, user, expectedMeta, projectedMeta); MasterFileCollection //Master has access to dali RemoteFileCollection : has access to remote esp // think some more

FileCollection::GatherFileInformation(<logical-filename>, gatherOptions); - potentially called once per query. - class is responsible for optimizing case where it matches the previous call (e.g. in a child query). - possibly responsible for retrieving the split points ()

Following options are used to control whether split points are retrieved when file information is gathered * number of channels reading the data? * number of strands reading each channel? * preserve order?

gatherOptions: * is it a temporary file?

This class serializes all information to every worker, where it is used to recereate a copy of the master filecollection. This will contain information derived from dali, and locally e.g. options specified in the activity helper. Each worker has a complete copy of the file information. (This is similar to dafilesrv with security tokens.)

The files that are actually required by a worker are calculated by calling the following function. (Note the derived information is not serialized.)

FilePartition FileCollection::calculatePartition(numChannels, partitionOptions)

partitionOptions: * number of channels reading the data? * number of strands reading each channel? * which channel? * preserve order? * myIP

A file partition contains a list of file slices:

class FileSlice (not serialized) { IMagicRowStream * createRowStream(filter, ...); // MORE! File * logicalFile; offset_t startOffset; offset_t endOffset; };

Things to bear in mind: - Optimize same file reused in a child query (filter likely to change) - Optimize same format reused in a child query (filename may be dynamic) - Intergrating third party file formats and distributed file systems may require extra information. - optimize reusing the format options. - ideally fail over to a backup copy midstream.. and retry in failed read e.g. if network fault

Examples

Example definition for a thor400, and two thor200s on the same nodes:

hostGroup: - name: thor400Group host: [node400_01,node400_02,node400_03,...node400_400]

storage:

: planes: #Simple 400 way thor - name: thor400 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group #The storage plane used for replicating files on thor. - name: thor400_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group offset: 1 # A 200 way thor using the first 200 nodes as the thor 400 - name: thor200A prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 # A 200 way thor using the second 200 nodes as the thor 400 - name: thor200B prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 start: 200 # The replication plane for a 200 way thor using the second 200 nodes as the thor 400 - name: thor200B_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group size: 200 start: 200 offset: 1 # A roxie storage where 50way files are stored on a 100 way roxie - name: roxie100 prefix: /var/lib/HPCCSystems/roxie100 hosts: thor400Group size: 50 # The replica of the roxie storage where 50way files are stored on a 100 way roxie - name: roxie100_R1 prefix: /var/lib/HPCCSystems/thor400 hosts: thor400Group start: 50 size: 50

device = (start + (part + offset) % size;

size <= numDevices offset < numDevices device <= numDevices;

There is no special casing of roxie replication, and each file exists on multiple storage planes. All of these should be considered when determining which is the best copy to read from a particular engine node.

Creating storage planes from an existing systems [implemented]

Milestones:

a) Create baremetal storage planes [done]

b) [a] Start simplifying information in dali meta (e.g. partmask, remove full path name) c) [a] Switch reading code to use storageplane away from using dali path and environment paths - in ALL disk reading and writing code - change numDevices so it matches the container d) [c] Convert dali information from using copies to multiple groups/planese) [a] Reimplement the current code to create an IPropertyTree from dali file information (in a form that can be reused in dali) *f) [e] Refactor existing PR to use data in an IPropertyTree and cleanly separate the interfaces. g) Switch hthor over to using the new classes by default and work through all issues h) Refactor stream reading code. Look at the spark interfaces for inspiration/compatibility i) Refactor disk writing code into common class? j) [e] create esp service for accessing meta information k) [h] Refactor and review azure blob code l) [k] Re-implement S3 reading and writing code.

m) Switch fileview over to using the new classes. (Great test they can be used in another context + fixes a longstanding bug.)

) Implications for index reading? Will they end up being treated as a normal file? Don't implement for 8.0, although interface may support it.

*) My primary focus for initial work.

File reading refactoring

Buffer sizes: - storage plane specifies an optimal reading minimum - compression may have a requirement - the use for the data may impose a requirement e.g. a subset of the data, or only fetching a single record

  • parallel disk reading may want to read a big chunk, but then process in sections. groan.

Look at lambda functions to create split points for a file. Can we use the java classes to implement it on binary files (and csv/xml)?

****************** Reading classes and meta information****************** meta comes from a combination of the information in dfs and the helper

The main meta information uses the same structure that is return by the function that returns file infromation from dali. The format specific options are contained in a nested attribute so they can be completely arbitrary

The helper class also generates a meta structure. Some options fill in root elements - e.g. compressed. Some fill in a new section (hints: @x=y). The format options are generated from the paramaters to the dataset format.

note normally there is only a single (or very few) files, so merging isn't too painful. queryMeta() queryOptions() rename meta to format? ???

DFU server

Where does DFUserver fit in in a container system?

DFU has the following main functionality in a bare metal system: a) Spray a file from a 1 way landing zone to an N-way thor b) Convert file format when spraying. I suspect utf-16->utf8 is the only option actually used. c) Spray multiple files from a landing zone to a single logical file on an N-way thor d) Copy a logical file from a remote environment e) Despray a logical file to an external landing zone. f) Replicate an existing logical file on a given group. g) Copy logical files between groups h) File monitoring i) logical file operations j) superfile operations

ECL has the ability to read a logical file directly from a landingzone using 'FILE::<ip>' file syntax, but I don't think it is used very frequently.

How does this map to a containerized system? I think the same basic operations are likely to be useful. a) In most scenarios Landing zones are likely to be replaced with (blob) storage accounts. But for security reasons these are likely to remain distinct from the main location used by HPCC to store datasets. (The customer will have only access keys to copy files to and from those storage accounts.) The containerized system has a way for ECL to directly read from a blob storage account ('PLANE::<plane'), but I imagine users will still want to copy the files in many situations to control the lifetime of the copies etc. b) We still need a way to convert from utf16 to utf8, or extend the platform to allow utf16 to be read directly. c) This is still equally useful, allowing a set of files to be stored as a single file in a form that is easy for ECL to process. d) Important for copying data from an existing bare metal system to the cloud, and from a cloud system back to a bare metal system. e) Useful for exporting results to customers f+g) Essentially the same thing in the cloud world. It might still be useful to have h) I suspect we will need to map this to cloud-specific apis. i+j) Just as applicable in the container world.

Broadly, landing zones in bare metal map to special storage planes in containerized, and groups also map to more general storage planes.

There are a couple of complications connected with the implementation:

  1. Copying is currently done by starting an ftslave process on either the source or the target nodes. In the container world there is no local node, and I think we would prefer not to start a process in order to copy each file. 2) Copying between storage groups should be done using the cloud provider api, rather than transferring data via a k8s job.

Suggestions:

  • Have a load balanced dafilesrv which supports multiple replicas. It would have a secure external service, and an internal service for trusted components.
  • Move the ftslave logic into dafilesrv. Move the current code for ftslave actions into dafilesrv with new operations.
  • When copying from/to a bare metal system the requests are sent to the dafilesrv for the node that currently runs ftslave. For a container system the requests are sent to the loadbalanced service.
  • It might be possible to migrate to lamda style functions for some of the work...
  • A later optimization would use a cloud service where it was possible.
  • When local split points are supported it may be better to spray a file 1:1 along with partition information. Even without local split points it may still be better to spray a file 1:1 (cheaper).
  • What are the spray targets? It may need to be storage plane + number of parts, rather than a target cluster. The default number of parts is the #devices on the storage plane.

=> Milestones a) Move ftslave code to dafilesrv (partition, pull, push) [Should be included in 7.12.x stream to allow remote read compatibility?] b) Create a dafilesrv component to the helm charts, with internal and external services. c) use storage planes to determine how files are sprayed etc. (bare-metal, #devices) Adapt dfu/fileservices calls to take (storageplane,number) instead of cluster. There should already be a 1:1 mapping from existing cluster to storage planes in a bare-metal system, so this may not involve much work. [May also need a flag to indicate if ._1_of_1 is appended?] d) Select correct dafilesrv for bare-metal storage planes, or load balanced service for other. (May need to think through how remote files are represented.)

=> Can import from a bare metal system or a containerized system using command line??

: NOTE: Bare-metal to containerized will likely need push operations on the bare-metal system. (And therefore serialized security information) This may still cause issues since it is unlikely containerized will be able to pull from bare-metal. Pushing, but not creating a logical file entry on the containerized system should be easier since it can use a local storage plane definition.

e) Switch over to using the esp based meta information, so that it can include details of storage planes and secrets.

: [Note this would also need to be in 7.12.x to allow remote export to containerized, that may well be a step too far]

f) Add option to configure the number of file parts for spray/copy/despray g) Ensure that eclwatch picks up the list of storage planes (and the default number of file parts), and has ability to specify #parts.

Later: h) plan how cloud-services can be used for some of the copies i) investigate using serverless functions to calculate split points. j) Use refactored disk read/write interfaces to clean up read and copy code. k) we may not want to expose access keys to allow remote reads/writes - in which they would need to be pushed from a bare-metal dafilesrv to a containerized dafilesrv.

Other dependencies: * Refactored file meta information. If this is switching to being plane based, then the meta information should also be plane based. Main difference is not including the path in the meta information (can just be ignored) * esp service for getting file information. When reading remotely it needs to go via this now...

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/README.html b/devdoc/README.html new file mode 100644 index 00000000000..7efeee778f6 --- /dev/null +++ b/devdoc/README.html @@ -0,0 +1,24 @@ + + + + + + Developer Documentation | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Developer Documentation

This directory contains the documentation specifically targeted at developers of the HPCC system.

TIP

These documents are generated from Markdown by VitePress. See VitePress Markdown for more details.

General documentation

Implementation details for different parts of the system

  • Workunit Workflow: An explanation of workunits, and a walk-through of the steps in executing a query.
  • Code Generator: Details of the internals of eclcc.
  • Roxie: History and design details for roxie.
  • Memory Manager: Details of the memory manager (roxiemem) used by the query engines.
  • Metrics: Metrics Framework Design.

Other documentation

The ECL language is documented in the ecl language reference manual (generated as ECLLanguageReference-<version>.pdf).

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/SecurityConfig.html b/devdoc/SecurityConfig.html new file mode 100644 index 00000000000..da983509863 --- /dev/null +++ b/devdoc/SecurityConfig.html @@ -0,0 +1,24 @@ + + + + + + Security Configuration | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Security Configuration

This document covers security configuration values and meanings. It does not serve as the source for how to configure security, but rather what the different values mean. These are not covered in the docs nor does any reasonable help information exist in the config manager or yaml files.

Supported Configurations

Security is configured either through an LDAP server or a plugin. Additionally, these are supported in both legacy deployments that use environment.xml and containerized deployments using Kubernetes and Helm charts. While these methods differ, the configuration values remain the same. Focus is placed on the different values and not the deployment target. Differences based on deployment can be found in the relevant platform documents.

Security Managers

Security is implemented via a security manager interface. Managers are loaded and used by components within the system to check authorization and authentication. LDAP is an exception to the loadable manager model. It is not a compliant loadable module like other security plugins. For that reason, the configuration for each is separated into two sections below: LDAP and Plugin Security Managers.

LDAP

LDAP is a protocol that connects to an Active Directory server (AD). The term LDAP is used interchangeably with AD. Below are the configuration values for an LDAP connection. These are valid for both legacy (environment.xml) and containerized deployments. For legacy deployments the configuration manager is the primary vehicle for setting these values. However, some values are not available through the tool and must be set manually in the environment.xml if needed for a legacy deployment.

In containerized environments, a LDAP configuration block is required for each component. Currently, this results in a verbose configuration where much of the information is repeated.

LDAP is capable if handling user authentication and feature access authorization (such as filescopes).

ValueExampleMeaning
adminGroupNameHPCCAdminsGroup name containing admin users for the AD
cacheTimeout60Timeout in minutes to keep cached security data
ldapCipherSuiteN/AUsed when AD is not up to date with latest SSL libs.
AD admin must provide
ldapPort389 (default)Insecure port
ldapSecurePort636 (default)Secure port over TLS
ldapProtocolldapldap for insecure (default), using ldapPort
ldaps for secure using ldapSecurePort
ldapTimeoutSec60 (default 5 for debug, 60 otherwise)Connection timeout to an AD before rollint to next AD
serverTypeActiveDirectoryIdentifies the type of AD server. (2)
filesBasednou=files,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where filescopes are stored
groupsBasednou=groups,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where groups are stored
modulesBaseDnou=modules,ou=ecl_kr,DC=z0lpf,DC=onmicrosoft,DC=comDN where permissions for resource are stored (1)
systemBasednOU=AADDC Users,DC=z0lpf,DC=onmicrosoft,DC=comDN where the system user is stored
usersBasednOU=AADDC Users,DC=z0lpf,DC=onmicrosoft,DC=comDN where users are stored (3)
systemUserhpccAdminAppears to only be used for IPlanet type ADs, but may still be required
systemCommonNamehpccAdminAD username of user to proxy all AD operations
systemPasswordSystem user passwordAD user password
ldapAdminSecretKeynoneKey for Kubernetes secrets (4) (5)
ldapAdminVaultIdnoneVault ID used to load system username and password (5)
ldapDomainnoneAppears to be a comma separated version of the AD domain name components (5)
ldapAddress192.168.10.42IP address to the AD
commonBasednDC=z0lpf,DC=onmicrosoft,DC=comOverrides the domain retrieved from the AD for the system user (5)
templateNamenoneTemplate used when adding resources (5)
authMethodnoneNot sure yet

Notes:

  1. modulesBaseDn is the same as resourcesBaseDn The code looks for first for modulesBaseDn and if not found will search for resourcesBaseDn
  2. Allowed values for serverType are ActiveDirectory, AzureActiveDirectory, 389DirectoryServer, OpenLDAP, Fedora389
  3. For AzureAD, users are managed from the AD dashboard, not via ECLWatch or through LDAP
  4. If present, ldapAdminVaultId is read and systemCommonName and systemPassword are read from the Kubernetes secrets store and not from the LDAP config values
  5. Must be configured manually in the environment.xml in legacy environments

Plugin Security Managers

Plugin security managers are separate shared objects loaded and initialized by the system. The manager interface is passed to components in order to provide necessary security functions. Each plugin has its own configuration. HPCC components can be configured to use a plugin as needed.

httpasswd Security Manager

See documentation for the settings and how to enable.

Single User Security Manager

To be added.

JWT Security Manager

To be added

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/SecurityUserAuthentication.html b/devdoc/SecurityUserAuthentication.html new file mode 100644 index 00000000000..a97f3b5a213 --- /dev/null +++ b/devdoc/SecurityUserAuthentication.html @@ -0,0 +1,24 @@ + + + + + + User Authentication | HPCC Platform + + + + + + + + + + + + + +
Skip to content

User Authentication

This document covers user authentication, the process of verifying the identity of the user. Authorization is a separate topic covering whether a user should be allowed to perform a specific operation of access a specific resource.

Each supported security manager is covered.

Generally, when authentication is needed, the security manager client should call the ISecManagerauthenticateUser method. The method also allows the caller to detect if the user being authenticated is a superuser. Use of that feature is beyond the scope of this document. In practice, this method is rarely if ever called. User authentication is generally performed as part of authorization. This is covered in more detail below.

Security Manager User Authentication

This section covers how each supported security manager handles user authentication. As stated above, the method authenticateUser is defined for this purpose. However, other methods also perform user authentication. The sections that follow describe in general how each security manager performs user authentication, whether from directly calling the authenticateUser method, or as an ancillary action taken when another method is called.

LDAP

The LDAP security manager uses the configured Active Directory to authenticate users. Once authenticated, the user is added to the permissions cache, if enabled, to prevent repeated trips to the AD whenever an authentication check is required.

If caching is enabled, a lookup is done to see if the user is already cached. If so, the cached user authentication status is returned. Note that the cached status remains until either the cache time to live expires or is cleared either manually or through some other programmatic action.

If caching is not enabled, a request is sent to the AD to validate the user credentials.

In either case, if digital signatures are configured, the user is also digitally signed using the username. Digitally signing the user allows for quick authentication by validating the signature against the username. During initial authentication, if the digital signature exists, it is verified to provide a fast way to authenticate the user. If the signature is not verified, the user is marked as not authenticated.

Authentication status is stored in the security user object so that further checks are not necessary when the same user object is used in multiple calls to the security manager.

HTPasswd

Authentication in the htpasswd manager does not support singularly authenticating the user without also authorizing resource access. See the special case for authentication with authorization below.

Regardless, the htpasswd manager authenticates users using the .htpasswd file that is installed on the cluster. It does so by finding the user in the file and verifying that the input hashed password matches the stored hashed password in file.

Single User

The single user security manager allows the definition of a single username with a password. The values are set in the environment configuration and are read during the initialization of the manager. All authentication requests validate against the configured username and password. The process is a simple comparison. Note that the password stored in the environment is hashed.

User Authentication During Authorization

Since resource access authorization requires an authenticated user, the authorization process also authenticates the user before checking authorization. There are a couple of advantages to this

  • Two separate calls are not required to check authorization (one to verify the user and one to check authorization)
  • The caller can perform third party authorization for specific user access

The authenticate method, or any of its overloads or derivatives, accepts a resource or resource list and a user. These methods authenticate the user first before checking access to the specified resource.

ECL Watch uses user authentication during authorization during its log in process. Instead of first authenticating the user, it calls an authenticate method passing both the user and the necessary resources for which the user must have access in order to log into ECL Watch.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/StyleGuide.html b/devdoc/StyleGuide.html new file mode 100644 index 00000000000..3dbf5807ec4 --- /dev/null +++ b/devdoc/StyleGuide.html @@ -0,0 +1,74 @@ + + + + + + Coding conventions | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Coding conventions

Why coding conventions?

Everyone has their own ideas of what the best code formatting style is, but most would agree that code in a mixture of styles is the worst of all worlds. A consistent coding style makes unfamiliar code easier to understand and navigate.

In an ideal world, the HPCC sources would adhere to the coding standards described perfectly. In reality, there are many places that do not. These are being cleaned up as and when we find time.

C++ coding conventions

Unlike most software projects around, HPCC has some very specific constraints that makes most basic design decisions difficult, and often the results are odd to developers getting acquainted with its code base. For example, when HPCC was initially developed, most common-place libraries we have today (like STL and Boost) weren't available or stable enough at the time.

Also, at the beginning, both C++ and Java were being considered as the language of choice, but development started with C++. So a C++ library that copied most behaviour of the Java standard library (At the time, Java 1.4) was created (see jlib below) to make the transition, if ever taken, easier. The transition never happened, but the decisions were taken and the whole platform is designed on those terms.

Most importantly, the performance constraints in HPCC can make no-brainer decisions look impossible in HPCC. One example is the use of traditional smart pointers implementations (such as boost::shared_ptr or C++'s auto_ptr), that can lead to up to 20% performance hit if used instead of our internal shared pointer implementation.

The last important point to consider is that some libraries/systems were designed to replace older ones but haven't got replaced yet. There is a slow movement to deprecate old systems in favour of consolidating a few ones as the elected official ways to use HPCC (Thor, Roxie) but old systems still could be used for years in tests or legacy sub-systems.

In a nutshell, expect re-implementation of well-known containers and algorithms, expect duplicated functionality of sub-systems and expect to be required to use less-friendly libraries for the sake of performance, stability and longevity.

For the most part out coding style conventions match those described at http://geosoft.no/development/cppstyle.html, with a few exceptions or extensions as noted below.

Source files

We use the extension .cpp for C++ source files, and .h or .hpp for header files. Header files with the .hpp extension should be used for headers that are internal to a single library, while header files with the .h extension should be used for the interface that the library exposes. There will typically be one .h file per library, and one .hpp file per cpp file.

Source file names within a single shared library should share a common prefix to aid in identifying where they belong.

Header files with extension .ipp (i for internal) and .tpp (t for template) will be phased out in favour of the scheme described above.

Java-style

We adopted a Java-like inheritance model, with macro substitution for the basic Java keywords. This changes nothing on the code, but make it clearer for the reader on what's the recipient of the inheritance doing with it's base.

  • interface (struct): declares an interface (pure virtual class)
  • extends (public): One interface extending another, both are pure virtual
  • implements (public): Concrete class implementing an interface

There is no semantic check, which makes it difficult to enforce such scheme, which has led to code not using it intermixed with code using it. You should use it when possible, most importantly on code that already uses it.

We also tend to write methods inline, which matches well with C++ Templates requirements. We, however, do not enforce the one-class-per-file rule.

See the Interfaces section for more information on our implementation of interfaces.

Identifiers

Class and interface names are in CamelCase with a leading capital letter. Interface names should be prefixed capital I followed by another capital. Class names may be prefixed with a C if there is a corresponding I-prefixed interface name, e.g. when the interface is primarily used to create an opaque type, but need not be otherwise.

Variables, function and method names, and parameters use camelCase starting with a lower case letter. Parameters may be prefixed with underscore when the parameter is used to initialize a member variable of the same name. Common cases are constructors and setter methods.

Example:

cpp
class MySQLSuperClass
+{
+    bool haslocalcopy = false;
+    void mySQLFunctionIsCool(int _haslocalcopy, bool enablewrite)
+    {
+        if (enablewrite)
+            haslocalcopy = _haslocalcopy;
+    }
+};

Pointers

Use real pointers when you can, and smart pointers when you have to. Take extra care on understanding the needs of your pointers and their scope. Most programs can afford a few dangling pointers, but a high-performance clustering platform cannot.

Most importantly, use common sense and a lot of thought. Here are a few guidelines:

  • Use real pointers for return values, parameter passing.
  • For .md variables use real pointers if their lifetime is guaranteed to be longer than the function (and no exception is thrown from functions you call), shared pointers otherwise.
  • Use Shared pointers for member variables - unless there is a strong guarantee the object has a longer lifetime.
  • Create Shared<X> with either:
    • Owned<X>: if your new pointer will take ownership of the pointer
    • Linked<X>: if you are sharing the ownership (shared)

Warning: Direct manipulation of the ownership might cause Shared<> pointers to lose the pointers, so subsequent calls to it (like o2->doIt() after o3 gets ownership) will cause segmentation faults.

Refer to [Reference counted objects]{.title-ref} for more information on our smart pointer implementation, Shared<>.

Methods that return pointers to link counted objects, or that use them, should use a common naming standard:

  • Foo * queryFoo() Does not return a linked pointer since lifetime is guaranteed for a set period. Caller should link if it needs to retain it for longer.
  • Foo * getFoo() Returned value is linked and should be assigned to an owned, or returned directly.
  • void setFoo(Foo * x) Generally parameters to functions are assumed to be owned by the caller, the callee needs to link them if they are retained.
  • void setFoo(Foo * ownedX) Some calls do transfer ownership of parameters - the parameter should be named to indicate this. If the function only has a single signficant parameter then sometimes the name of the function indicates the ownership.

Indentation

We use 4 spaces to indent each level. TAB characters should not be used.

The { that starts a new scope and the corresponding } to close it are placed on a new line by themselves, and are not indented. This is sometimes known as the Allman or ANSI style.

Comments

We generally believe in the philosophy that well written code is self-documenting. Comments are also encouraged to describe why something is done, rather than how - which should be clear from the code.

javadoc-formatted comments for classes and interfaces are being added.

Classes

The virtual keyword should be included on the declaration of all virtual functions - including those in derived classes, and the override keyword should be used on all virtual functions in derived classes.

Namespaces

MORE: Update!!!

We do not use namespaces. We probably should, following the Google style guide's guidelines - see http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces

Other

We often pretend we are coding in Java and write all our class members inline.

C++11

Other coding conventions

ECL code

The ECL style guide is published separately.

Javascript, XML, XSL etc

We use the commonly accepted conventions for formatting these files.


Design Patterns

Why Design Patterns?

Consistent use of design patterns helps make the code easy to understand.

Interfaces

While C++ does not have explicit support for interfaces (in the java sense), an abstract class with no data members and all functions pure virtual can be used in the same way.

Interfaces are pure virtual classes. They are similar concepts to Java's interfaces and should be used on public APIs. If you need common code, use policies (see below).

An interface's name must start with an 'I' and the base class for its concrete implementations should start with a 'C' and have the same name, ex:

cpp
CFoo : implements IFoo { };

When an interface has multiple implementations, try to stay as close as possible to this rule. Ex:

cpp
CFooCool : implements IFoo { };
+CFooWarm : implements IFoo { };
+CFooALot : implements IFoo { };

Or, for partial implementation, use something like this:

cpp
CFoo : implements IFoo { };
+CFooCool : public CFoo { };
+CFooWarm : public CFoo { };

Extend current interfaces only on a 'is-a' approach, not to aggregate functionality. Avoid pollution of public interfaces by having only the public methods on the most-base interface in the header, and internal implementation in the source file. Prefer pImpl idiom (pointer-to-implementation) for functionality-only requirements and policy based design for interface requirements.

Example 1: You want to decouple part of the implementation from your class, and this part does not implements the interface your contract requires.:

cpp
interface IFoo
+{
+    virtual void foo()=0;
+};
+
+// Following is implemented in a separate private file...
+class CFoo : implements IFoo
+{
+    MyImpl *pImpl;
+public:
+    virtual void foo() override { pImpl->doSomething(); }
+};

Example2: You want to implement the common part of one (or more) interface(s) in a range of sub-classes.:

cpp
interface ICommon
+{
+    virtual void common()=0;
+};
+interface IFoo : extends ICommon
+{
+    virtual void foo()=0;
+};
+interface IBar : extends ICommon
+{
+    virtual void bar()=0;
+};
+
+template <class IFACE>
+class Base : implements IFACE
+{
+    virtual void common() override { ... };
+}; // Still virtual
+
+class CFoo : public Base<IFoo>
+{
+    void foo() override { 1+1; };
+};
+class CBar : public Base<IBar>
+{
+    void bar() override { 2+2; };
+};

NOTE: Interfaces deliberately do not contain virtual destructors. This is to help ensure that they are never destroyed by calling delete directly.

Reference counted objects

Shared<> is an in-house intrusive smart pointer implementation. It is close to boost's intrusive_ptr. It has two derived implementations: Linked and Owned, which are used to control whether the pointer is linked when a shared pointer is created from a real pointer or not, respectively. Ex:

cpp
Owned<Foo> myFoo = new Foo; // Take owenership of the pointers
+Linked<Foo> anotherFoo = = myFoo; // Shared ownership

Shared<> is thread-safe and uses atomic reference count handled by each object (rather than by the smart pointer itself, like boost's shared_ptr).

This means that, to use Shared<>, your class must implement the Link() and Release() methods - most commonly by extending the CInterfaceOf<> class, or the CInterface class (and using the IMPLEMENT_IINTERFACE macro in the public section of your class declaration).

This interface controls how you Link() and Release() the pointer. This is necessary because in some inner parts of HPCC, the use of a "really smart" smart pointer would add too many links and releases (on temporaries, local variables, members, etc) that could add to a significant performance hit.

The CInterface implementation also include a virtual function beforeDispose() which is called before the object is deleted. This allows resources to be cleanly freed up, with the full class hierarchy (including virtual functions) available even when freeing items in base classes. It is often used for caches that do not cause the objects to be retained.

STL

MORE: This needs documenting

Structure of the HPCC source tree

MORE!

Requiring more work: * namespaces * STL * c++11 * Review all documentation * Better examples for shared

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/UserBuildAssets.html b/devdoc/UserBuildAssets.html new file mode 100644 index 00000000000..7b9ab551b1e --- /dev/null +++ b/devdoc/UserBuildAssets.html @@ -0,0 +1,57 @@ + + + + + + Build Assets for individual developer | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Build Assets for individual developer

Build Assets

The modern tool used for generating all our official assets is the Github Actions build-asset workflow on the hpcc-systems/HPCC-Platform repository, located here. Developers and contributors can utilize this same workflow on their own forked repository. This allows developers to quickly create assets for testing changes and test for errors before the peer review process.

Build assets will generate every available project under the HPCC-Platform namespace. There currently is not an option to control which packages in the build matrix get generated. But most packages get built in parallel, and released after the individual matrix job is completed, so there is no waiting on packages you don't need. Exceptions to this are for packages that require other builds to complete, such as the ECLIDE.

Upon completion of each step and matrix job in the workflow, the assets will be output to the repositories tags tab. An example for the hpcc-systems user repository is hpcc-systems/HPCC-Platform/tags.

Tag tab screenshot

Dependent variables

The build assets workflow requires several repository secrets be available on a developers machine in order to run properly. You can access these secrets and variables by going to the settings tab in your forked repository, and then clicking on the Secrets and Variables - Actions drop down under Security on the lefthand side of the settings screen.

Actions secrets and variables

Create a secret by clicking the green New Repository Secret button. The following secrets are needed;

  • LNB_ACTOR - Your Github username
  • LNB_TOKEN - Classic Github token for your user with LN repo access
  • DOCKER_USERNAME - Your docker.io username
  • DOCKER_PASSWORD - Your docker.io password
  • SIGNING_CERTIFICATE - pks12 self signed cert encoded to base64 for windows signing
  • SIGNING_CERTIFICATE_PASSPHRASE - passphrase for pks12 cert
  • SIGNING_SECRET - ssh-keygen private key for signing linux builds
  • SIGN_MODULES_KEYID - email used to generate key
  • SIGN_MODULES_PASSPHRASE - passphrase for private key

Generating the windows signing certificate

To generate the self signed certificate for windows packages, you will need to do the following steps.

  1. Generate a root certificate authority

openssl req -x509 -sha256 -days 365 -nodes -newkey rsa:2048 -subj "/CN=example.com/C=US/L=Boca Raton" -keyout rootCA.key -out rootCA.crt

  1. Create the server secret key

openssl genrsa -out server.key 2048

  1. generate a csr.conf file
cat > csr.conf <<EOF
+[ req ]
+default_bits = 2048
+prompt = no
+default_md = sha256
+req_extensions = req_ext
+distinguished_name = dn
+
+[ dn ]
+C = US
+ST = Florida
+L = Boca Raton
+O = LexisNexis Risk
+OU = HPCCSystems Development
+CN = example.com
+
+[ req_ext ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = example.com
+IP.1 = 127.0.0.1
+
+EOF
  1. Generate Cert Signing Request

openssl req -new -key server.key -out server.csr -config csr.conf

  1. Create cert.conf file
cat > cert.conf <<EOF
+
+authorityKeyIdentifier=keyid,issuer
+basicConstraints=CA:FALSE
+keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = example.com
+
+EOF
  1. Generate SSL cert with self signed CA

openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256 -extfile cert.conf

  1. Use server.crt to generate PKCS12 needed for windows tools

openssl pkcs12 -inkey server.key -in server.crt -export -name "hpcc_sign_cert" -out hpcc_sign_cert.pfx

You will be asked to "enter export password", this will be what goes in the variable SIGNING_CERTIFICATE_PASSPHRASE in Github Actions.

  1. Convert to base64

On linux: base64 hpcc_sign_cert.pfx > hpcc_sign_cert.base64

On MacOS: base64 -i hpcc_sign_cert.pfx -o hpcc_sign_cert.base64

From here you can cat the output of hpcc_sign_cert.base64 and copy the output into the variable SIGNING_CERTIFICATE in Github Actions.

Generating a signing key for linux builds

For linux builds we're going to generate a private key using GnuPG (gpg).

Start the process by entering a terminal and run the command;gpg --full-generate-key

You will be given several options in this process.

For type of key, select RSA and RSA default.

For keysize, enter 4096.

For expiration date, select 0 = key does not expire.

Input your real name.

Input your company email address.

For comment, input something like Github actions key for signing linux builds.

Then it will ask you to enter a passphrase for the key, and confirm the passphrase. Do not leave this blank.

A key should be output and entered into your gpg keychain. Now we need to export the key for use in the github actions secret.

To extract your key run gpg --output private.pgp --armor --export-secret-key <email-address-used>.

Now open private.pgp, copy all, and go to github actions secrets. Paste the output into the secret "SIGNING_SECRET"

Starting a build

The build-asset workflow is kicked off by a tag being pushed to the developers HPCC-Platform repository. Before we push the tag to our HPCC-Platform repository, we will want to have other tags in place if we want LN and ECLIDE builds to function correctly. Suggested tag patterns are community_HPCC-12345-rc1 or HPCC-12345-rc1.

If you choose not to tag the LN and ECLIDE builds, the community builds will generate but errors will be thrown for any build utilizing the LN repository. ECLIDE will not even attempt a build unless you are also successfully building LN due to the dependency scheme we use. The 'Baremetal' builds are designed to generate our clienttools targets for windows-2022 and macos-12 distributions. These jobs contain both the COMMUNITY and LN builds. If the LN build is not tagged, the COMMUNITY section of the job will run, and the assets will be uploaded, but the job will fail when it tries to build LN.

If you choose to precede your Jira number with community_ then you must tag LN with internal_ and ECLIDE with eclide_. Otherwise just use the Jira tag in all three repositories.

Once the LN and ECLIDE repository tags have been created and pushed with the same base branch that your work is based on for the HPCC-Platform, then you are free to push the HPCC-Platform tag which will initiate the build process.

The summary of the build-asset workflow can then be viewed for progress, and individual jobs can be selected to check build outputs. Build Summary HPCC-12345

Asset output

Assets from the workflow will be released into the corresponding tag location, either in the HPCC-Platform repository for all community based builds, or the LN repository for any builds containing proprietary plugins. Simply browse to the releases or tag tab of your repository and select the tag name you just built. The assets will show up there as the build completes. An example of this on the hpcc-systems repository is hpcc-systems/HPCC-Platform/releases.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/VersionSupport.html b/devdoc/VersionSupport.html new file mode 100644 index 00000000000..b9029549af2 --- /dev/null +++ b/devdoc/VersionSupport.html @@ -0,0 +1,24 @@ + + + + + + Current versions | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Current versions

nameversion
current9.8.x
previous9.6.x
critical9.4.x
security9.2.x

Supported versions

We release a new version of the platform every 3 months. If there are major changes in functionality, or significant backward compatibility issues then it will be tagged as a new major version, otherwise a new minor version. We normally maintain 4 versions of the system, which means that each new release will typically be supported for a year. Once a new major or minor version has been tagged gold it should not have any changes that change the behavior of queries.

Which versions should changes be applied to? The following gives some examples of the types of changes and which version they would be most relevant to target.

"master":

  • New features.
  • Bug fixes that will change the semantics of existing queries or processes.
  • Refactoring.
  • Performance improvements (unless simple and safe)

"<current>":

  • Bug fixes that only change behavior where it previously crashes or had undefined behavior (If well defined but wrong need to have very strong justification to change.)
  • Fixes for race conditions (the behavior was previously indeterminate so less of an argument against it changing)
  • Data corruption fixes - on a case by case basis if they change existing query results.
  • Missing functionality that prevents features from working.
  • Changes for tech-preview work that only effect those who are using it.
  • Regressions.
  • Improvements to logging and error messages (possibly in "previous" if simple and added to help diagnose problems).
  • Occasional simple refactoring that makes up-merging simpler..
  • Changes to improve backward compatibility of new features. (E.g. adding an ignored syntax to the compiler.)
  • Performance improvements - if simple and safe

"<previous>":

  • Simple bug fixes that do not change behavior
  • Simple changes for missing functionality
  • Regressions with simple fixes (but care is needed if it caused a change in behavior)
  • Serious regressions
  • Complex security fixes

"<critical>" fixes only:

  • Simple security fixes
  • Complex security fixes if sufficiently serious

"<security>" fixes only:

  • Serious security fixes

Occasionally earlier branches will be chosen, (e.g. security fixes to even older versions) but they should always be carefully discussed (and documented).

Patches and images

We aim to produce new point releases once a week. The point releases will contain

a) Any changes to the code base for that branch. b) Any security fixes for libraries that are project dependencies. We will upgrade to the latest point release for the library that fixes the security issue. c) For the cloud any security fixes in the base image or the packages installed in that image.

If there are no changes in any of those areas for a particular version then a new point release will not be created.

If you are deploying a system to the cloud you have one of two options

a) Use the images that are automatically built and published as part of the build pipeline. This image is currently based on ubuntu 22.04 and contains the majority of packages users will require.

b) Use your own hardened base image, and install the containerized package that we publish into that image.

Package versions.

We currently generate the following versions of the package and images:

  • debug
  • release with symbols
  • release without symbols.

It is recommended that you deploy the "release with symbols" version to all bare-metal and non-production cloud deployments. The extra symbols allow the system to generate stack backtraces which make it much easier to diagnose problems if they occur. The "release without symbols" version is recommended for Kubernetes production deployments. Deploying a system without symbols reduces the size of the images. This reduces the time it takes Kubernetes to copy the image before provisioning a new node.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/Workunits.html b/devdoc/Workunits.html new file mode 100644 index 00000000000..dce738a29b3 --- /dev/null +++ b/devdoc/Workunits.html @@ -0,0 +1,426 @@ + + + + + + Understanding workunits | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Understanding workunits

Introduction

A workunit contains all the information that the system requires about a query - including the parameters it takes, how to execute it, and how to format the results. Understanding the contents of a workunit is a key step to understanding how the HPCC system fits together. This document begins with an overview of the different elements in a workunit. That is then followed by a walk-through executing a simple query, with a more detailed description of some of the workunit components to show how they all tie together.

Before looking at the contents of a workunit it is important to understand one of the design goals behind the HPCC system. The HPCC system logically splits the code that is needed to execute a query in two. On the one hand there are the algorithms that are used to perform different dataset operations (e.g. sorting, deduping). The same algorithms are used by all the queries that execute on the system. On the other hand there is the meta-data that describes the columns present in the datasets, which columns you need to sort by, and the order of operations required by the query. These are typically different for each query. This "meta-data" includes generated compare functions, classes that describe the record formats, serialized data and graphs.

A workunit only contains data and code relating to the meta data for the query, i.e. "what to do", while the different engines (hthor, roxie, and thor) implement the algorithms - "how to do it". If you look at a workunit for an ECL query that sorts a dataset you will not find code to perform the sort itself in the workunit - you will not even find a call to a sort library function - that logic is contained in the engine that executes the query.

One consequence of this split, which can be initially confusing, is that execution continually passes back and forth between the generated code and the engines. By the end of this document you should have a better understanding of how the generated code is structured and the part it plays in executing a query.

Note the term "Query" is used as a generic term to cover read-only queries (typically run in roxie) and ETL (Extract, Transform, and Load) style queries that create lots of persistent datafiles (typically run in Thor). Also, the term "workunit" is used ambiguously. The dll created from a query is called a workunit (which is static), but "workunit" is also used to describe a single execution of a query (which includes the parameters and results). It should be clear from the context which of these is meant.

Throughout this document "dll" is a generic term used to refer to a dynamically loaded library. These correspond to shared objects in Linux (extension '.so'), dynamic libraries in Max OS X ('.dylib'), and dynamic link libraries in windows ('.dll').

Contents of a workunit

A workunit is generated by the ecl compiler, and consists of a single dll. That dll contains several different elements:

  • Various C++ helper classes, and exported factory methods are used to create instances of those classes.
  • An XML resource containing different pieces of information about a workunit, including workflow and graphs.
  • Other user-defined resources included in the manifest.

A workunit dll contains everything that the engines need to execute the query. When a workunit is executed, key elements of the xml information are cloned from the workunit dll and copied into a database. This is then augmented with other information as the query is executed - e.g. input parameters, results, statistics, etc.. The contents of the workunit are accessed through an "IWorkUnit" interface (defined in common/workunit/workunit.hpp) that hides the implementation details.

(Workunit information is currently stored in the Dali database - one of the components within the HPCC platform. Work is in-progress to allow the bulk of this workunit data to be stored in Cassandra or another third-party database instead.)

How is the workunit used?

The workunit information is used by most of the components in the system. The following is a quick outline:

  • eclcc

    Creates a workunit dll from an ecl query.

  • eclccserver

    Executes eclcc to create a workunit dll, and then clones some of the information into dali to create an active instance, ready to execute.

  • esp

    Uses information in the workunit dll to publish workunits. This includes details of the parameters that the query takes, how they should be formatted, and the results it returns.

  • eclscheduler

    Monitors workunits that are waiting for events, and updates them when those events occur.

  • eclagent/Roxie

    Process the different workflow actions, and workflow code.

  • hThor/Roxie/Thor

    Execute graphs within the workflow items.

  • Dali

    This database is used to store the state of the workunit state.

Example

The following ECL will be used as an example for the rest of the discussion. It is a very simple search that takes a string parameter 'searchName', which is the name of the person to search for, and returns the matching records from an index. It also outputs the word 'Done!' as a separate result.

ecl
STRING searchName := 'Smith' : STORED('searchName');
+nameIndex := INDEX({ STRING40 name, STRING80 address }, 'names');
+results := nameIndex(KEYED(name = searchName));
+OUTPUT(results);
+OUTPUT('Done!');

Extracts from the XML and C++ that are generated from this example will be included in the following discussion.

Workunit Main Elements

This section outlines the different sections in a workunit. This is followed by a walk-through of the stages that take place when a workunit is executed, together with a more detailed explanation of the workunit contents.

Workflow

The workflow is the highest level of control within a workunit. It is used for two related purposes:

  • Scheduling The HPCC system allows ECL code to be executed when certain events occur - e.g. every hour or when files are uploaded to a landing zone. Each piece of ECL code that is triggered by an external event creates a separate workflow action. This allows each of those events to be processed independently.
  • Splitting up queries. There are situations where it is useful to break parts of an ECL query into independent sections. The simplest example is the PERSIST workflow operation, which allows results to be shared between different work units. Each workflow operation creates one (or sometimes more) independent workflow items, which are then connected together.

Each piece of independent ECL is given a unique workflow id (wfid). Often workflow items need to be executed in a particular order, e.g. ensuring a persist exists before using it, which is managed with dependencies between different workflow items.

Our example above generates the following XML entry in the workunit:

xml
<Workflow>
+    <Item .... wfid="1"/>
+    <Item .... wfid="2">
+    <Dependency wfid="1"/>
+    <Schedule/>
+    </Item>
+</Workflow>

This contains two workflow items. The first workflow item (wfid=1) ensures that the stored value has a default value if it has not been supplied. The second item (with wfid=2) is the main code for the query. This has a dependency on the first workflow item because the stored variable needs to be intialised before it is executed.

MyProcess

The generated code contains a class instance that is used for executing the code associated with the workflow items. It is generated at the end of the main C++ module. E.g.:

cpp
struct MyEclProcess : public EclProcess {
+    virtual int perform(IGlobalCodeContext * gctx, unsigned wfid) {
+        ....
+        switch (wfid) {
+            case 1U:
+                ... code for workflow item 1 ...
+            case 2U:
+                ... code for workflow item 2 ...
+            break;
+        }
+        return 2U;
+    }
+};

The main element is a switch statement inside the perform() function that allows the workflow engines to execute the code associated with a particular workflow item.

There is also an associated factory function that is exported from the dll, and is used by the engines to create instances of the class:

cpp
extern "C" ECL_API IEclProcess* createProcess()
+{
+    return new MyEclProcess;
+}

Graph

Most of the work executing a query involves processing dataset operations, which are implemented as a graph of activities. Each graph is represented in the workunit as an xml graph structure (currently it uses the xgmml format). The graph xml includes details of which types of activities are required to be executed, how they are linked together, and any other dependencies.

The graph in our example is particularly simple:

xml
<Graph name="graph1" type="activities">
+    <xgmml>
+    <graph wfid="2">
+    <node id="1">
+    <att>
+        <graph>
+        <att name="rootGraph" value="1"/>
+        <edge id="2_0" source="2" target="3"/>
+        <node id="2" label="Index Read&#10;&apos;names&apos;">
+        ... attributes for activity 2 ...
+        </node>
+        <node id="3" label="Output&#10;Result #1">
+        ... attributes for activity 3 ...
+        </node>
+        </graph>
+    </att>
+    </node>
+    </graph>
+    </xgmml>
+</Graph>

This graph contains a single subgraph (node id=2) that contains two activities - an index read activity and an output result activity. These activities are linked by a single edge (id "2_0"). The details of the contents are covered in the section on executing graphs below.

Generated Activity Helpers

Each activity has a corresponding class instance in the generated code, and a factory function for creating instances of that class:

cpp
struct cAc2 : public CThorIndexReadArg {
+    ... Implementation of the helper for activity #2 ...
+};
+extern "C" ECL_API IHThorArg * fAc2() { return new cAc2; }
+
+struct cAc3 : public CThorWorkUnitWriteArg {
+    ... Implementation of the helper for activity #3 ...
+};
+extern "C" ECL_API IHThorArg * fAc3() { return new cAc3; }

The helper class for an activity implements the interface that is required for that particular kind. (The interfaces are defined in rtl/include/eclhelper.hpp - further details below.)

Other

The are several other items, detailed below, that are logically associated with a workunit. The information may be stored in the workunit dll or in various other location e.g. Dali, Sasha or Cassandra. It is all accessed through the IWorkUnit interface in common/workunit/workunit.hpp that hides the implementation details. For instance information generated at runtime cannot by definition be included in the workunit dll.

Options

Options that are supplied to eclcc via the -f command line option, or the #option statement are included in the <Debug> section of the workunit xml:

xml
<Debug>
+    <addtimingtoworkunit>0</addtimingtoworkunit>
+    <noterecordsizeingraph>1</noterecordsizeingraph>
+    <showmetaingraph>1</showmetaingraph>
+    <showrecordcountingraph>1</showrecordcountingraph>
+    <spanmultiplecpp>0</spanmultiplecpp>
+    <targetclustertype>hthor</targetclustertype>
+</Debug>

Note, the names of workunit options are case insensitive, and converted to lower case.

Input Parameters

Many queries contain input parameters that modify their behaviour. These correspond to STORED definitions in the ECL. Our example contains a single string "searchName", so the workunit contains a single input parameter:

xml
<Variables>
+    <Variable name="searchname">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+        searchname&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;   
+    </SchemaRaw>
+    </Variable>
+</Variables>

The implementation details of the schema information is encapsulated by the IConstWUResult interface in workunit.hpp.

Results

The workunit xml also contains details of each result that the query generates, including a serialized description of the output record format:

xml
<Results>
+    <Result isScalar="0"
+            name="Result 1"
+            recordSizeEntry="mf1"
+            rowLimit="-1"
+            sequence="0">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+    name&#xe000;&#xe004;(&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;address&#xe000;&#xe004;P&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;%&#xe000;&#xe000;&#xe000;{ string40 name, string80 address };&#10;   </SchemaRaw>
+    </Result>
+    <Result name="Result 2" sequence="1">
+    <SchemaRaw xsi:type="SOAP-ENC:base64">
+    Result_2&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;   </SchemaRaw>
+    </Result>
+</Results>

in our example there are two - the dataset of results and the text string "Done!". The values of the results for a query are associated with the workunit. (They are currently saved in dali, but this may change in version 6.0.)

Timings and Statistics

Any timings generated when compiling the query are included in the workunit dll:

xml
<Statistics>
+    <Statistic c="eclcc"
+            count="1"
+            creator="eclcc"
+            kind="SizePeakMemory"
+            s="compile"
+            scope=">compile"
+            ts="1428933081084000"
+            unit="sz"
+            value="27885568"/>
+</Statistics>

Other statistics and timings created when running the query are stored in the runtime copy of the workunit. (Statistics for graph elements are stored in a different format from global statistics, but the IWorkUnit interface ensures the implementation details are hidden.)

Manifests

It is possible to include other user-defined resources in the workunit dll - e.g. web pages, or dashboard layouts. I have to confess I do not understand them... ??Tony please provide some more information....!

Stages of Execution

Once a workunit has been compiled to a dll it is ready to be executed. Execution can be triggered in different ways, E.g.:

  • The ECL for a query is submitted to esp
    • A workunit entry, containing the ECL, is created in dali and added to an eclccserver queue.
    • An eclccserver instance removes the workunit form the queue, and compiles the ECL to a workunit dll.
    • The dali workunit entry is updated with the information from the workunit dll.
    • The dali workunit is added to the agent execution queue associated with the target cluster.
    • The associated engine (actually agentexec for hThor and Thor) pulls a query form the queue and executes it.
  • A query is submitted and published with a name. Another request is then submitted to execute this previously compiled query.
    • A workunit entry, containing the ECL, is created in dali and added to an eclccserver queue.
    • An eclccserver instance removes the workunit form the queue, and compiles the ECL to a workunit dll.
    • There is a 'query set' for each combination of query name and the target cluster. The new workunit dll is added to the appropriate query set, and marked as the current active implementation.
    • Later, a query that references a named query is submitted to esp.
    • The name and target cluster are mapped via the query set to the active implementation, and a workunit instance is created from the active workunit dll.
    • The workunit is added to a roxie or eclagentexec queue ready to be executed.
    • The associated engine pulls a query form the queue and executes it.
  • A query is compiled as a stand alone executable. The executable is then run.
    • eclcc is executed on the command line without the -shared command line option.
    • The resulting executable is run. The engine used to execute the query depends on the -platform parameter supplied to eclcc.

Most queries create persistent workunits in dali and then update those workunits with results as they are calculated, however for some roxie queries (e.g. in a production system) the execution workunits are transient.

The following walk-through details the main stages executing a query, and the effect each of the query elements has.

Queues

The system uses several inter-process queues to communicate between the different components in the system. These queues are implemented by dali. Components can subscribe to one or more queues, and receive notifications when entries are avaialable.

Some example queues are:

  • <cluster>.eclserver - workunits to be compiled
  • <cluster>.roxie - workunits to execute on roxie
  • <cluster>.thor - graphs to execute on thor
  • <cluster>.eclscheduler - workunits that need to wait for events
  • <cluster>.agent - workunits to be executed on hthor or thor.
  • dfuserver_queue - dfu workunits for sprays/file copies etc.

Workflow

When a workunit is ready to be run, the workflow controls the flow of execution. The workflow engine (defined in common/workunit/workflow.cpp) is responsible for determining which workflow item should be executed next.

The workflow for Thor and hThor jobs is coordinated by eclagent, while roxie includes the workflow engine in its process. The eclscheduler also uses the workflow engine to process events and mark workflow items ready for execution.

eclagent, or roxie calls the createProcess() function from the workunit dll to create an instance of the generated workflow helper class, and passes it to the workflow engine. The workflow engine walks the workflow items to find any items that are ready to be executed (have the state "reqd" - i.e. required). If a required workflow item has dependencies on other child workflow items then those children are executed first. Once all dependencies have executed successfully the parent workflow item is executed. The example has the following workflow entries:

xml
<Workflow>
+    <Item mode="normal"
+        state="null"
+        type="normal"
+        wfid="1"/>
+    <Item mode="normal"
+        state="reqd"
+        type="normal"
+        wfid="2">
+        <Dependency wfid="1"/>
+        <Schedule/>
+    </Item>
+</Workflow>

Item 2 has a state of "reqd", so it should be evaluated now. Item 2 has a dependency on item 1, so that must be evaluated first. This is achieved by calling MyEclProcess::perform() on the object that was previously created from the workunit dll, passing in wfid = 1. That will execute the following code:

cpp
switch (wfid) {
+    case 1U:
+        if (!gctx->isResult("searchname",4294967295U)) {
+            ctx->setResultString("searchname",4294967295U,5U,"Smith");
+        }
+        break;
+    break;
+}

This checks if a value has been provided for the input parameter, and if not assigns a default value of "Smith". The function returns control to the workflow engine. With the dependencies for wfid 2 now satisfied, the generated code for that workflow id is now executed:

cpp
switch (wfid) {
+    case 2U: {
+        ctx->executeGraph("graph1",false,0,NULL);
+        ctx->setResultString(0,1U,5U,"Done!");
+    }
+    break;
+}

Most of the work for this workflow item involves executing graph1 (by calling back into eclagent/roxie). However, the code also directly sets another result. This is fairly typical - the code inside MyProcess::perform often combines evaluating scalar results, executing graphs, and calling functions that cannot (currently) be called inside a graph (e.g. those involving superfile transactions).

Once all of the required workflow items are executed, the workunit is marked as completed. Alternatively, if there are workflow items that are waiting to be triggered by an event, the workunit will be passed to the scheduler, which will keep monitoring for events.

Note that most items in the xml workflow begin in the state WFStateNull. This means that it is valid to execute them, but they haven't been executed yet. Typically, only a few items begin with the state WFStateReqd.

There are various specialised types of workflow items - e.g. sequential, wait, independent, but they all follow the same basic approach of executing dependencies and then executing that particular item.

Most of the interesting work in an ECL query is done within a graph. The call ctx->executeGraph will either execute the graph locally (in the case of hthor and roxie), or add the workunit onto a queue (for Thor). Whichever happens, control will pass to that engine.

Specialised Workflow Items

Each item mode/type can affect the dependency structure of the workflow:

  • Sequential/Ordered

    The workflow structure for sequential and ordered is the same. An item is made to contain all of the actions in the statement. This is achieved by making each action a dependency of this item. The dependencies, and consequently the actions, must be executed in order. An extra item is always inserted before each dependency. This means that if other statements reference the same dependency, it will only be performed once.

  • Persist

    When the persist workflow service is used, two items are created. One item contains the graphs that perform the expression defined in ECL. It also stores the wfid for the second item. The second item is used to determine whether the persist is up to date.

  • Condition (IF)

    The IF function has either 2 or 3 arguments: the expression, the trueresult, and sometimes the falseresult. For each argument, a workflow item is created. These items are stored as dependencies to the condition, in the order stated above.

  • Contingency (SUCCESS/FAILURE)

    When a contingency clause is defined for an attribute, the attribute item stores the wfid of the contingency. If both success and failure are used, then the item will store the wfid of each contingency. The contingency is composed of items, just like the larger query.

  • Recovery

    When a workflow item fails, if it has a recovery clause, the item will be re-executed. The clause contains actions defined by the programmer to remedy the problem. This clause is stored differently to SUCCESS/FAILURE, in that the recovery clause is a dependency of the failed item. In order to stop the recovery clause from being executed like the other dependencies, it is marked with WFStateSkip.

  • Independent

    This specifier is used when a programmer wants to common up code for the query. It prevents the same piece of code from being executed twice in different contexts. To achieve this, an extra item is inserted between the expression and whichever items depend on it. This means that although the attribute can be referenced several times, it will only be executed once.

Graph Execution

All the engines (roxie, hThor, Thor) execute graphs in a very similar way. The main differences are that hThor and Thor execute a sub graph at a time, while roxie executes a complete graph as one. Roxie is also optimized to minimize the overhead of executing a query - since the same query tends to be run multiple times. This means that roxie creates a graph of factory objects and those are then used to create the activities. The core details are the same for each of them though.

Details of the graph structure

First, a recap of the structure of the graph together with the full xml for the graph definition in our example:

xml
<Graph name="graph1" type="activities">
+    <xgmml>
+        <graph wfid="2">
+            <node id="1">
+                <att>
+                    <graph>
+                        <att name="rootGraph" value="1"/>
+                        <edge id="2_0" source="2" target="3"/>
+                        <node id="2" label="Index Read&#10;&apos;names&apos;">
+                            <att name="definition" value="workuniteg1.ecl(3,1)"/>
+                            <att name="name" value="results"/>
+                            <att name="_kind" value="77"/>
+                            <att name="ecl" value="INDEX({ string40 name, string80 address }, &apos;names&apos;, fileposition(false));&#10;FILTER(KEYED(name = STORED(&apos;searchname&apos;)));&#10;"/>
+                            <att name="recordSize" value="120"/>
+                            <att name="predictedCount" value="0..?[disk]"/>
+                            <att name="_fileName" value="names"/>
+                        </node>
+                        <node id="3" label="Output&#10;Result #1">
+                            <att name="definition" value="workuniteg1.ecl(4,1)"/>
+                            <att name="_kind" value="16"/>
+                            <att name="ecl" value="OUTPUT(..., workunit);&#10;"/>
+                            <att name="recordSize" value="120"/>
+                        </node>
+                    </graph>
+                </att>
+            </node>
+        </graph>
+    </xgmml>
+</Graph>

Each graph (e.g. graph1) consists of 1 or more subgraphs (in the example above, node id=1). Each of those subgraphs contains 1 or more activities (node id=2, node id=3).

The xml for each activity might contain the following information:

  • A unique id (e.g. id="2").
  • The "kind" of the activity, e.g. <att name="_kind" value="77"/>. The value is an item from the enum ThorActivityKind in eclhelper.hpp.
  • The ECL that created the activity. E.g. <att name="ecl" value="...">
  • The identifier of the ecl definition. E.g. <att name="name" value="results"/>
  • Location (e.g. file, line number, column) of the original ECL. E.g. <att name="definition" value="workuniteg1.ecl(3,1)"/>
  • Meta information the code generator has deduced about the activity output. Examples include the record size, expected number of rows, sort order etc. E.g. <att name="recordSize" value="120"/>
  • Hints, which are used for fine control of options for a particular activity (e.g,, the number of threads to use while sorting).
  • Record counts and stats once the job has executed. (These are logically associated with the activities in the graph, but stored separately.)

Graphs also contain edges that can take one of 3 forms:

Edges within graphs

: These are used to indicate how the activities are connected. The source activity is used as the input to the target activity. These edges have the following format:

    <edge id="<source-activity-id>_<output-count>" source="<source-activity-id>" target="<target-activity-id">
+
+There is only one edge in our example workunit: \<edge id="2\_0"
+source="2" target="3"/\>.
+

Edges between graphs

: These are used to indicate direct dependencies between activities. For instance there will be an edge connecting the activity that writes a spill file to the activity that reads it. These edges have the following format:

    <edge id="<source-activity-id>_<target-activity-id>" source="<source-subgraph-id>" target="<target-subgraph-id>"
+       <att name="_sourceActivity" value="<source-activity-id>"/>
+       <att name="_targetActivity" value="<target-activity-id>"/>
+    </edge>
+
+Roxie often optimizes spilled datasets and treats these edges as
+equivalent to the edges between activities.
+

Other dependencies.

: These are similar to the edges between graphs, but they are used for values that are used within an activity. For instance one part of the graph may calculate the maximum value of a column, and another activity may filter out all records that do not match that maximum value. The format is the same as the edges between graphs except that the edge contains the following attribute:

    <att name="_dependsOn" value="1"/>
+

Each activity in a graph also has a corresponding helper class instance in the generated code. (The name of the class is "cAc" followed by the activity number, and the exported factory method is "fAc" followed by the activity number.) Each helper class implements a specialised interface (derived from IHThorArg) - the particular interface is determined by the value of the "_kind" attribute for the activity.

The contents of file rtl/include/eclhelper.hpp is key to understanding how the generated code relates to the activities. Each kind of activity requires a helper class that implements a specific interface. The helpers allow the engine to tailor the generalised activity implementation to the the particular instance - e.g. for a filter activity whether a row should be included or excluded. The appendix at the end of this document contains some further information about this file.

The classes in the generated workunits are normally derived from base implementations of those interfaces (which are implemented in rtl/include/eclhelper_base.hpp). This reduces the size of the generated code by providing default implementations for various functions.

For instance the helper for the index read (activity 2) is defined as:

cpp
struct cAc2 : public CThorIndexReadArg {
+    virtual unsigned getFormatCrc() {
+        return 470622073U;
+    }
+    virtual bool getIndexLayout(size32_t & __lenResult, void * & __result) { getLayout5(__lenResult, __result, ctx); return true; }
+    virtual IOutputMetaData * queryDiskRecordSize() { return &mx1; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void onCreate(ICodeContext * _ctx, IHThorArg *, MemoryBuffer * in) {
+        ctx = _ctx;
+        ctx->getResultString(v2,v1.refstr(),"searchname",4294967295U);
+    }
+    rtlDataAttr v1;
+    unsigned v2;
+    virtual const char * getFileName() {
+        return "names";
+    }
+    virtual void createSegmentMonitors(IIndexReadContext *irc) {
+        Owned<IStringSet> set3;
+        set3.setown(createRtlStringSet(40));
+        char v4[40];
+        rtlStrToStr(40U,v4,v2,v1.getstr());
+        if (rtlCompareStrStr(v2,v1.getstr(),40U,v4) == 0) {
+            set3->addRange(v4,v4);
+        }
+        irc->append(createKeySegmentMonitor(false, set3.getClear(), 0, 40));
+        irc->append(createWildKeySegmentMonitor(40, 80));
+    }
+    virtual size32_t transform(ARowBuilder & crSelf, const void * _left) {
+        crSelf.getSelf();
+        unsigned char * left = (unsigned char *)_left;
+        memcpy(crSelf.row() + 0U,left + 0U,120U);
+        return 120U;
+    }
+};

Some of the methods to highlight are:

a) onCreate() - common to all activities. It is called by the engine when the helper is first created, and allows the helper to cache information that does not change - in this case the name that is being searched for. b) getFileName() - determines the name of the index being read. c) createSegmentMonitors() - defines which columns to filter, and which values to match against. d) transform() - create the record to return from the activity. It controls which columns should be included from the index row in the output. (In this case all.)

Executing the graph

To execute a graph, the engine walks the activities in the graph xml and creates, in memory, a graph of implementation activities.

For each activity, the name of the helper factory is calculated from the activity number (e.g. fAc2 for activity 2). That function is imported from the loaded dll, and then called to create an instance of the generated helper class - in this case cAc2.

The engine then creates an instance of the class for implementing the activity, and passes the previously created helper object to the constructor. The engine uses the _kind attribute in the graph to determine which activity class should be used. E.g. In the example above activity 2 has a _kind of 77, which corresponds to TAKindexread. For an index-read activity roxie will create an instance of CRoxieServerIndexReadActivity. (The generated helper that is passed to the activity instance will implement IHThorIndexReadArg). The activity implementations may also extract other information from the xml for the activity - e.g. hints. Once all the activities are created the edge information is used to link inputs activities to output activities and add other dependencies.

Note: Any subgraph that is marked with <att name="rootGraph" value="1"/> is a root subgraph. An activity within a subgraph that has no outputs is called a 'sink' (and an activity without any inputs is called a 'source').

Executing a graph involves executing all the root subgraphs that it contains. All dependencies of the activities within the subgraph must be executed before a subgraph is executed. To execute a subgraph, the engine executes each of the sink activities on separate threads, and then waits for each of those threads to complete. Each sink activity lazily pulls input rows on demand from activities further up the graph, processes them and returns when complete.

(If you examine the code you will find that this is a simplification. The implementation for processing dependencies is more fine grained to ensure IF datasets, OUPUT(,UPDATE) and other ECL constructs are executed correctly.)

In our example the execution flows as follows:

  1. Only a single root subgraph, so need to execute that.
  2. The engine will execute activity 3 - the workunit-write activity (TAKworkunitwrite).
  3. The workunit-write activity will start its input.
  4. The index-read activity will call the helper functions to obtain the filename, resolve the index, and create the filter.
  5. The workunit-write activity requests a row from its input.
  6. The index-read finds the first row, and calls the helper's transform() method to create an output row.
  7. The workunit-write activity persists the row to a buffer (using the serializer provided by the IOutputMetaData interface implemented by the class mx1).
  8. Back to step 5, workunit-write reading a row from its input, until end of file is returned (notified as two consecutive NULL rows.
  9. Workunit-write commits the results and finishes.

The execution generally switches back and forth between the code in the engines, and the members of the generated helper classes.

There are some other details of query execution that are worth highlighting:

Child Queries

: Some activities perform complicated operations on child datasets of the input rows. E.g. remove all duplicate people who are marked as living at this address. This will create a "child query" in the graph - i.e. a nested graph within a subgraph, which may be executed each time a new input row is processed by the containing activity. (The graph of activities for each child query is created at the same time as the parent activity. The activity instances are reinitialised and re-executed for each input row processed by the parent activity to minimise the create-time overhead.)

Other helper functions

: The generated code contains other functions that are used to describe the meta information for the rows processed within the graph. E.g. the following class describes the output from the index read activity:

cpp
struct mi1 : public CFixedOutputMetaData {
+    inline mi1() : CFixedOutputMetaData(120) {}
+    virtual const RtlTypeInfo * queryTypeInfo() const { return &ty1; }
+} mx1;
This represents a fixed size row that occupies 120 bytes. The object
+returned by the queryTypeInfo() function provides information about
+the types of the fields:
+
cpp
const RtlStringTypeInfo ty2(0x4,40);
+const RtlFieldStrInfo rf1("name",NULL,&ty2);
+const RtlStringTypeInfo ty3(0x4,80);
+const RtlFieldStrInfo rf2("address",NULL,&ty3);
+const RtlFieldInfo * const tl4[] = { &rf1,&rf2, 0 };
+const RtlRecordTypeInfo ty1(0xd,120,tl4);
I.e. a string column, length 40 called "name", followed by a
+string column, length 80 called "address". The interface
+IOutputMetaData in eclhelper.hpp is key to understanding how the
+rows are processed.
+

Inline dataset operations

: The rule mentioned at the start - that the generated code does not contain any knowledge of how to perform a particular dataset operation - does have one notable exception. Some operations on child datasets are very simple to implement, and more efficient if they are implemented using inline C++ code. (The generated code is smaller, and they avoid the overhead of setting up a child graph.) Examples include filtering and aggregating column values from a child dataset.

The full code in the different engines is more complicated than the simplified process outlined above, especially when it comes to executing dependencies, but the broad outline is the same.

Appendix

More information on the work done in the code generator to create the workunit can be found in ecl/eclcc/DOCUMENTATION.rst.

The C++ code can be generated as a single C++ file or multiple files. The system defaults to multiple C++ files, so that they can be compiled in parallel (and to avoid problems some compilers have with very large files). When multipe C++ files are generated the metadata classes and workflow classes are generated in the main module, and the activities are generated in the files suffixed with a number. It may be easier to understand the generated code if it is in one place. In which case, compile your query with the option -fspanMultipleCpp=0. Use -fsaveCppTempFiles to ensure the C++ files are not deleted (the C++ files will appear as helpers in the workunit details).

Key types and interfaces from eclhelper.hpp

IEclProcess

: The interface that is used by the workflow engine to execute the different workflow items in the generated code.

ThorActivityKind

: This enumeration contains one entry for each activity supported by the engines.

ICodeContext

: This interface is implemented by the engine, and provides a mechanism for the generated code to call back into the engine. For example resolveChildQuery() is used to obtain a reference to a child query that can then be executed later.

IOutputMetaData

: This interface is used to describe any meta data associated with the data being processed by the queries.

IHThorArg

: The base interface for defining information about an activity. Each activity has an associated interface that is derived from this interface. E.g. each instance of the sort activity will have a helper class implementing IHThorSortArg in the generated query. There is normally a corresponding base class for each interface in eclhelper_base.hpp that is used by the generated code e.g. CThorSortArg.

ARowBuilder

: This abstract base class is used by the transform functions to reserve memory for the rows that are created.

IEngineRowAllocator

: Used by the generated code to allocate rows and rowsets. Can also be used to release rows (or call the global function rtlReleaseRow()).

IGlobalCodeContext

: Provides access to functions that cannot be called inside a graph - i.e. can only be called from the global workflow code. Most functions are related to the internal implementation of particular workflow item types (e.g. persists).

Glossary

activity

: An activity is the basic unit of dataset processing implemented by the engines. Each activity corresponds to a node in the thor execution graph. Instances of the activities are connnected together to create the graph.

dll

: A dynamically loaded library. These correspond to shared objects in Linux (extension '.so'), dynamic libraries in Max OS X ('.dylib'), and dynamic link libraries in windows ('.dll').

superfile

: A composite file which allows a collection of files to be treated as a single compound file.

?What else should go here?

Full text of the workunit XML

The XML for a workunit can be viewed on the XML tag in eclwatch, or generated by compiling the ECL using the -wu option with eclcc. Alternatively eclcc -b -S can be used to generate the XML and the C++ at the same time (the output filenames are derived from the input name).

xml
<W_LOCAL buildVersion="internal_5.3.0-closedown0"
+    cloneable="1"
+    codeVersion="157"
+    eclVersion="5.3.0"
+    hash="2344844820"
+    state="completed"
+    xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
+    <Debug>
+        <addtimingtoworkunit>0</addtimingtoworkunit>
+        <debugnlp>1</debugnlp>
+        <expandpersistinputdependencies>1</expandpersistinputdependencies>
+        <forcegenerate>1</forcegenerate>
+        <noterecordsizeingraph>1</noterecordsizeingraph>
+        <regressiontest>1</regressiontest>
+        <showmetaingraph>1</showmetaingraph>
+        <showrecordcountingraph>1</showrecordcountingraph>
+        <spanmultiplecpp>0</spanmultiplecpp>
+        <targetclustertype>hthor</targetclustertype>
+    </Debug>
+    <Graphs>
+        <Graph name="graph1" type="activities">
+            <xgmml>
+                <graph wfid="2">
+                    <node id="1">
+                        <att>
+                            <graph>
+                                <att name="rootGraph" value="1" />
+                                <edge id="2_0" source="2" target="3" />
+                                <node id="2" label="Index Read&#10;&apos;names&apos;">
+                                    <att name="definition" value="workuniteg1.ecl(3,1)" />
+                                    <att name="name" value="results" />
+                                    <att name="_kind" value="77" />
+                                    <att name="ecl"
+                                        value="INDEX({ string40 name, string80 address }, &apos;names&apos;, fileposition(false));&#10;FILTER(KEYED(name = STORED(&apos;searchname&apos;)));&#10;" />
+                                    <att name="recordSize" value="120" />
+                                    <att name="predictedCount" value="0..?[disk]" />
+                                    <att name="_fileName" value="names" />
+                                </node>
+                                <node id="3" label="Output&#10;Result #1">
+                                    <att name="definition" value="workuniteg1.ecl(4,1)" />
+                                    <att name="_kind" value="16" />
+                                    <att name="ecl" value="OUTPUT(..., workunit);&#10;" />
+                                    <att name="recordSize" value="120" />
+                                </node>
+                            </graph>
+                        </att>
+                    </node>
+                </graph>
+            </xgmml>
+        </Graph>
+    </Graphs>
+    <Query fetchEntire="1">
+        <Associated>
+            <File desc="workuniteg1.ecl.cpp"
+                filename="c:\regressout\workuniteg1.ecl.cpp"
+                ip="10.121.159.73"
+                type="cpp" />
+        </Associated>
+    </Query>
+    <Results>
+        <Result isScalar="0"
+            name="Result 1"
+            recordSizeEntry="mf1"
+            rowLimit="-1"
+            sequence="0">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                name&#xe000;&#xe004;(&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;address&#xe000;&#xe004;P&#xe000;&#xe000;&#xe000;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;%&#xe000;&#xe000;&#xe000;{
+                string40 name, string80 address };&#10; </SchemaRaw>
+        </Result>
+        <Result name="Result 2" sequence="1">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                Result_2&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;
+            </SchemaRaw>
+        </Result>
+    </Results>
+    <Statistics>
+        <Statistic c="eclcc"
+            count="1"
+            creator="eclcc"
+            kind="SizePeakMemory"
+            s="compile"
+            scope=">compile"
+            ts="1428933081084000"
+            unit="sz"
+            value="27885568" />
+    </Statistics>
+    <Variables>
+        <Variable name="searchname">
+            <SchemaRaw xsi:type="SOAP-ENC:base64">
+                searchname&#xe000;&#xe004;&#241;&#255;&#255;&#255;&#xe001;ascii&#xe000;&#xe001;ascii&#xe000;&#xe000;&#xe018;&#xe000;&#xe000;&#xe000;&#xe000;
+            </SchemaRaw>
+        </Variable>
+    </Variables>
+    <Workflow>
+        <Item mode="normal"
+            state="null"
+            type="normal"
+            wfid="1" />
+        <Item mode="normal"
+            state="reqd"
+            type="normal"
+            wfid="2">
+            <Dependency wfid="1" />
+            <Schedule />
+        </Item>
+    </Workflow>
+</W_LOCAL>

Full contents of the generated C++ (as a single file)

cpp
/* Template for generating thor/hthor/roxie output */
+#include "eclinclude4.hpp"
+#include "eclrtl.hpp"
+#include "rtlkey.hpp"
+
+extern RTL_API void rtlStrToStr(size32_t lenTgt,void * tgt,size32_t lenSrc,const void * src);
+extern RTL_API int rtlCompareStrStr(size32_t lenL,const char * l,size32_t lenR,const char * r);
+
+
+const RtlStringTypeInfo ty2(0x4,40);
+const RtlFieldStrInfo rf1("name",NULL,&ty2);
+const RtlStringTypeInfo ty3(0x4,80);
+const RtlFieldStrInfo rf2("address",NULL,&ty3);
+const RtlFieldInfo * const tl4[] = { &rf1,&rf2, 0 };
+const RtlRecordTypeInfo ty1(0xd,120,tl4);
+void getLayout5(size32_t & __lenResult, void * & __result, IResourceContext * ctx) {
+    rtlStrToDataX(__lenResult,__result,87U,"\000R\000\000\000\001x\000\000\000\002\000\000\000\003\004\000\000\000name\004(\000\000\000\001ascii\000\001ascii\000\000\000\000\000\000\003\007\000\000\000address\004P\000\000\000\001ascii\000\001ascii\000\000\000\000\000\000\002\000\000\000");
+}
+struct mi1 : public CFixedOutputMetaData {
+    inline mi1() : CFixedOutputMetaData(120) {}
+    virtual const RtlTypeInfo * queryTypeInfo() const { return &ty1; }
+} mx1;
+extern "C" ECL_API IOutputMetaData * mf1() { mx1.Link(); return &mx1; }
+
+struct cAc2 : public CThorIndexReadArg {
+    virtual unsigned getFormatCrc() {
+        return 470622073U;
+    }
+    virtual bool getIndexLayout(size32_t & __lenResult, void * & __result) { getLayout5(__lenResult, __result, ctx); return true; }
+    virtual IOutputMetaData * queryDiskRecordSize() { return &mx1; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void onCreate(ICodeContext * _ctx, IHThorArg *, MemoryBuffer * in) {
+        ctx = _ctx;
+        ctx->getResultString(v2,v1.refstr(),"searchname",4294967295U);
+    }
+    rtlDataAttr v1;
+    unsigned v2;
+    virtual const char * getFileName() {
+        return "names";
+    }
+    virtual void createSegmentMonitors(IIndexReadContext *irc) {
+        Owned<IStringSet> set3;
+        set3.setown(createRtlStringSet(40));
+        char v4[40];
+        rtlStrToStr(40U,v4,v2,v1.getstr());
+        if (rtlCompareStrStr(v2,v1.getstr(),40U,v4) == 0) {
+            set3->addRange(v4,v4);
+        }
+        irc->append(createKeySegmentMonitor(false, set3.getClear(), 0, 40));
+        irc->append(createWildKeySegmentMonitor(40, 80));
+    }
+    virtual size32_t transform(ARowBuilder & crSelf, const void * _left) {
+        crSelf.getSelf();
+        unsigned char * left = (unsigned char *)_left;
+        memcpy(crSelf.row() + 0U,left + 0U,120U);
+        return 120U;
+    }
+};
+extern "C" ECL_API IHThorArg * fAc2() { return new cAc2; }
+struct cAc3 : public CThorWorkUnitWriteArg {
+    virtual int getSequence() { return 0; }
+    virtual IOutputMetaData * queryOutputMeta() { return &mx1; }
+    virtual void serializeXml(const byte * self, IXmlWriter & out) {
+        mx1.toXML(self, out);
+    }
+};
+extern "C" ECL_API IHThorArg * fAc3() { return new cAc3; }
+
+
+struct MyEclProcess : public EclProcess {
+    virtual unsigned getActivityVersion() const { return ACTIVITY_INTERFACE_VERSION; }
+    virtual int perform(IGlobalCodeContext * gctx, unsigned wfid) {
+        ICodeContext * ctx;
+        ctx = gctx->queryCodeContext();
+        switch (wfid) {
+            case 1U:
+                if (!gctx->isResult("searchname",4294967295U)) {
+                    ctx->setResultString("searchname",4294967295U,5U,"Smith");
+                }
+                break;
+            case 2U: {
+                ctx->executeGraph("graph1",false,0,NULL);
+                ctx->setResultString(0,1U,5U,"Done!");
+            }
+            break;
+        }
+        return 2U;
+    }
+};
+
+
+extern "C" ECL_API IEclProcess* createProcess()
+{
+
+    return new MyEclProcess;
+}

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/docs/ContributeDocs.html b/devdoc/docs/ContributeDocs.html new file mode 100644 index 00000000000..2ba692d0650 --- /dev/null +++ b/devdoc/docs/ContributeDocs.html @@ -0,0 +1,24 @@ + + + + + + Contributing Documentation to the HPCC Systems Platform Project | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Contributing Documentation to the HPCC Systems Platform Project

This document is intended for anyone that wants to contribute documentation to our project. The first audience is platform developers, so we can streamline the process of documenting new features. However, these guidelines apply to anyone who wants to contribute to any of our documentation (Language Reference, Programmer’s Guide, etc.).

This set of guidelines should help you understand the information needed to create sufficient documentation for any new feature you add. The good news is that we are all here to help you and support your writing efforts. We will help by advising you along the way, and by reviewing and editing your submissions.

Documenting a New Software Feature--Required and Optional Components

When you create a new feature or function, clear documentation is crucial for both internal teams and external users. You worked hard on the feature, so it deserves proper notice and usage.

Contributions to the platform are always welcome, and we strongly encourage developers and users to contribute documentation.

You can contribute on many levels:

  1. Developer Notes

  2. End user “Readmes” in the form of MD files in the GitHub repository

  3. Blogs

  4. Formal documentation

Regardless of the form you are planning to deliver, here are the required and optional components to include in a document.

Tip: VS Code is very good at editing MD files. There is a built-in Preview panel available to be able to see the rendered form.

In addition, GitHub Copilot is MD-aware and can help you write and format. For example, you can ask the Copilot, “How can I align the content within the cells of my Markdown table?” GitHub copilot will show you the alignment options.

Required Components:

  1. Overview

    • What it is: Briefly describe the feature's purpose and the problem it solves.

    • Why it matters: Explain the value proposition for users and the overall impact on the software.

    • Target audience: Specify who this feature is designed for (for example, all users or specific user roles).

    • Use Cases: Provide concrete examples of how a user might leverage this feature in a real-world scenario.

  2. Installation and Configuration: Details on how to install and basic setup for use, if needed. This must include any system requirements or dependencies.

  3. User Guide / Functionality

    • How it works: Provide a task-oriented, step-by-step guide for using the feature. If possible, include screenshots for visual learners.

    • Tips, Tricks, and Techniques: Explain any shortcuts or clever uses of the feature that may be non-obvious.

    • Inputs and Outputs: Detail the information users need to provide to the feature and the format of the results.

    • Error Handling: Explain what happens if users encounter errors and how to troubleshoot common issues.

  4. Limitations and Considerations:

    • Limitations: Acknowledge any restrictions or boundaries associated with the feature's functionality.

Optional Components:

  1. Advanced Usage

    • Detailed configuration options: If the feature offers advanced settings or customizations, provide in-depth instructions for experienced users. This is similar to the way some options command line program's usage are only displayed using the verbose option.
  2. API Reference (for technical audiences)

    • Technical specifications: For features with an API component, include detailed API reference documentation for developers integrating it into their applications.
  3. FAQs

    • Frequently Asked Questions: Address any commonly anticipated user questions about the feature to pre-empt confusion.
  4. Additional Resources

    • Links to related documentation: Include links to relevant documentation for features that interact with this new addition.

    • Tutorials: Consider creating tutorials for a more interactive learning experience.

    • Videos: Consider requesting that a video be made to provide a more interactive visual learning experience. You should provide a simple script or outline of what should be shown in the video.

General Tips

  • Target your audience: Tailor the level of detail and technical jargon you use based on whether the documentation is for developers or end-users.

  • Clarity and Conciseness: Use clear, concise language and maintain a consistent structure for easy navigation. Always use present tense, active voice. Remember, you’re writing for users and programmers, not academics, so keep it simple and straightforward. See the HPCC Style Guide for additional guidance.

  • Visual Aids: Screenshots, diagrams, and flowcharts can significantly enhance understanding. A picture can communicate instantly what a thousand words cannot.

  • Maintain and Update: Regularly review and update documentation as the feature evolves or based on user feedback.

By following these guidelines and including the required and optional components, you can create comprehensive documentation that empowers users and streamlines the adoption of your new software feature.

Who should write it?

The boundary between a developer's responsibilities and the documentation team’s responsibility is not cast in stone. However, there are some guidelines that can help you decide what your responsibility is. Here are some examples:

Changing the default value of a configuration setting

This typically needs a simple one or two word change in the area of the documentation where that setting is documented. However, the change could impact existing deployments or existing code and therefore it might also require a short write-up for the Red Book and/or Release Announcement. If the setting is used by both bare-metal and containerized, you should provide information about how the new setting is used in each of those deployments.

Adding or modifying a Language keyword, Standard Library function, or command line tool action

This needs some changes to existing documentation so the best way to provide the information is in a documentation Jira issue. If it s a new keyword, function, or action, a brief overview should be included. For a Standard Library function, the developer should update the Javadoc comment in the appropriate ECL file. For a command line tool change, the developer should update the Usage section of the code.

Adding a new feature that requires an overview.

This is a candidate for either an MD file, a blog, or both. Since there should have been some sort of design specification document, that could easily be repurposed as a good start for this.

A feature/function that is only used internally to the system

Since this is information that is probably only of interest to other developers, a write-up in the form of an MD file in the repo is the best approach. If it affects end-users or operations, then a more formal document or a blog might be a good idea. If it affects existing deployments or existing code, then a Red Book notice might also be needed.

Extending the tests in the regression suite

New tests are frequently added and the regression suite readme files should be updated at the same time. If the tests are noteworthy, we could add a mention in the Platform Release Notes.

Placement

In general, it makes sense to keep simple documentation near the code. For example, a document about ECL Agent should go in the ECLAgent folder. However, there are times where that is either not possible or a document may cover more than one component. In those cases, there are a few options as shown below.

Other Folders

devdoc: This is a general folder for any developer document.

devdoc/docs: This is a folder for documents about documentation.

devdoc/userdoc: This is a collection of docs targeted toward the end-user rather than developers.

This is primarily for informal documents aimed at end-users. This info can and should be incorporated into the formal docs. devdoc/userdoc/troubleshoot: Information related to troubleshooting particular components

devdoc/userdoc/azure: Useful information about Azure Cloud portal and cli

devdoc/userdoc/roxie: Useful information for running roxie

devdoc/userdoc/thor: Useful information for running thor

devdoc/userdoc/blogs: COMING SOON: Location and storage of original text for blogs. It also has docs with guidelines and instructions on writing Blogs

Pull Requests

You can include your documentation with your code in a Pull Request or create a separate Jira and Pull Request for the documentation. This depends on the size of the code and doc. For a large project or change, a separate Pull request for the documentation is better. This might allow the code change to be merged faster.

Documentation Jira Issues

For minor code changes, for example the addition of a parameter to an existing ECL keyword, you can request a documentation change in a Jira issue. You should provide sufficient details in the Jira.

For example, If you add an optional parameter named Foo, you should provide details about what values can be passed in through the Foo parameter and what those values mean. You should also provide the default value used if the parameter is omitted.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/docs/DocTemplate.html b/devdoc/docs/DocTemplate.html new file mode 100644 index 00000000000..678d2b6fc1a --- /dev/null +++ b/devdoc/docs/DocTemplate.html @@ -0,0 +1,24 @@ + + + + + + Feature Documentation Template | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Feature Documentation Template

Use this template to create new documentation for a feature, function, or process. Not every feature will require all six sections. Delete the ones that are not applicaple.

Overview

[In this section, provide a brief introduction to the new feature, highlighting its purpose and benefits.]

Setup & Configuration

[If applicable, explain the steps required to set up and configure the new feature, including any dependencies or prerequisites.]

User Guide

[Provide detailed instructions on how to use the new feature, including its functionality, options, and any relevant examples.]

API/CLI/Parameter Reference

[If applicable, document the references for the new feature, including any classes, methods, or parameters.]

Tutorial

[Offer a step-by-step tutorial on how to implement or utilize the new feature, with code snippets or screenshots if appropriate.]

Troubleshooting

[Address common issues or errors that users may encounter while using the new feature, along with possible solutions or workarounds.]

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/docs/HPCCStyleGuide.html b/devdoc/docs/HPCCStyleGuide.html new file mode 100644 index 00000000000..977aa04359c --- /dev/null +++ b/devdoc/docs/HPCCStyleGuide.html @@ -0,0 +1,24 @@ + + + + + + HPCC Writing Style Guide | HPCC Platform + + + + + + + + + + + + + +
Skip to content

HPCC Writing Style Guide

This section covers the best practice information for writing and contributing to the HPCC Systems® Platform documentation.

General

We strive to maintain a consistent voice. These guidelines can help your writing match that voice.

  • Use present tense, active voice. Documentation should be you speaking directly to the reader. Simply tell them what to do.

    This example sentence: "The user selects the file menu." is passive voice. You wouldn’t say it that way in a conversation.

    You should use active voice wording, such as: "Select the file menu".

    Similarly, instructions like these are active voice: "Press the button" or "Submit the file".

    Documentation is you instructing the user. Just tell them what to do.

  • Be Brief and keep it simple. Be efficient with your words. Keep sentences short and concise. Keep paragraphs short as well. Use just a few sentences per paragraph. Use simple words wherever possible and try to avoid lengthy explanations.

  • Consistency: Be consistent. Use the same voice across all documents. Use the same term, use the same spelling, punctuation, etc. Follow the conventions in this guide.

  • When writing formal documentation that is more than a new feature announcement, do not refer to a new feature, or coming features as such, this does not hold up well over time.

    Do not refer to how things were done in the past. For example, "in the past we had to do X-Y-Z steps and now we no longer have to. This ‘new feature’ can do it in one step, Z". This only adds potential confusion. Just instruct on exactly what needs to be done now, using words efficiently as possible, so for this example just say “perform step Z”

Terms

There are many terms specific to HPCC Systems® and the HPCC Systems platform. Use the following style guide for word usage and capitalization guidelines to use when referring to system components or other HPCC-specific entities. Maintain consistent usage throughout all docs.

HPCC Systems®

Officially and legally the organizaion's name is HPCC Systems® and it is a registered trademark.

You should always refer to the platform as the HPCC Systems® platform and the registration mark ® should appear in the first and most prominent mention of the name.

While it is acceptable to use the ® anywhere in a document, it is required to be used in the first and most prominent mention - so the average reader will be aware. Any usage after that first and most prominent is optional.

Components and Tools

  • HPCC Systems Platform

  • Dali

  • Sasha

  • Thor

  • hThor

  • ROXIE

  • DFU Server (Distributed File Utility Server)

  • ESP Server (Enterprise Server Platform)

  • ESP Services

  • WsECL

  • ECL Watch

  • ECL Server

  • ECLCC Server

  • ECL Agent

  • ECL IDE

  • ECL Plug-in for Eclipse

  • ECL Playground

  • LDAP

  • dafilesrv

  • VS Code (no hyphen)

  • ECL Language Extension for VS Code

  • Configuration Manager (not ConfigMgr or ConfigManager)

Note: when referring to the startup command, use configmgr (always lowercase)

Other Terms

  • HPCCSystems.com or http://HPCCSystems.com (do not include the www portion)

  • ECL (Enterprise Control Language)

  • ECL command-line interface

Note: when referring to the ECL command-line tool command, ecl is lowercase

  • DFU Plus command-line interface
  • cloud-native (always hyphenated when used as an adjective)
  • DFU Workunits

  • ECL Workunits

  • Workunit

  • WUID

  • HPCC Systems®

  • multi-node

  • Superfiles, subfiles

  • package map

  • The username is the (usually unique) thing you type in with your password, for example: bobsmith66.

  • The user name is the name of the user, the user's real-life name, for example: Bob Smith.

Common Documentation terms

Use the following conventions for these commonly used terms:

  • right-click

  • double-click

  • drag-and-drop

  • click-and-drag

  • plug-in

  • drop list

  • bare metal (no hyphen)

  • blue/green (not blue-green)

  • Common Vulnerabilities and Exposures (CVEs)

Usage Instructions

  • You click a link.

  • You select a tab.

  • You press a button.

  • You check a (check)box.

Word Choices

Write up vs Write-up

Hyphenated when used as a noun. No Hyphen when used as a verb phrase.

Examples:

  • Did you read the write-up?

  • Would you write up the steps to reproduce?

Assure vs ensure vs insure

To “assure” a person of something is to make him or her confident of it.

According to the Associated Press style, to “ensure” that something happens is to make certain that it does, and to “insure” is to issue an insurance policy.

Other authorities, however, consider “ensure” and “insure” interchangeable. We prefer ensure when it is not talking about insurance.

If you have any questions, feel free to contact us at docfeedback@hpccsystems.com

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/hpcc-icon.png b/devdoc/hpcc-icon.png new file mode 100644 index 00000000000..9ab15960122 Binary files /dev/null and b/devdoc/hpcc-icon.png differ diff --git a/devdoc/hpccsystems.png b/devdoc/hpccsystems.png new file mode 100644 index 00000000000..2fd32ce9736 Binary files /dev/null and b/devdoc/hpccsystems.png differ diff --git a/devdoc/hpccsystemsdark.png b/devdoc/hpccsystemsdark.png new file mode 100644 index 00000000000..642e1ad6670 Binary files /dev/null and b/devdoc/hpccsystemsdark.png differ diff --git a/devdoc/newActivity.html b/devdoc/newActivity.html new file mode 100644 index 00000000000..f49fcb0c127 --- /dev/null +++ b/devdoc/newActivity.html @@ -0,0 +1,34 @@ + + + + + + Quantile 1 - What is it? | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Quantile 1 - What is it?

This series of blog posts started life as a series of walk-throughs and brainstorming sessions at a team offsite. This series will look at adding a new activity to the system. The idea is to give an walk through of the work involved, to highlight the different areas that need changing, and hopefully encourage others to add their own activities. In parallel with the description in this blog there is a series of commits to the github repository that correspond to the different stages in adding the activity. Once the blog is completed, the text will also be checked into the source control tree for future reference.

The new activity is going to be a QUANTILE activity, which can be used to find the records that split a dataset into equal sized blocks. Two common uses are to find the median of a set of data (split into 2) or percentiles (split into 100). It can also be used to split a dataset for distribution across the nodes in a system. One hope is that the classes used to implement quantile in Thor can also be used to improve the performance of the global sort operation.

It may seem fatuous, but the first task in adding any activity to the system is to work out what that activity is going to do! You can approach this in an iterative manner - starting with a minimal set of functionality and adding options as you think of them - or start with a more complete initial design. We have used both approaches in the past to add capabilities to the HPCC system, but on this occasion we will be starting from a more complete design - the conclusion of our initial design discussion:

"What are the inputs, options and capabilities that might be useful in a QUANTILE activity?"

The discussion produced the following items:

  • Which dataset is being processed?

    This is always required and should be the first argument to the activity.

  • How many parts to split the dataset into?

    This is always required, so it should be the next argument to the activity.

  • Which fields are being used to order (and split) the dataset?

    Again this is always required, so the list of fields should follow the number of partitions.

  • Which fields are returned?

    Normally the input row, but often it would be useful for the output to include details of which quantile a row corresponds to. To allow this an optional transform could be passed the input row as LEFT and the quantile number as COUNTER.

  • How about first and last rows in the dataset?

    Sometimes it is also useful to know the first and last rows. Add flags to allow them to be optionally returned.

  • How do you cope with too few input rows (including an empty input)?

    After some discussion we decided that QUANTILE should always return the number of parts requested. If there were fewer items in the input they would be duplicated as appropriate. We should provide a DEDUP flag for the situations when that is not desired. If there is an empty dataset as input then the default (blank) row will be created.

  • Should all rows have the same weighting?

    Generally you want the same weighting for each row. However, if you are using QUANTILE to split your dataset, and the cost of the next operation depends on some feature of the row (e.g., the frequency of the firstname) then you may want to weight the rows differently.

  • What if we are only interested in the 5th and 95th centiles?

    We could optionally allow a set of values to be selected from the results.

There were also some implementation details concluded from the discussions:

  • How accurate should the results be?

    The simplest implementation of QUANTILE (sort and then select the correct rows) will always produce accurate results. However, there may be some implementations that can produce an approximate answer more quickly. Therefore we could add a SKEW attribute to allow early termination.

  • Does the implementation need to be stable?

    In other words, if there are rows with identical values for the ordering fields, but other fields not part of the ordering with different values, does it matter which of those rows are returned? Does the relative order within those matching rows matter?

    The general principle in the HPCC system is that sort operations should be stable, and that where possible activities return consistent, reproducible results. However, that often has a cost - either in performance or memory consumption. The design discussion highlighted the fact that if all the fields from the row are included in the sort order then the relative order does not matter because the duplicate rows will be indistinguishable. (This is also true for sorts, and following the discussion an optimization was added to 5.2 to take advantage of this.) For the QUANTILE activity we will add an ECL flag, but the code generator should also aim to spot this automatically.

  • Returning counts of the numbers in each quantile might be interesting.

    This has little value when the results are exact, but may be more useful when a SKEW is specified to allow an approximate answer, or if a dataset might have a vast numbers of duplicates. It is possibly something to add to a future version of the activity. For an approximate answer, calculating the counts is likely to add an additional cost to the implementation, so the target engine should be informed if this is required.

  • Is the output always sorted by the partition fields?

    If this naturally falls out of the implementations then it would be worth including it in the specification. Initially we will assume not, but will revisit after it has been implemented.

After all the discussions we arrived at the following syntax:

QUANTILE(<dataset>, <number-of-ranges>, { sort-order } [, <transform>(LEFT, COUNTER)]
+        [,FIRST][,LAST][,SKEW(<n>)][,UNSTABLE][,SCORE(<score>)][,RANGE(set)][,DEDUP][,LOCAL]
+
+FIRST - Match the first row in the input dataset (as quantile 0)
+LAST -  Match the last row in the input dataset (as quantile <n>)
+SKEW -  The maximum deviation from the correct results allowed.  Defaults to 0.
+UNSTABLE - Is the order of the original input values unimportant?
+SCORE - What weighting should be applied for each row.  Defaults to 1.
+RANGE - Which quantiles should actually be returned.  (Defaults to ALL).
+DEDUP - Avoid returning a match for an input row more than once.
+

We also summarised a few implementation details:

  • The activity needs to be available in GLOBAL, LOCAL and GROUPED variants.
  • The code generator should derive UNSTABLE if no non-sort fields are returned.
  • Flags to indicate if a score/range is required.
  • Flag to indicate if a transform is required.

Finally, deciding on the name of the activity took almost as long as designing it!

The end result of this process was summarised in a JIRA issue: https://track.hpccsystems.com/browse/HPCC-12267, which contains details of the desired syntax and semantics. It also contains some details of the next blog topic - test cases.

Incidentally, a question that arose from of the design discussion was "What ECL can we use if we want to annotate a dataset with partition points?". Ideally the user needs a join activity which walks through a table of rows, and matches against the first row that contains key values less than or equal to the values in the search row. There are other situations where that operation would also be useful. Our conclusion was that the system does not have a simple way to achieve that, and that it was a deficiency in the current system, so another JIRA was created (see https://track.hpccsystems.com/browse/HPCC-13016). This is often how the design discussions proceed, with discussions in one area leading to new ideas in another. Similarly we concluded it would be useful to distribute rows in a dataset based on a partition (see https://track.hpccsystems.com/browse/HPCC-13260).

Quantile 2 - Test cases

When adding new features to the system, or changing the code generator, the first step is often to write some ECL test cases. They have proved very useful for several reasons:

  • Developing the test cases can help clarify issues, and other details that the implementation needs to take into account. (E.g., what happens if the input dataset is empty?)
  • They provide something concrete to aim towards when implementing the feature.
  • They provide a set of milestones to show progress.
  • They can be used to check the implementation on the different engines.

As part of the design discussion we also started to create a list of useful test cases (they follow below in the order they were discussed). The tests perform varying functions. Some of the tests are checking that the core functionality works correctly, while others check unusual situations and that strange boundary cases are covered. The tests are not exhaustive, but they are a good starting point and new tests can be added as the implementation progresses.

The following is the list of tests that should be created as part of implementing this activity:

  1. Compare with values extracted from a SORT. Useful to check the implementation, but also to ensure we clearly define which results we are expecting.
  2. QUANTILE with a number-of-ranges = 1, 0, and a very large number. Should also test the number of ranges can be dynamic as well as a constant.
  3. Empty dataset as input.
  4. All input entries are duplicates.
  5. Dataset smaller than number of ranges.
  6. Input sorted and reverse sorted.
  7. Normal data with small number of entries.
  8. Duplicates in the input dataset that cause empty ranges.
  9. Random distribution of numbers without duplicates.
  10. Local and grouped cases.
  11. SKEW that fails.
  12. Test scoring functions.
  13. Testing different skews that work on the same dataset.
  14. An example that uses all the keywords.
  15. Examples that do and do not have extra fields not included in the sort order. (Check that the unstable flag is correctly deduced.)
  16. Globally partitioned already (e.g., globally sorted). All partition points on a single node.
  17. Apply quantile to a dataset, and also to the same dataset that has been reordered/distributed. Check the resulting quantiles are the same.
  18. Calculate just the 5 and 95 centiles from a dataset.
  19. Check a non constant number of splits (and also in a child query where it depends on the parent row).
  20. A transform that does something interesting to the sort order. (Check any order is tracked correctly.)
  21. Check the counts are correct for grouped and local operations.
  22. Call in a child query with options that depend on the parent row (e.g., num partitions).
  23. Split points that fall in the middle of two items.
  24. No input rows and DEDUP attribute specified.

Ideally any test cases for features should be included in the runtime regression suite, which is found in the testing/regress directory in the github repository. Tests that check invalid syntax should go in the compiler regression suite (ecl/regress). Commit https://github.com/ghalliday/HPCC-Platform/commit/d75e6b40e3503f851265670a27889d8adc73f645 contains the test cases so far. Note, the test examples in that commit do not yet cover all the cases above. Before the final pull request for the feature is merged the list above should be revisited and the test suite extended to include any missing tests.

In practice it may be easier to write the test cases in parallel with implementing the parser -since that allows you to check their syntax. Some of the examples in the commit were created before work was started on the parser, others during, and some while implementing the feature itself.

Quantile 3 - The parser

The first stage in implementing QUANTILE will be to add it to the parser. This can sometimes highlight issues with the syntax and cause revisions to the design. In this case there were two technical issues integrating the syntax into the grammar. (If you are not interested in shift/reduce conflicts you may want to skip a few paragraphs and jump to the walkthrough of the changes.)

Originally, the optional transform was specified inside an attribute, e.g., something like OUTPUT(transform). However, this was not very consistent with the way that other transforms were implemented, so the syntax was updated so it became an optional transform following the partition field list.

When the syntax was added to the grammar we hit another problem: Currently, a single production (sortList) in the grammar is used for matching sort orders. As well as accepting fields from a dataset the sort order production has been extended to accept any named attribute that can follow a sort order (e.g., LOCAL). This is because (with one token lookahead) it is ambiguous where the sort order finishes and the list of attributes begins. Trying to include transforms in those productions revealed other problems:

  • If a production has a sortList followed by a transform (or attribute) then it introduces a shift/reduce error on ','. To avoid the ambiguity all trailing attributes or values need to be included in the sortList.
  • Including a transform production in the sortList elements causes problems with other transform disambiguation (e.g., DATASET[x] and AGGREGATE).
  • We could require an attribute around the transform e.g., OUTPUT(transform), but that does not really fit in with other activities in the language.
  • We could change the parameter order, e.g., move the transform earlier, but that would make the syntax counter-intuitive.
  • We could require { } around the list - but this is inconsistent with some of the other sort orders.

In order to make some progress I elected to choose the last option and require the sort order to be included in curly braces. There are already a couple of activities - subsort and a form of atmost that similarly require them (and if redesigning ECL from scratch I would be tempted to require them everywhere). The final syntax is something that will need further discussion as part of the review of the pull request though, and may need to be revisited.

Having decided how to solve the ambiguities in the grammar, the following is a walkthrough of the changes that were made as part of commit https://github.com/ghalliday/HPCC-Platform/commit/3d623d1c6cd151a0a5608aa20ae4739a008f6e44:

  • no_quantile in hqlexpr.hpp

    The ECL query is represented by a graph of "expression" nodes - each has a "kind" that comes from the enumeration _node_operator. The first requirement is to add a new enumeration to represent the new activity - in this case we elected to reuse an unused placeholder. (These placeholders correspond to some old operators that are no longer supported. They have not been removed because the other elements in the enumeration need to keep the same values since they are used for calculating derived persistent values e.g., the hashes for persists.)

  • New attribute names in hqlatoms.

    The quantile activity introduces some new attribute names that have not been used before. All names are represented in an atom table, so the code in hqlatoms.hpp/cpp is updated to define the new atoms.

  • Properties of no_quantile

    There are various places that need to be updated to allow the system to know about the properties of the new operator:

    • hqlattr

      This contains code to calculate derived attributes. The first entry in the case statement is currently unused (the function should be removed). The second, inside calcRowInformation(), is used to predict how many rows are generated by this activity. This information is percolated through the graph and is used for optimizations, and input counts can be used to select the best implementation for a particular activity.

    • hqlexpr

      Most changes are relatively simple including the text for the operator, whether it is constant, and the number of dataset arguments it has. One key function is getChildDatasetType() that indicates the kind of dataset arguments the operator has, which in turn controls how LEFT/RIGHT are interpreted. In this case some of the activity arguments (e.g., the number of quantiles) implicitly use fields within the parent dataset, and the transform uses LEFT, so the operator returns childdataset_datasetleft.

    • hqlir

      This entry is used for generating an intermediate representation of the graph. This can be useful for debugging issues. (Running eclcc with the logging options "--logfile xxx" and "--logdetail 999" will include details of the expression tree at each point in the code generation process in the log file. Also defining -ftraceIR will output the graphs in the IR format.)

    • hqlfold

      This is the constant folder. At the moment the only change is to ensure that fields that are assigned constants within the transform are processed correctly. Future work could add code to optimize quantile applied to an empty dataset, or selecting 1 division.

    • hqlmeta

      Similar to the functions in hqlattr that calculate derived attributes, these functions are used to calculate how the rows coming out of an activity are sorted, grouped and distributed. It is vital to only preserve information that is guaranteed to be true - otherwise invalid optimizations might be performed on the rest of the expression tree.

    • reservedwords.cpp

      A new entry indicating which category the keyword belongs to.

Finally we have the changes to the parser to recognise the new syntax:

  • hqllex.l

    This file contains the lexer that breaks the ecl file into tokens. There are two new tokens - QUANTILE and SCORE.

  • Hqlgram.y

    This file contains the grammar that matches the language. There are two productions - one that matches the version of QUANTILE with a transform and one without. (Two productions are used instead of an optional transform to avoid shift/reduce errors.)

  • hqlgram2.cpp

    This contains the bulk of the code that is executed by the productions in the grammar. Changes here include new entries added to a case statement to get the text for the new tokens, and a new entry in the simplify() call. This helps reduce the number of valid tokens that could follow when reporting a syntax error.

Looking back over those changes, one reflection is that there are lots of different places that need to be changed. How does a programmer know which functions need to change, and what happens if some are missed? In this example, the locations were found by searching for an activity with a similar syntax e.g., no_soapcall_ds or no_normalize.

It is too easy to miss something, especially for somebody new to the code - although if you do then you will trigger a runtime internal error. It would be much better if the code was refactored so that the bulk of the changes were in one place. (See JIRA https://track.hpccsystems.com/browse/HPCC-13434 that has been added to track improvement of the situation.)

With these changes implemented the examples from the previous pull request now syntax check. The next stage in the process involves thinking through the details of how the activity will be implemented.

Quantile 4 - The engine interface.

The next stage in adding a new activity to the system is to define the interface between the generated code and the engines. The important file for this stage is rtl/include/eclhelper.hpp, which contains the interfaces between the engines and the generated code. These interfaces define the information required by the engines to customize each of the different activities. The changes that define the interface for quantile are found in commit https://github.com/ghalliday/HPCC-Platform/commit/06534d8e9962637fe9a5188d1cc4ab32c3925010.

Adding a quantile activity involves the following changes:

  • ThorActivityKind - TAKquantile

    Each activity that the engines support has an entry in this enumeration. This value is stored in the graph as the _kind attribute of the node.

  • ActivityInterfaceEnum - TAIquantilearg_1

    This enumeration in combination with the selectInterface() member of IHThorArg provides a mechanism for helper interfaces to be extended while preserving backwards compatibility with older workunits. The mechanism is rarely used (but valuable when it is), and adding a new activity only requires a single new entry.

  • IHThorArg

    This is the base interface that all activity interfaces are derived from. This interface does not need to change, but it is worth noting because each activity defines a specialized version of it. The names of the specialised interfaces follow a pattern; in this case the new interface is IHThorQuantileArg.

  • IHThorQuantileArg

    The following is an outline of the new member functions, with comments on their use:

    • getFlags()

      Many of the interfaces have a getFlags() function. It provides a concise way of returning several Boolean options in a single call - provided those options do not change during the execution of the activity. The flags are normally defined with explicit values in an enumeration before the interface. The labels often follow the pattern T<First-letter-of-activity>F<lowercase-name>, i.e. TQFxxx ~= Thor-Quantile-Flag-XXX.

    • getNumDivisions()

      Returns how many parts to split the dataset into.

    • getSkew()

      Corresponds to the SKEW() attribute.

    • queryCompare()

      Returns an implementation of the interface used to compare two rows.

    • createDefault(rowBuilder)

      A function used to create a default row - used if there are no input rows.

    • transform(rowBuilder, _left, _counter)

      The function to create the output record from the input record and the partition number (passed as counter).

    • getScore(_left)

      What weighting should be given to this row?

    • getRange(isAll, tlen, tgt)

      Corresponds to the RANGE attribute.

Note that the different engines all use the same specialised interface - it contains a superset of the functions required by the different targets. Occasionally some of the engines do not need to use some of the functions (e.g., to serialize information between nodes) so the code generator may output empty implementations.

For each interface defined in eclhelper.hpp there is a base implementation class defined in eclhelper_base.hpp. The classes generated for each activity in a query by the code generator are derived from one of these base classes. Therefore we need to create a corresponding new class CThorQuantileArg. It often provides default implementations for some of the helper functions to help reduce the size of the generated code (e.g., getScore returning 1).

Often the process of designing the helper interface is dynamic. As the implementation is created, new options or possibilities for optimizations appear. These require extensions and changes to the helper interface in order to be implemented by the engines. Once the initial interface has been agreed, work on the code generator and the engines can proceeded in parallel. (It is equally possible to design this interface before any work on the parser begins, allowing more work to overlap.)

There are some more details on the contents of thorhelper.hpp in the documentation ecl/eclcc/WORKUNIT.rst within the HPCC repository.

Quantile 5 - The code generator

Adding a new activity to the code generator is (surprisingly!) a relatively simple operation. The process is more complicated if the activity also requires an implementation that generates inline C++, but that only applies to a small subset of very simple activities, e.g., filter, aggregate. Changes to the code generator also tend to be more substantial if you add a new type, but that is also not the case for the quantile activity.

For quantile, the only change required is to add a function that generates an implementation of the helper class. The code for all the different activities follows a very similar pattern - generate input activities, generate the helper for this activity, and link the input activities to this new activity. It is often easiest to copy the boiler-plate code from a similar activity (e.g., sort) and then adapt it. (Yes, some of this code could also be refactored... any volunteers?) There are a large number of helper functions available to help generate transforms and other member functions, which also simplifies the process.

The new code is found in commit https://github.com/ghalliday/HPCC-Platform/commit/47f850d827f1655fd6a78fb9c07f1e911b708175.

Most of the code should be self explanatory, but one item is worth highlighting. The code generator builds up a structure in memory that represents the C++ code that is being generated. The BuildCtx class is used to represent a location within that generated code where new code can be inserted. The instance variable contains several BuildCtx members that are used to represent locations to generate code within the helper class (classctx, nestedctx, createctx and startctx). They are used for different purposes:

  • classctx

    Used to generate any member functions that can be called as soon as the helper object has been created, e.g., getFlags().

  • nestedctx

    Used to generate nested member classes and objects - e.g., comparison classes.

  • startctx

    Any function that may return a value that depends on the context/parent activity. For example if QUANTILE is used inside the TRANSFORM of a PROJECT, the number of partition points may depend on a field in the LEFT row of the PROJECT. Therefore the getNumDivisions() member function needs to be generated inside instance->startctx. These functions can only be called by the engine after onCreate() and onStart() have been called to set up the current context.

  • createctx

    Really, this is a historical artefact from many years ago. It was originally used for functions that could be dependent on a global expression, but not a parent row. Almost all such restrictions have since been removed, and those that remain should probably be replaced with either classctx or startctx.

The only other change is to extend the switch statement in common/thorcommon/thorcommon.cpp to add a text description of the activity.

Quantile 6 - Roxie

With the code generator outputting all the information we need, we can now implement the activity in one of the engines. (As I mentioned previously, in practice this is often done in parallel with adding it to the code generator.) Roxie and hThor are the best engines to start with because most of their activities run on a single node - so the implementations tend to be less complicated. It is also relatively easy to debug them, by compiling to create a stand-alone executable, and then running that executable inside a debugger. The following description walks-through the roxie changes:

The changes have been split into two commits to make the code changes easier to follow. The first commit (https://github.com/ghalliday/HPCC-Platform/commit/30da006df9ae01c9aa784e91129457883e9bb8f3) adds the simplest implementation of the activity:

Code is added to ccdquery to process the new TAKquantile activity kind, and create a factory object of the correct type. The implementation of the factory class is relatively simple - it primarily contains a method for creating an instance of the activity class. Some factories create instances of the helper and cache any information that never changes (in this case the value returned by getFlags(), which is a very marginal optimization).

The classes that implement the existing sort algorithms are extended to return the sorted array in a single call. This allows the quicksort variants to be implemented more efficiently.

The class CRoxieServerQuantileActivity contains the code for implementing the quantile activity. It has the following methods:

  • Constructor

    Extracts any information from the helper that does not vary, and initializes all member variables.

  • start()

    This function is called before the graph is executed. It evaluates any helper methods that might vary from execution to execution (e.g., getRange(), numDivisions()), but which do not depend on the current row.

  • reset()

    Called when a graph has finished executing - after an activity has finished processing all its records. It is used to clean up any variables, and restore the activity ready for processing again (e.g., if it is inside a child query).

  • needsAllocator()

    Returns true if this activity creates new rows.

  • nextInGroup()

    The main function in the activity. This function is called by whichever activity is next in the graph to request a new row from the quantile activity. The functions should be designed so they return the next row as quickly as possible, and delay any processing until it is needed. In this case the input is not read and sorted until the first row is requested.

    Note, the call to the helper.transform() returns the size of the resulting row, and returns zero if the row should be skipped. The call to finaliseRowClear() after a successful row creation is there to indicate that the row can no longer be modified, and ensures that any child rows will be correctly freed when the row is freed.

    The function also contains extra logic to ensure that groups are implemented correctly. The end of a group is marked by returning a single NULL row, the end of the dataset by two contiguous NULL rows. It is important to ensure that a group that has all its output rows skipped doesn't return two NULLs in a row - hence the checks for anyThisGroup.

With those changes in place, the second commit https://github.com/ghalliday/HPCC-Platform/commit/aeaa209092ea1af9660c6908062c1b0b9acff36b adds support for the RANGE, FIRST, and LAST attributes. It also optimizes the cases where the input is already sorted, and the version of QUANTILE which does not include a transform. (If you are looking at the change in github then it is useful to ignore whitespace changes by appending ?w=1 to the URL). The main changes are

  • Extra helper methods called in start() to obtain the range.
  • Optimize the situation where the input is known to be sorted by reading the input rows directly into the "sorted" array.
  • Extra checks to see if this quantile should be included in the output (FIRST,LAST,RANGE,DEDUP)
  • An optimization to link the incoming row if the transform does not modify it, by testing the TQFneedtransform flag.

Quantile 7 - Possible roxie improvements

TBD...

hthor - trivial,sharing code and deprecated.

Discussion, of possible improvements.

Hoares' algorithm.

Ln2(n) < 4k?

SKEW and Hoares

Ordered RANGE. Calc offsets from the quantile (see testing/regress/ecl/xxxxx?)

SCORE

Quantile 8 - Thor

TBD

Basic activity structure

Locally sorting and allowing the inputs to spill.

The partitioning approach

Classes

Skew

Optimizations

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/roxie.html b/devdoc/roxie.html new file mode 100644 index 00000000000..1c87c244926 --- /dev/null +++ b/devdoc/roxie.html @@ -0,0 +1,31 @@ + + + + + + Everything you ever wanted to know about Roxie | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Everything you ever wanted to know about Roxie

Why did I create it?

Because I could. Many of the pieces needed for Roxie were already created for use in other systems – ECL language, code generator, index creation in Thor, etc. Indexes could be used by Moxie, but that relied on monolithic single-part indexes, was single-threaded (forked a process per query) and had limited ability to do any queries beyond simple index lookups. ECL had already proved itself as a way to express more complex queries concisely, and the concept of doing the processing next to the data had been proved in hOle and Thor, so Roxie – using the same concept for online queries using indexes – was a natural extension of that, reusing the existing index creation and code generation, but adding a new run-time engine geared towards pre-deployed queries and sending index lookup requests to the node holding the index data.

The code generator creates a graph (DAG) representing the query, with one node per activity and links representing the inputs and dependencies. There is also a helper class for each activity.

Roxie loads this graph for all published queries, creating a factory for each activity and recording how they are linked. When a query is executed, the factories create the activity instances and link them together. All activities without output activities (known as ‘sinks’) are then executed (often on parallel threads), and will typically result in a value being written to a workunit, to the socket that the query was received in, or to a global “context” area where subsequent parts of the query might read it.

Data is pulled through the activity graph, by any activity that wants a row from its input requesting it. Evaluation is therefore lazy, with data only calculated as needed. However, to reduce latency in some cases activities will prepare results ahead of when they are requested – for example an index read activity will send the request to the agent(s) as soon as it is started rather than waiting for the data to be requested by its downstream activity. This may result in wasted work, and in some cases may result in data coming back from an agent after the requesting query has completed after discovering it didn’t need it after all – this results in the dreaded “NO msg collator found – using default” tracing (not an error but may be indicative of a query that could use some tuning).

Before requesting rows from an input, it should be started, and when no more rows are required it should be stopped. It should be reset before destruction or reuse (for example for the next row in a child query).

Balancing the desire to reduce latency with the desire to avoid wasted work can be tricky. Conditional activities (IF etc) will not start their unused inputs, so that queries can be written that do different index reads depending on the input. There is also the concept of a “delayed start” activity – I would need to look at the code to remind myself of how those are used.

Where are the Dragons?

Splitter activities are a bit painful – they may result in arbitrary buffering of the data consumed by one output until another output is ready to request a row. It’s particularly complex when some of the outputs don’t start at all – the splitter needs to keep track of how many of the inputs have been started and stopped (an input that is not going to be used must be stopped, so that splitters know not to keep data for them). Tracking these start/stop/reset calls accurately is very important otherwise you can end up with weird bugs including potential crashes when activities are destroyed. Therefore we report errors if the counts don’t tally properly at the end of a query – but working out where a call was missed is often not trivial. Usually it’s because of an exception thrown from an unexpected place, e.g. midway through starting.

Note that row requests from the activities above a splitter may execute on whichever thread downstream from the splitter happens to need that particular row first.

The splitter code for tracking whether any of the downstream activities still need a row is a bit hairy/inefficient, IIRC. There may be scope to optimize (but I would recommend adding some good unit test cases first!)

How does “I beat you to it” work?

When there are multiple agents fulfilling data on a channel, work is shared among them via a hash of the packet header, which is used to determine which agent should work on that packet. However, if it doesn’t start working on it within a short period (either because the node is down, or because it is too busy on other in-flight requests), then another node may take over. The IBYTI messages are used to indicate that a node has started to work on a packet and therefore there is no need for a secondary to take over.

The priority of agents as determined by the packet hash is also used to determine how to proceed if an IBYTI is received after starting to work on a request. If the IBYTI is from a lower priority buddy (sub-channel) then it is ignored, if it’s from a higher priority one then the processing will be abandoned.

When multicast is enabled, the IBYTI is sent on the same multicast channel as the original packet (and care is needed to ignore ones sent by yourself). Otherwise it is sent to all buddy IPs.

Nodes keep track of how often they have had to step in for a supposedly higher priority node, and reduce their wait time before stepping in each time this happens, so if a node has crashed then the buddy nodes will end up taking over without every packet being delayed.

(QUESTION – does this result in the load on the first node after the failed node getting double the load?)

Newer code for cloud systems (where the topology may change dynamically) send the information about the buddy nodes in the packet header rather than assuming all nodes already have a consistent version of that information. This ensures that all agents are using the same assumptions about buddy nodes and their ordering.

All about index compression

An index is basically a big sorted table of the keyed fields, divided into pages, with an index of the last row from each page used to be able to locate pages quickly. The bottom level pages (‘leaves’) may also contain payload fields that do not form part of the lookup but can be returned with it.

Typical usage within LN Risk tends to lean towards one of two cases:

  • Many keyed fields with a single “ID” field in the payload
  • A single “ID” field in the key with many “PII” fields in the payload.

There may be some other cases of note too though – e.g. an error code lookup file which heavily, used, or Boolean search logic keys using smart-stepping to implement boolean search conditions.

It is necessary to store the index pages on disk compressed – they are very compressible – but decompression can be expensive. For this reason traditionally we have maintained a cache of decompressed pages in addition to the cache of compressed pages that can be found in the Linux page cache. However, it would be much preferred if we could avoid decompressing as much as possible, ideally to the point where no significant cache of the decompressed pages was needed.

Presently we need to decompress to search, so we’ve been looking at options to compress the pages in such a way that searching can be done using the compressed form. The current design being played with here uses a form of DFA to perform searching/matching on the keyed fields – the DFA data is a compact representation of the data in the keyed fields but is also efficient to use as for searching. For the payload part, we are looking at several options (potentially using more than one of them depending on the exact data) including:

  • Do not compress (may be appropriate for ID case, for example)
  • Compress individual rows, using a shared dictionary (perhaps trained on first n rows of the index)
  • Compress blocks of rows (in particular, rows that have the same key value)

A fast (to decompress) compression algorithm that handles small blocks of data efficiently is needed. Zstd may be one possible candidate.

Preliminary work to enable the above changes involved some code restructuring to make it possible to plug in different compression formats more easily, and to vary the compression format per page.

What is the topology server for?

It’s used in the cloud to ensure that all nodes can know the IP addresses of all agents currently processing requests for a given channel. These addresses can change over time due to pod restarts or scaling events. Nodes report to the topology server periodically, and it responds to them with the current topology state. There may be multiple topology servers running (for redundancy purposes). If so all reports should go to all, and it should not matter which one’s answer is used. (QUESTION – how is the send to all done?)

Lazy File IO

All IFileIO objects used to read files from Roxie are instantiated as IRoxieLazyFileIO objects, which means:

  • The underlying file handles can be closed in the background, in order to handle the case where file handles are a limited resource. The maximum (and minimum) number of open files can be configured separately for local versus remote files (sometimes remote connections are a scarcer resource than local, if there are limits at the remote end).

  • The actual file connected to can be switched out in the background, to handle the case where a file read from a remote location becomes unavailable, and to switch to reading from a local location after a background file copy operation completes.

New IBYTI mode

Original IBYTI implementation allocated a thread (from the pool) to each incoming query packet, but some will block for a period to allow an IBYTI to arrive to avoid unnecessary work. It was done this way for historical reasons - mainly that the addition of the delay was after the initial IBYTI implementation, so that in the very earliest versions there was no priority given to any particular subchannel and all would start processing at the same time if they had capacity to do so.

This implementation does not seem particularly smart - in particular it's typing up worker threads even though they are not actually working, and may result in the throughput of the Roxie agent being reduced. For that reason an alternative implementation (controlled by the NEW_IBYTI flag) was created during the cloud transition which tracks what incoming packets are waiting for IBYTI expiry via a separate queue, and they are only allocated to a worker thread once the IBYTI delay times out.

So far the NEW_IBYTI flag has only been set on containerized systems (simply to avoid rocking the boat on the bare-metal systems), but we may turn on in bare metal too going forward (and if so, the old version of the code can be removed sooner or later).

Testing Roxie code

Sometimes when developing/debugging Roxie features, it's simplest to run a standalone executable. Using server mode may be useful if wanting to debug server/agent traffic messaging.

For example, to test IBYTI behaviour on a single node, use

./a.out --server --port=9999 --traceLevel=1 --logFullQueries=1 --expert.addDummyNode --roxieMulticastEnabled=0 --traceRoxiePackets=1
+

Having first compiled a suitable bit of ECL into a.out. I have found a snippet like this quite handy:

rtl := SERVICE
+ unsigned4 sleep(unsigned4 _delay) : eclrtl,action,library='eclrtl',entrypoint='rtlSleep';
+END;
+
+d := dataset([{rtl.sleep(5000)}], {unsigned a});
+allnodes(d)+d;
+

Cache prewarm

Roxie (optionally) maintains a list of the most recently accessed file pages (in a circular buffer), and flushes this information periodically to text files that will persist from one run of Roxie to the next. On startup, these files are processed and the relevant pages preloaded into the linux page cache to ensure that the "hot" pages are already available and maximum performance is available immediately once the Roxie is brought online, rather than requiring a replay of a "typical" query set to heat the cache as used to be done. In particular this should allow a node to be "warm" before being added to the cluster when autoscaling.

There are some questions outstanding about how this operates that may require empirical testing to answer. Firstly, how does this interact with volumes mounted via k8s pvc's, and in particular with cloud billing systems that charge per read. Will the reads that are done to warm the cache be done in large chunks, or will they happen one linux page at a time? The code at the Roxie level operates by memory-mapping the file then touching a byte within each linux page that we want to be "warm", but does the linux paging subsystem fetch larger blocks? Do huge pages play a part here?

Secondly, the prewarm is actually done by a child process (ccdcache), but the parent process is blocked while it happens. It would probably make sense to at allow at least some of the other startup operations of the parent process to proceed in parallel. There are two reasons why the cache prewarm is done using a child process. Firstly is to allow there to be a standalone way to prewarm prior to launching a Roxie, which might be useful for automation in some bare-metal systems. Secondly, because there is a possibility of segfaults resulting from the prewarm if the file has changed size since the cache warming was done, it is easier to contain, capture, and recover from such faults in a child process than it would be inside Roxie. However, it would probably be possible to avoid these segfaults (by checking more carefully against file size before trying to warm a page, for example) and then link the code into Roxie while still keeping the code common with the standalone executable version.

Thirdly, need to check that the prewarm is complete before adding a new agent to the topology. This is especially relevant if we make any change to do the prewarm asynchronously.

Fourthly, there are potential race conditions when reading/writing the file containing cache information, since this file may be written by any agent operating on the same channel, at any time.

Fifthly, how is the amount of information tracked decided? It should be at least related to the amount of memory available to the linux page cache, but that's not a completely trivial thing to calculate. Should we restrict to the most recent N when outputting, where N is calculated from, for example /proc/meminfo's Active(file) value? Unfortunately on containerized sytems that reflects the host, but perhaps /sys/fs/cgroup/memory.stat can be used instead?

When deciding how much to track, we can pick an upper limit from the pod's memory limit. This could be read from /sys/fs/cgroup/memory.max though we currently read from the config file instead. We should probably (a) subtract the roxiemem size from that and (b) think about a value that will work on bare-metal and fusion too. However, because we don't dedup the entries in the circular buffer used for tracking hot pages until the info is flushed, the appropriate size is not really the same as the memory size.

We track all reads by page, and before writing also add all pages in the jhtree cache with info about the node type. Note that a hit in the jhtree page cache won't be noted as a read OTHER than via this last-minute add.

Blacklisting sockets

This isn't really specific to Roxie, but was originally added for federated Roxie systems...

When a Roxie query (or hthor/thor) makes a SOAPCALL, there is an option to specify a list of target gateway IPs, and failover to the next in the list if the first does not respond in a timely fashion. In order to avoid this "timely fashion" check adding an overhead to every query made when a listed gateway is unavailable, we maintain a "blacklist" of nodes that have been seen to fail, and do not attempt to connect to them. There is a "deblacklister" thread that checks periodically whether it is now possible to connect to a previously-blacklisted gateway, and removes it from the list if so.

There are a number of potential questions and issues with this code:

  1. It would appear that a blacklist is applied even when there is only one gateway listed. In this case, the blacklist may be doing more harm than good? I'm not sure that is true - it is still causing rapid failures in cases that are never going to work...
  2. Even when there is only one gateway listed, a blacklist MIGHT still be useful (you don't really want EVERY query to block trying to connect, if the gateway is down - may prefer a fast failure). Also applies when there are multiple records, all being passed to a gateway, and with an ONFAIL.
  3. Is the blacklist shared between all queries? I'm pretty sure it is NOT shared across Roxie nodes... Looks like it is a global object, shared between all queries and activities. However, connections that were started in parallel will all be reported as failed rather than blacklisted, which can make it look like it is maintained per-activity.
  4. Is it only a failed connect that leads to blacklisting, or does a slow/error response also cause a gateway endpoint to be blacklisted? It's only a failed connect.
  5. When deblacklisting, can we check any condition other than "Successfully connected"? If not, blacklisting for any reason other than "Did not connect" feels like a recipe for problems. We only check for a connection (and correspondingly only blacklist for a failed connection).
  6. Are we ever using the functionality where there are more than one gateway listed? Most of the time a load-balancer is a preferable solution...
  7. The "deblacklister" thread seems to add an escalating delay between attempts. Is this delay ever reset? Is it configurable? Is it appropriate?
  8. There's a thread (in the blacklister's pool) for each blacklisted endpoint. These threads will never go away if the endpoint does not recover...
  9. There's a delay of up to 10 seconds in terminating caused by the deblacklister's connect having to timeout before it notices that we are stopping. Can we close the socket as well as interrupting the semaphore?
  10. Does the "reconnect" attempt from the deblacklister cause any pain for the server it is connecting to? Lots of connect attempts without any data could look like a DoS attack...
  11. Retries/timeout seems to translate to Owned<ISocketConnectWait> scw = nonBlockingConnect(ep, timeoutMS == WAIT_FOREVER ? 60000 : timeoutMS*(retries+1)); I am not sure that is correct (a single attempt to connect with a long timeout doesn't feel like it is the same as multiple attempts with shorter timeouts, for example if there is a load balancer in the mix).
  12. Perhaps an option to not use blacklister would solve the immediate issue?
  13. The blacklister uses an array of endpoints - If there were a lot blacklisted, a hash table would be better
  14. Hints to control behaviour of deblacklister would behave unpredictably if multiple activities connected to the same endpoint with different hints unless we make the blacklist lookup match the hint values too.
  15. Deblacklister should use nonBlockingConnect too.

Should the scope of the blacklist be different? Possible scopes are:

  1. Shared across all queries/activities (current behaviour)
  2. Specific to an activity, but shared across queries (i.e. owned by the activity factory)
  3. Specific to all activities in a deployed query (i.e. owned by the query factory)
  4. Specific to a particular activity instance (i.e. owned by the activity object)
  5. Specific to a particular query instance (i.e. owned by the query object)

Options 2 and 4 above would allow all aspects of the blacklisting behaviour to be specified by options on the SOAPCALL. We could control whether or not the blacklister is to be used at all via a SOAPCALL option with any of the above...

perftrace options

The HPCC Platform includes a rudimentary performance tracing feature using periodic stack capture to generate flame graphs. Roxie supports this in 3 ways:

  1. If expert/@profileStartup is set in roxie config, a flame graph is generated for operations during Roxie startup phase.
  2. If @perf is set on an incoming query, a flame graph is generated for the lifetime of that query's execution, and returned along with the query results
  3. If expert/perftrace is set in roxie config, one-shot roxie queries (e.g. eclagent mode) generate a flame graph (currently just to a text file).

The perf trace operates as follows:

  1. A child process is launched that runs the doperf script. This samples the current stack(s) every 0.2s (configurable) to a series of text files.
  2. When tracing is done, these text files are "folded" via a perl script that notes every unique stack and how many times it was seen, one line per unique stack
  3. This folded stack list is filtered to suppress some stacks that are not very interesting
  4. The filtered folded stack list is passed to another perl script that generates an svg file.

The basic info captured at step 1 (or maybe 2) could also be analysed to give other insights, such as:

  1. A list of "time in function, time in children of function".
  2. An expanded list of callers to __GI___lll_lock_wait and __GI___lll_lock_wake, to help spot contended critsecs.

Unfortunately some info present in the original stack text files is lost in the folded summary - in particular related to the TID that the stack is on. Can we spot lifetimes of threads and/or should we treat "stacks" on different threads as different? Thread pools might render this difficult though. There is an option in stack-collapse-elfutils.pl to include the TID when considering whether stacks match, so perhaps we should just (optionally) use that.

Some notes on LocalAgent mode

In localAgent mode, the global queueManager object (normally a RoxieUdpSocketQueueManager) is replaced by a RoxieLocalQueueManager. Outbound packets are added directly to target queue, inbound are packed into DataBuffers.

There is also "local optimizations" mode where any index operation reading a one-part file (does the same apply to one-part disk files?) just reads it directly on the server (regardless of localAgent setting). Typically still injected into receiver code though as otherwise handling exception cases, limits etc would all be duplicated/messy. Rows created in localOptimization mode are created directly in the caller's row manager, and are injected in serialized format.

Why are inbound not created directly in the desired destination's allocator and then marked as serialized? Some lifespan issues... are they insurmountable? We do pack into dataBuffers rather than MemoryBuffers, which avoids a need to copy the data before the receiver can use it. Large rows get split and will require copying again, but we could set dataBufferSize to be bigger in localAgent mode to mitigate this somewhat.

What is the lifespan issue? In-flight queries may be abandoned when a server-side query fails, times out, or no longer needs the data. Using DataBuffer does not have this issue as they are attached to the query's memory manager/allocation once read. Or we could bypass the agent queue altogether, but rather more refactoring needed for that (might almost be easier to extent the "local optimization" mode to use multiple threads at that point)

abortPending, replyPending, and abortPendingData methods are unimplemented, which may lead to some inefficiencies?

Some notes on UDP packet sending mechanism

Requests from server to agents are send via UDP (and have a size limit of 64k as a result). Historically they were sent using multicast to go to all agents on a channel at the same time, but since most cloud providers do not support multicast, there has long been an option to avoid multicast and send explicitly to the agent IPs. In bare metal systems these IPs are known via the topology file, and do not change. In cloud systems the topology server provides the IPs of all agents for a channel.

In cloud systems, the list of IPs that a message was sent to is included in the message header, so that the IBYTI messages can be sent without requiring that all agents/servers have the same topology information at any given moment (they will stay in sync because of topology server, but may be temporarily out of sync when nodes are added/removed, until next time topology info is retrieved). This is controled by the SUBCHANNELS_IN_HEADER define.

Packets back from agents to server go via the udplib message-passing code. This can best be described by looking at the sending and receiving sides separately.

When sending, results are split into individual packets (DataBuffers), each designed to be under 1 MTU in size. Traditionally this meant they were 1k, but they can be set larger (8k is good). They do have to be a power of 2 because of how they are allocated from the roxiemem heap. The sender maintains a set of UdpReceiverEntry objects, one for each server that it is conversing with. Each UdpReceiverEntry maintains multiple queues of data packets waiting to be sent, one queue for each priority. The UdpReceiverEntry maintains a count of how many packets are contained across all its queues in packetsQueued, so that it knows if there is data to send.

The priority levels are: 0: Out Of Band 1: Fast lane 2: Standard

This is designed to allow control information to be sent without getting blocked by data, and high priority queries to avoid being blocked by data going to lower priority ones. The mechanism for deciding what packet to send next is a little odd though - rather than sending all higher-priorty packets before any lower-priority ones, it round robins across the queues sending up to N^2 from queue 0 then up to N from queue 1 then 1 from queue 2, where N is set by the UdpOutQsPriority option, or 1 if not set. This may be a mistake - probably any from queue 0 should be sent first, before round-robining the other queues in this fashion.

UdpReceiverEntry objects are also responsible for maintaining a list of packets that have been sent but receiver has not yet indicated that they have arrived.

If an agent has data ready for a given receiver, it will send a requestToSend to that receiver, and wait for a permitToSend response. Sequence numbers are used to handle situations where these messages get lost. A permitToSend that does not contain the expected sequence number is ignored.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/userdoc/AzureTipsTricks.html b/devdoc/userdoc/AzureTipsTricks.html new file mode 100644 index 00000000000..f01b314e579 --- /dev/null +++ b/devdoc/userdoc/AzureTipsTricks.html @@ -0,0 +1,24 @@ + + + + + + HPCC Platform + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/devdoc/userdoc/Blogs.html b/devdoc/userdoc/Blogs.html new file mode 100644 index 00000000000..7990650bde6 --- /dev/null +++ b/devdoc/userdoc/Blogs.html @@ -0,0 +1,24 @@ + + + + + + HPCC Platform + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/devdoc/userdoc/README.html b/devdoc/userdoc/README.html new file mode 100644 index 00000000000..96045701dd2 --- /dev/null +++ b/devdoc/userdoc/README.html @@ -0,0 +1,24 @@ + + + + + + User Documentation | HPCC Platform + + + + + + + + + + + + + +
Skip to content

User Documentation

Documentation in this directory is targeted to end-users of the HPCC Systems Platform. This is less-formal documentation intended to be produced and released more quickly than the published HPCC documentation. See HPCC documentation if you would like to contribute to our official docs.

Directory structure under devdoc

INFO

  • userdoc/troubleshoot: Information related to troubleshooting particular components
  • userdoc/azure: Useful information about Azure Cloud portal and cli
  • userdoc/roxie: Useful information for running roxie
  • userdoc/thor: COMING SOON: Useful information for running thor
  • userdoc/blogs: COMING SOON: Location and instructions for all Blogs

General documentation

HPCC Website documentation

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/userdoc/WikiGuidelines.html b/devdoc/userdoc/WikiGuidelines.html new file mode 100644 index 00000000000..3d37c879057 --- /dev/null +++ b/devdoc/userdoc/WikiGuidelines.html @@ -0,0 +1,24 @@ + + + + + + HPCC Platform + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/devdoc/userdoc/azure/TipsAndTricks.html b/devdoc/userdoc/azure/TipsAndTricks.html new file mode 100644 index 00000000000..e9be6e15a78 --- /dev/null +++ b/devdoc/userdoc/azure/TipsAndTricks.html @@ -0,0 +1,77 @@ + + + + + + Azure Portal FAQs | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Azure Portal FAQs

  1. How do I see who created a resource group?

Answer:

1. go to the resource group service page (https://portal.azure.com/#view/HubsExtension/BrowseResourceGroups)
+2. "Add filter"
+3. Filter on "Admin"
+4. Set the Value to "ALL" or select the names you are interested in (the names in the list are the only ones that have the Admin tag set)  This works for all other fields that you can filter on
  1. How do I create a dashboard in Azure?

Answer:

Login to the Azure portal.
+
+Click on the `hamburger icon`, located at the top left corner of the page.
+
+Click on `Dashboard` to go to your dashboards.
+
+Click on `Create`, located at the top left corner.
+
+Click on the `Custom` tile.
+
+Edit the input box to name your dashboard.
+
+Click on `Resource groups` in the tile gallery.
+
+Click `Add`.
+
+Click and drag the `lower right corner` of the tile to resize it to your likings.
+
+Click `Save` to save your settings.
+
+You should now be taken to your new dashboard.
+
+Click on your new dashboard tile.
+
+Click on `Add filter`, located at the top center of the page.
+
+Click on the `Filter` input box to reveal the tags.
+
+Select the `Admin` tag.
+
+Click on the `Value` input box.
+
+Click on `Select all` to unselect all.
+
+Select your name.
+
+Click on `Apply`.
+
+Next, click on `Manage view`, located at the top left of the page.
+
+Select `Save view`.
+
+Enter a name for the view in the input box.
+
+Click `Save`
+
+ 
+
+ 
+
+Click on `Manage View`, located at the top left of the page

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/userdoc/copilot/CopilotPromptTips.html b/devdoc/userdoc/copilot/CopilotPromptTips.html new file mode 100644 index 00000000000..fe0b6c55b08 --- /dev/null +++ b/devdoc/userdoc/copilot/CopilotPromptTips.html @@ -0,0 +1,24 @@ + + + + + + Copilot Prompt Tips | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Copilot Prompt Tips

Unlock the full potential of GitHub Copilot with these carefully curated prompts designed to streamline your workflow and enhance productivity.

Whether you're summarizing complex texts, brainstorming innovative ideas, or creating detailed guides, these prompts will help you get the most out of Copilot's capabilities.

Dive in and discover how these simple yet powerful prompts can save you time and effort in your daily tasks.

Generic Prompts

Here are a few simple prompts that can save time:

  • Provide a brief summary of the text below. Show the key points in a bullet list.

  • List [N (number)] ways to [accomplish a specific goal or solve a problem]? Include a short description for each approach in a bullet list.

    • Example: List 10 ways to reduce redundancy in text. Include a short description for each approach in a bullet list.
  • What are the key differences/similarities between [concept A] and [concept B]? Present the information in a table format.

    • Example: What are the key differences/similarities between a quicksort ad a bubble sort? Present the information in a table format.
  • Explain [complex topic] in simple terms. Use analogies or examples to help make it easier to understand.

    • Example: Explain generative AI in simple terms. Use analogies or examples to help make it easier to understand.
  • Brainstorm [N (number)] ideas for [a specific project or topic]? Include a short description for each idea in a bullet list.

    • Example: Brainstorm 7 ideas for an instructional video for new programmers? Include a short description for each idea in a bullet list.
  • Create a template for [a specific type of document, such as a business email, proposal, etc.]. Include the key elements to include in a bullet list.

    • Example: Create a template for product version release announcement. Include the key elements to include in a bullet list.
  • Write a step-by-step guide on how to [specific task or procedure]. Number the steps to improve clarity.

    • Example: Write a step-by-step guide on how to make a PB & J sandwich. Number the steps to improve clarity.

Specific Prompts

These prompts are more focused and are meant to accomplish a specific purpose. They are included here to spark your imagination.

If you think of others that could be included here to share with the world, please send your ideas to docfeedback@hpccsystems.com.

  • Write comments for this ECL code in javadoc format

  • Create an ECL File Definition, record structure, and inline dataset with [N (number)] of records with the following fields [list of fields]

    • Example: Create an ECL file definition with a record structure and an inline dataset with 20 records for a dataset of animals with the following fields: ReferenceNumber, AnimalName, ClassName, FamilyName, Color, Size, and LifeExpectancy.
  • Write ECL code to classify records into [categories].

    • Example: Write ECL code to classify customer feedback into neutral, negative, or positive categories.

How to Avoid AI Hallucinations with Good Prompts

AI hallucinations refer to instances where artificial intelligence systems generate information or responses that are incorrect, misleading, or entirely fabricated.

Hallucinations can occur due to various reasons, such as limitations in the training data, inherent biases, or the AI's attempt to provide an answer even when it lacks sufficient context or knowledge.

Understanding and mitigating AI hallucinations is crucial for ensuring the reliability and accuracy of AI-driven applications.

Creating effective prompts is essential to minimize AI hallucinations. Here are some tips to help you craft prompts that lead to accurate and reliable responses:

  • Be Specific and Clear: Ambiguous prompts can lead to incorrect or irrelevant answers. Clearly define the task and provide specific instructions.

    • Example: Instead of asking "What is AI?", ask "Explain the concept of artificial intelligence and its primary applications in healthcare."
  • Provide Context: Give the AI enough background information to understand the task. This helps in generating more accurate responses.

    • Example: "Given the following text about climate change, summarize the key points in a bullet list."
  • Use Constraints: Limit the scope of the response by specifying constraints such as word count, format, or specific details to include.

    • Example: "List 5 benefits of renewable energy in a bullet list, each point not exceeding 20 words."
  • Ask for Evidence or Sources: Encourage the AI to provide evidence or cite sources for the information it generates.

    • Example: "Explain the impact of social media on mental health and provide references to recent studies."
  • Iterative Refinement: Start with a broad prompt and refine it based on the initial responses to get more accurate results.

    • Example: Begin with "Describe the process of photosynthesis." If the response is too vague, refine it to "Describe the process of photosynthesis in plants, including the role of chlorophyll and sunlight."

By following these guidelines, you can reduce the likelihood of AI hallucinations and ensure that the responses generated are accurate and useful.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/userdoc/roxie/FAQ.html b/devdoc/userdoc/roxie/FAQ.html new file mode 100644 index 00000000000..a16cf49905f --- /dev/null +++ b/devdoc/userdoc/roxie/FAQ.html @@ -0,0 +1,47 @@ + + + + + + ROXIE FAQs | HPCC Platform + + + + + + + + + + + + + +
Skip to content

ROXIE FAQs

  1. How I can compile a query on a containerized or cloud-based system?

Answer:

Same way as bare metal. Command line, or with the IDE, or from ECL Watch. Just point to the HPCC Systems instance to compile.
+For Example:
+ecl deploy <target> <file>
  1. How do I copy queries from an on-prem cluster to Azure?

Answer:

The copy query command – use the Azure host name or IP address for the target.
+For example:
+ecl queries copy <source_query_path> <target_queryset>
  1. How can I get the IP address for the Azure target cluster?

Answer:

Use the "kubectl get svc" command. Use the external IP address listed for ECL Watch.
+kubectl get svc
  1. Do we have to have use the DNSName or do we need to use the IP address?

Answer:

If you can reach ECL Watch with the DNS Name then it should also work for the command line.
  1. How can I find the ECL Watch or Dali hostname?

Answer:

If you did not set up the containerized instance, then you need to ask your Systems Administrator or whomever set it up..
  1. How do I publish a package file?

Answer:

Same way as bare metal.
+To add a new package file: ecl packagemap add or
+To copy exisitng package file : ecl packagemap copy
  1. How do I check the logs?

Answer:

kubectl log <podname>
+in addition you can use -f (follow) option to tail the logs. Optionally you can also issue the <namespace> parameter.
+For example:
+kbectl log roxie-agent-1-3b12a587b –namespace MyNameSpace
+Optionally, you may have implemented a log-processing solution such as the Elastic Stack (elastic4hpcclogs).
  1. How do I get the data on to Azure?

Answer:

Use the copy query command and copy or add the Packagemap.
+With data copy start in the logs…copy from remote location specified if data doesn’t exist on the local system.
+The remote location is the remote Dali (use the --daliip=<daliIP> parameter to specify the remote Dali)
+You can also use ECL Watch.
  1. How can I start a cloud cluster? (akin to the old Virtual Box image)?

Answer:

Can use Docker Desktop, or Azure or any cloud provider and install the HPCC Systems Cloud native helm
+charts
  1. How can I show the ECL queries that are published to a given Roxie?

Answer:

Can use WUListQueries
+For example:
+https://[eclwatch]:18010/WsWorkunits/WUListQueries.json?ver_=1.86&ClusterName=roxie&CheckAllNodes=0
  1. I set up persistent storage on my containerized HPCC Systems, and now it won't start. Why?

Answer:

One possible reason may be that all of the required storage directories are not present. The directories for ~/
+hpccdata/dalistorage, hpcc-data, debug, queries, sasha, and dropzone are all required to exist or your cluster may not start.
  1. Are there any new methods available to work with queries?

Answer:

Yes. There is a new method available ServiceQuery.
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&
+For example Roxie Queries:
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&Type=roxie
+or WsECL (eclqueries)
+https://[eclwatch]:18010/WsResources/ServiceQuery?ver_=1.01&Type=eclqueries

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/devdoc/userdoc/troubleshoot/ClientsToolIssues.html b/devdoc/userdoc/troubleshoot/ClientsToolIssues.html new file mode 100644 index 00000000000..ab238d95dcc --- /dev/null +++ b/devdoc/userdoc/troubleshoot/ClientsToolIssues.html @@ -0,0 +1,24 @@ + + + + + + How to resolve issues installing the ECL IDE / Client tools | HPCC Platform + + + + + + + + + + + + + +
Skip to content

How to resolve issues installing the ECL IDE / Client tools

Problem

Some users may experience an issue when trying to install the ECL IDE / client tools version 8.10 and later.
If you get an error saying something like, Windows Defender SmartScreen prevented access and there is no option other than "Don't Run":

  • Go to the folder where you downloaded the file.
  • Once there go to the app file properties click unblock

alt-text

alt-text

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/ecl/ecl-bundle/DOCUMENTATION.html b/ecl/ecl-bundle/DOCUMENTATION.html new file mode 100644 index 00000000000..3151e11a3c0 --- /dev/null +++ b/ecl/ecl-bundle/DOCUMENTATION.html @@ -0,0 +1,24 @@ + + + + + + Ecl-bundle source documentation | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Ecl-bundle source documentation

Introduction

Purpose

The ecl-bundle executable (normally executed from the ecl executable by specifying 'ecl bundle XXX' is designed to manipulate ecl bundle files

  • these are self-contained parcels of ECL code, packaged into a compressed file like a zip or .tar.gz file, that can be downloaded, installed, removed etc from an ECL installation.

Design

The metadata for ECL bundles is described using an exported module called Bundle within the bundle's source tree - typically, this means that a file called Bundle.ecl will be added to the highest level of the bundle's directory tree. In order to extract the information from the Bundle module, eclcc is run in 'evaluate' mode (using the -Me option) which will parse the bundle module and output the required fields to stdout.

ecl-bundle also executes eclcc (using the --showpaths option) to determine where bundle files are to be located.

Directory structure

In order to make versioning easier, bundle files are not copied directly into the bundles directory. A bundle called "MyBundle" that announces itself as version "x.y.z" will be installed to the directory

$BUNDLEDIR/_versions/MyBundle/x.y.z

A "redirect" file called MyBundle.ecl is then created in $BUNDLEDIR, which redirects any IMPORT MyBundle statement to actually import the currently active version of the bundle in _versions/MyBundle/x.y.z

By rewriting this redirect file, it is possible to switch to using a different version of a bundle without having to uninstall and reinstall.

In a future release, we hope to make it possible to specify that bundle A requires version X of bundle B, while bundle C requires version Y of bundle B. That will require the redirect files to be 'local' to a bundle (and will require that bundle B uses a redirect file to ensure it picks up the local copy of B when making internal calls).

Key classes

An IBundleInfo represents a specific copy of a bundle, and is created by explicitly parsing a snippet of ECL that imports it, with the ECL include path set to include only the specified bundle.

An IBundleInfoSet represents all the installed versions of a particular named bundle.

An IBundleCollection represents all the bundles on the system.

Every individual subcommand is represented by a class derived (directly or indirectly) from EclCmdCommon. These classes are responsible for command-line parsing, usage text output, and (most importantly) execution of the desired outcomes.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/ecllibrary/StyleGuide.html b/ecllibrary/StyleGuide.html new file mode 100644 index 00000000000..228e27d22e0 --- /dev/null +++ b/ecllibrary/StyleGuide.html @@ -0,0 +1,43 @@ + + + + + + ECL standard library style guide | HPCC Platform + + + + + + + + + + + + + +
Skip to content

ECL standard library style guide

The ECL code in the standard library should follow the following style guidelines:

  • All ECL keywords in upper case
  • ECL reserved types in upper case
  • Public attributes in camel case with leading upper case
  • Private attributes in lower case with underscore as a separator
  • Field names in lower case with underscore as a separator
  • Standard indent is 2 spaces (no tabs)
  • Maximum line length of 120 characters
  • Compound statements have contents indented, and END is aligned with the opening statement
  • Field names are not indented to make them line up within a record structure
  • Parameters are indented as necessary
  • Use javadoc style comments on all functions/attributes (see Writing Javadoc Comments)

For example:

ecl
my_record := RECORD
+    INTEGER4 id;
+    STRING firstname{MAXLENGTH(40)};
+    STRING lastname{MAXLENGTH(50)};
+END;
+
+/**
+  * Returns a dataset of people to treat with caution matching a particular lastname.  The
+  * names are maintained in a global database of undesirables.
+  *
+  * @param  search_lastname    A lastname used as a filter
+  * @return                    The list of people
+  * @see                       NoFlyList
+  * @see                       MorePeopleToAvoid
+  */
+
+EXPORT DodgyCharacters(STRING search_lastname) := FUNCTION
+    raw_ds := DATASET(my_record, 'undesirables', THOR);
+    RETURN raw_ds(last_name = search_lastname);
+END;

Some additional rules for attributes in the library:

  • Services should be SHARED and EXPORTed via intermediate attributes
  • All attributes must have at least one matching test. If you're not on the test list you're not coming in.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json new file mode 100644 index 00000000000..60d74ce9d0f --- /dev/null +++ b/hashmap.json @@ -0,0 +1 @@ +{"build_me.md":"Cs6Xx-2H","cmake_modules_documentation.md":"BLw9X_2f","devdoc_codegenerator.md":"DjbPmndU","devdoc_codereviews.md":"BB4WmPQp","devdoc_codesubmissions.md":"DoLTBfxy","devdoc_devdocs.md":"DbnKa3rJ","devdoc_development.md":"Cz70vUo4","devdoc_docs_contributedocs.md":"B6UEh3AF","devdoc_docs_doctemplate.md":"DO145SYO","devdoc_docs_hpccstyleguide.md":"ttTlrCG0","devdoc_gitauthenticate.md":"DWDVKYp3","devdoc_ldapsecuritymanager.md":"Cty5AMLN","devdoc_memorymanager.md":"lMxAbcSH","devdoc_metrics.md":"BVV7YlV-","devdoc_newactivity.md":"Cwh2eRu1","devdoc_newfileprocessing.md":"DGLPSf2v","devdoc_readme.md":"D15419X_","devdoc_roxie.md":"C6oOX-36","devdoc_securityconfig.md":"C2-pRa18","devdoc_securityuserauthentication.md":"DbBHZTGf","devdoc_styleguide.md":"6RHWelMX","devdoc_userbuildassets.md":"RFQFpFTM","devdoc_userdoc_azure_tipsandtricks.md":"2OyOG7Og","devdoc_userdoc_azuretipstricks.md":"BUnTMakG","devdoc_userdoc_blogs.md":"D5ZHWDBF","devdoc_userdoc_copilot_copilotprompttips.md":"_YhzUQ6a","devdoc_userdoc_readme.md":"6WX0TGeB","devdoc_userdoc_roxie_faq.md":"Cbz7RPiY","devdoc_userdoc_troubleshoot_clientstoolissues.md":"Buqpsc4y","devdoc_userdoc_wikiguidelines.md":"BcZTSYE9","devdoc_versionsupport.md":"DqUAdEd9","devdoc_workunits.md":"DJg1TI-D","ecl_ecl-bundle_documentation.md":"rUiAyrKM","ecllibrary_styleguide.md":"CjMQ9yWq","index.md":"C4lg4w25","readme.md":"aSZY6Tfx","system_httplib_readme.md":"WIkDaVQZ","system_masking_include_readme.md":"CNC1sarR","system_masking_plugins_datamasker_readme.md":"DKeIe1BA","system_security_plugins_jwtsecurity_readme.md":"B1LGJUHj","testing_regress_cleanupreadme.md":"BWXfHXn3","testing_regress_ecl_analyzers_corporate_tmp_readme.md":"KZkrkEkE","testing_regress_ecl_readme.md":"DWXau1FL","tools_esdlcmd_readme.md":"D3nMaAu6","tools_esp-api_readme.md":"WcTRZBDR","tools_tagging_readme.md":"CdzS0CPr"} diff --git a/index.html b/index.html new file mode 100644 index 00000000000..56b280b0cb7 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + HPCC Platform + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/system/httplib/README.html b/system/httplib/README.html new file mode 100644 index 00000000000..9424eba58b4 --- /dev/null +++ b/system/httplib/README.html @@ -0,0 +1,310 @@ + + + + + + cpp-httplib | HPCC Platform + + + + + + + + + + + + + +
Skip to content

cpp-httplib

A C++11 single-file header-only cross platform HTTP/HTTPS library.

It's extremely easy to setup. Just include httplib.h file in your code!

Server Example

c++
#include <httplib.h>
+
+int main(void)
+{
+  using namespace httplib;
+
+  Server svr;
+
+  svr.Get("/hi", [](const Request& req, Response& res) {
+    res.set_content("Hello World!", "text/plain");
+  });
+
+  svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
+    auto numbers = req.matches[1];
+    res.set_content(numbers, "text/plain");
+  });
+
+  svr.Get("/body-header-param", [](const Request& req, Response& res) {
+    if (req.has_header("Content-Length")) {
+      auto val = req.get_header_value("Content-Length");
+    }
+    if (req.has_param("key")) {
+      auto val = req.get_param_value("key");
+    }
+    res.set_content(req.body, "text/plain");
+  });
+
+  svr.Get("/stop", [&](const Request& req, Response& res) {
+    svr.stop();
+  });
+
+  svr.listen("localhost", 1234);
+}

Post, Put, Delete and Options methods are also supported.

Bind a socket to multiple interfaces and any available port

cpp
int port = svr.bind_to_any_port("0.0.0.0");
+svr.listen_after_bind();

Static File Server

cpp
// Mount / to ./www directory
+auto ret = svr.set_mount_point("/", "./www");
+if (!ret) {
+  // The specified base directory doesn't exist...
+}
+
+// Mount /public to ./www directory
+ret = svr.set_mount_point("/public", "./www");
+
+// Mount /public to ./www1 and ./www2 directories
+ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
+ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
+
+// Remove mount /
+ret = svr.remove_mount_point("/");
+
+// Remove mount /public
+ret = svr.remove_mount_point("/public");
cpp
// User defined file extension and MIME type mappings
+svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
+svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
+svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");

The followings are built-in mappings:

ExtensionMIME Type
txttext/plain
html, htmtext/html
csstext/css
jpeg, jpgimage/jpg
pngimage/png
gifimage/gif
svgimage/svg+xml
icoimage/x-icon
jsonapplication/json
pdfapplication/pdf
jsapplication/javascript
wasmapplication/wasm
xmlapplication/xml
xhtmlapplication/xhtml+xml

NOTE: These the static file server methods are not thread safe.

Logging

cpp
svr.set_logger([](const auto& req, const auto& res) {
+  your_logger(req, res);
+});

Error handler

cpp
svr.set_error_handler([](const auto& req, auto& res) {
+  auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
+  char buf[BUFSIZ];
+  snprintf(buf, sizeof(buf), fmt, res.status);
+  res.set_content(buf, "text/html");
+});

'multipart/form-data' POST data

cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
+  auto size = req.files.size();
+  auto ret = req.has_file("name1");
+  const auto& file = req.get_file_value("name1");
+  // file.filename;
+  // file.content_type;
+  // file.content;
+});

Receive content with Content receiver

cpp
svr.Post("/content_receiver",
+  [&](const Request &req, Response &res, const ContentReader &content_reader) {
+    if (req.is_multipart_form_data()) {
+      MultipartFormDataItems files;
+      content_reader(
+        [&](const MultipartFormData &file) {
+          files.push_back(file);
+          return true;
+        },
+        [&](const char *data, size_t data_length) {
+          files.back().content.append(data, data_length);
+          return true;
+        });
+    } else {
+      std::string body;
+      content_reader([&](const char *data, size_t data_length) {
+        body.append(data, data_length);
+        return true;
+      });
+      res.set_content(body, "text/plain");
+    }
+  });

Send content with Content provider

cpp
const size_t DATA_CHUNK_SIZE = 4;
+
+svr.Get("/stream", [&](const Request &req, Response &res) {
+  auto data = new std::string("abcdefg");
+
+  res.set_content_provider(
+    data->size(), // Content length
+    "text/plain", // Content type
+    [data](size_t offset, size_t length, DataSink &sink) {
+      const auto &d = *data;
+      sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
+      return true; // return 'false' if you want to cancel the process.
+    },
+    [data] { delete data; });
+});

Without content length:

cpp
svr.Get("/stream", [&](const Request &req, Response &res) {
+  res.set_content_provider(
+    "text/plain", // Content type
+    [&](size_t offset, size_t length, DataSink &sink) {
+      if (/* there is still data */) {
+        std::vector<char> data;
+        // prepare data...
+        sink.write(data.data(), data.size());
+      } else {
+        done(); // No more data
+      }
+      return true; // return 'false' if you want to cancel the process.
+    });
+});

Chunked transfer encoding

cpp
svr.Get("/chunked", [&](const Request& req, Response& res) {
+  res.set_chunked_content_provider(
+    [](size_t offset, DataSink &sink) {
+      sink.write("123", 3);
+      sink.write("345", 3);
+      sink.write("789", 3);
+      sink.done(); // No more data
+      return true; // return 'false' if you want to cancel the process.
+    }
+  );
+});

'Expect: 100-continue' handler

As default, the server sends 100 Continue response for Expect: 100-continue header.

cpp
// Send a '417 Expectation Failed' response.
+svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
+  return 417;
+});
cpp
// Send a final status without reading the message body.
+svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
+  return res.status = 401;
+});

Keep-Alive connection

cpp
svr.set_keep_alive_max_count(2); // Default is 5

Timeout

c++
svr.set_read_timeout(5, 0); // 5 seconds
+svr.set_write_timeout(5, 0); // 5 seconds
+svr.set_idle_interval(0, 100000); // 100 milliseconds

Set maximum payload length for reading request body

c++
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB

Server-Sent Events

Please see Server example and Client example.

Default thread pool support

ThreadPool is used as a default task queue, and the default thread count is set to value from std::thread::hardware_concurrency().

You can change the thread count by setting CPPHTTPLIB_THREAD_POOL_COUNT.

Override the default thread pool with yours

cpp
class YourThreadPoolTaskQueue : public TaskQueue {
+public:
+  YourThreadPoolTaskQueue(size_t n) {
+    pool_.start_with_thread_count(n);
+  }
+
+  virtual void enqueue(std::function<void()> fn) override {
+    pool_.enqueue(fn);
+  }
+
+  virtual void shutdown() override {
+    pool_.shutdown_gracefully();
+  }
+
+private:
+  YourThreadPool pool_;
+};
+
+svr.new_task_queue = [] {
+  return new YourThreadPoolTaskQueue(12);
+};

Client Example

c++
#include <httplib.h>
+#include <iostream>
+
+int main(void)
+{
+  httplib::Client cli("localhost", 1234);
+
+  if (auto res = cli.Get("/hi")) {
+    if (res->status == 200) {
+      std::cout << res->body << std::endl;
+    }
+  } else {
+    auto err = res.error();
+    ...
+  }
+}

NOTE: Constructor with scheme-host-port string is now supported!

c++
httplib::Client cli("localhost");
+httplib::Client cli("localhost:8080");
+httplib::Client cli("http://localhost");
+httplib::Client cli("http://localhost:8080");
+httplib::Client cli("https://localhost");

GET with HTTP headers

c++
httplib::Headers headers = {
+  { "Accept-Encoding", "gzip, deflate" }
+};
+auto res = cli.Get("/hi", headers);

or

c++
cli.set_default_headers({
+  { "Accept-Encoding", "gzip, deflate" }
+});
+auto res = cli.Get("/hi");

POST

c++
res = cli.Post("/post", "text", "text/plain");
+res = cli.Post("/person", "name=john1&note=coder", "application/x-www-form-urlencoded");

POST with parameters

c++
httplib::Params params;
+params.emplace("name", "john");
+params.emplace("note", "coder");
+
+auto res = cli.Post("/post", params);

or

c++
httplib::Params params{
+  { "name", "john" },
+  { "note", "coder" }
+};
+
+auto res = cli.Post("/post", params);

POST with Multipart Form Data

c++
httplib::MultipartFormDataItems items = {
+  { "text1", "text default", "", "" },
+  { "text2", "aωb", "", "" },
+  { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
+  { "file2", "{\n  \"world\", true\n}\n", "world.json", "application/json" },
+  { "file3", "", "", "application/octet-stream" },
+};
+
+auto res = cli.Post("/multipart", items);

PUT

c++
res = cli.Put("/resource/foo", "text", "text/plain");

DELETE

c++
res = cli.Delete("/resource/foo");

OPTIONS

c++
res = cli.Options("*");
+res = cli.Options("/resource/foo");

Timeout

c++
cli.set_connection_timeout(0, 300000); // 300 milliseconds
+cli.set_read_timeout(5, 0); // 5 seconds
+cli.set_write_timeout(5, 0); // 5 seconds

Receive content with Content receiver

c++
std::string body;
+
+auto res = cli.Get("/large-data",
+  [&](const char *data, size_t data_length) {
+    body.append(data, data_length);
+    return true;
+  });
cpp
std::string body;
+
+auto res = cli.Get(
+  "/stream", Headers(),
+  [&](const Response &response) {
+    EXPECT_EQ(200, response.status);
+    return true; // return 'false' if you want to cancel the request.
+  },
+  [&](const char *data, size_t data_length) {
+    body.append(data, data_length);
+    return true; // return 'false' if you want to cancel the request.
+  });

Send content with Content provider

cpp
std::string body = ...;
+
+auto res = cli_.Post(
+  "/stream", body.size(),
+  [](size_t offset, size_t length, DataSink &sink) {
+    sink.write(body.data() + offset, length);
+    return true; // return 'false' if you want to cancel the request.
+  },
+  "text/plain");

With Progress Callback

cpp
httplib::Client client(url, port);
+
+// prints: 0 / 000 bytes => 50% complete
+auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
+  printf("%lld / %lld bytes => %d%% complete\n",
+    len, total,
+    (int)(len*100/total));
+  return true; // return 'false' if you want to cancel the request.
+}
+);

progress

Authentication

cpp
// Basic Authentication
+cli.set_basic_auth("user", "pass");
+
+// Digest Authentication
+cli.set_digest_auth("user", "pass");
+
+// Bearer Token Authentication
+cli.set_bearer_token_auth("token");

NOTE: OpenSSL is required for Digest Authentication.

Proxy server support

cpp
cli.set_proxy("host", port);
+
+// Basic Authentication
+cli.set_proxy_basic_auth("user", "pass");
+
+// Digest Authentication
+cli.set_proxy_digest_auth("user", "pass");
+
+// Bearer Token Authentication
+cli.set_proxy_bearer_token_auth("pass");

NOTE: OpenSSL is required for Digest Authentication.

Range

cpp
httplib::Client cli("httpbin.org");
+
+auto res = cli.Get("/range/32", {
+  httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
+});
+// res->status should be 206.
+// res->body should be "bcdefghijk".
cpp
httplib::make_range_header({{1, 10}, {20, -1}})      // 'Range: bytes=1-10, 20-'
+httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599'
+httplib::make_range_header({{0, 0}, {-1, 1}})        // 'Range: bytes=0-0, -1'

Keep-Alive connection

cpp
httplib::Client cli("localhost", 1234);
+
+cli.Get("/hello");         // with "Connection: close"
+
+cli.set_keep_alive(true);
+cli.Get("/world");
+
+cli.set_keep_alive(false);
+cli.Get("/last-request");  // with "Connection: close"

Redirect

cpp
httplib::Client cli("yahoo.com");
+
+auto res = cli.Get("/");
+res->status; // 301
+
+cli.set_follow_location(true);
+res = cli.Get("/");
+res->status; // 200

Use a specitic network interface

NOTE: This feature is not available on Windows, yet.

cpp
cli.set_interface("eth0"); // Interface name, IP address or host name

OpenSSL Support

SSL support is available with CPPHTTPLIB_OPENSSL_SUPPORT. libssl and libcrypto should be linked.

NOTE: cpp-httplib currently supports only version 1.1.1.

c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
+
+SSLServer svr("./cert.pem", "./key.pem");
+
+SSLClient cli("localhost", 8080);
+cli.set_ca_cert_path("./ca-bundle.crt");
+cli.enable_server_certificate_verification(true);

Compression

The server can applie compression to the following MIME type contents:

  • all text types except text/event-stream
  • image/svg+xml
  • application/javascript
  • application/json
  • application/xml
  • application/xhtml+xml

Zlib Support

'gzip' compression is available with CPPHTTPLIB_ZLIB_SUPPORT. libz should be linked.

Brotli Support

Brotli compression is available with CPPHTTPLIB_BROTLI_SUPPORT. Necessary libraries should be linked. Please see https://github.com/google/brotli for more detail.

Compress request body on client

c++
cli.set_compress(true);
+res = cli.Post("/resource/foo", "...", "text/plain");

Compress response body on client

c++
cli.set_decompress(false);
+res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
+res->body; // Compressed data

Split httplib.h into .h and .cc

bash
> python3 split.py
+> ls out
+httplib.h  httplib.cc

NOTE

g++

g++ 4.8 and below cannot build this library since <regex> in the versions are broken.

Windows

Include httplib.h before Windows.h or include Windows.h by defining WIN32_LEAN_AND_MEAN beforehand.

cpp
#include <httplib.h>
+#include <Windows.h>
cpp
#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <httplib.h>

Note: Cygwin on Windows is not supported.

License

MIT license (© 2020 Yuji Hirose)

Special Thanks To

These folks made great contributions to polish this library to totally another level from a simple toy!

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/system/masking/include/readme.html b/system/masking/include/readme.html new file mode 100644 index 00000000000..ca296a04042 --- /dev/null +++ b/system/masking/include/readme.html @@ -0,0 +1,116 @@ + + + + + + Data Masking Framework | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Data Masking Framework

This is a high level description of the data obfuscation framework defined in system/masking/include. The framework includes a platform component, the engine, that exposes obfuscation logic supplied by dynamically loaded plugin libraries. The framework can be used to obfuscate sensitive data, such as passwords and PII. Possible use cases including preventing trace output from revealing sensitive values and partially masking values in situations where a user needs to see "enough" of a value to confirm its correctness without seeing all of the value (e.g., when a user is asked to confirm the last four digits of an account number).

FileContents
datamasking.hDeclares the core framework interfaces. Most are used by the engine and plugins.
datamaskingengine.hppDefines the engine component from the core engine interface.
datamaskingplugin.hppDefines a set of classes derived from the core plugin interfaces that can be used to provide rudimentary obfuscation. It is expected that the some or all classes will be subclassed, if not replaced outright, in new plugins.
datamaskingshared.hppDefines utilities shared by engine and plugin implementations.

Glossary

Domain

A domain is a representation of the obfuscation requirements applicable to a set of data.

Consider that the data used to represent individuals likely differs between countries. With different data, requirements for obfuscation may reasonably be expected to vary. Assuming that requirements do change between countries, each country's requirements could logically constitute a separate domain. The capacity to define multiple domains does not create a requirement to do so.

Requirements can change over time. To support this, a domain can be seen as a collection of requirement snapshots where each snapshot defines the complete set of requirements for the domain at a point in time. Snapshots are referenced by unique version numbers, which should be sequential starting at 1.

Obfuscation is always applied based on a single snapshot of a domain's requirements.

Domains are represented in the framework interface as text identifiers. Each distinct domain is identified by at least one unique identifier.

Masker

A masker is a provider of obfuscation for a single snapshot of a domain's requirements. There are three masking operations defined in this framework. Each instance decides which of the three it will support, and how it will support them. The three operations are:

  • maskValue obfuscates individual values based on snapshot-defined meanings. For example, a value identified as a password might require complete obfuscation anywhere it appears, or an account number may require complete obfuscation in some cases and partial obfuscation in others.
  • maskContent obfuscates a variable number of values based on context provided by surrounding text. For example, the value of an HTTP authentication header or the text between <Password> and </Password> might require obfuscation. This operation can apply to both structured and unstructured text content.
  • maskMarkupValue obfuscates individual values based on their locationa within an in-memory representation of a structured document, such as XML or JSON. For example, the value of element Password might require obfuscation unconditionally, while the value of element Value might require obfuscation only if a sibling element named Name exists with value password. This operation relies on the caller's ability to supply context parsed from structured content.

A masker may be either stateless or stateful. With a stateless masker, identical input will produce identical output for every requested operation. A stateful masker, however, enables its user to affect operation outputs (i.e., identical input may not produce identical output for each operation).

Maskers are represented in the framework interface using IDataMasker, with IDataMaskerInspector providing access to less frequently used information about the domain.

Profile

A profile is a stateless masker. Each instance defines the requirements of one or more snapshots of a single domain.

Snapshots are versioned. Each profile declares a minimum, maximum, and default version. Masker operations apply to the default version. Other declared versions may be accessed using a stateful context, which the profile can create on demand.

Each instance must support at least one version of a domain's requirements. Whether an instance supports more than one version depends on the implementation and on user preference. A domain can be viewed as a collection of one or more profiles where each profile defines a unique set of requirement snapshots applicable to the same underlying data.

Refer to IDataMaskingProfile (extending IDataMasker) and IDataMaskingProfileInspector (extending IDataMaskerInspector) for additional information.

Context

A context is a stateful masker. Instantiated by and tightly coupled to a profile, it provides some user control over how masking operations are completed.

  • For a profile supporting multiple versions, the requirements of a non-default version may be applied.
  • Custom properties, defined by a profile, may be managed.
    • valuetype-set is a pre-defined property used to select the group of value types that may be masked by any operation.
    • rule-set is a pre-defined property used to select the group of rules that will be applied for maskContent requests.
    • Profile implementations may define additional properties as needed. For example, one might define mask-pattern to override the default obfuscation pattern to avoid replicating (and complicating) configurations just to change the appearance of obfuscated data.
  • Trace output produced by operation requests, including errors and warnings encountered during request processing, can be controlled per context. This does not affect the operation output, per-se, but provides compatibility with transactional trace output control.

Refer to IDataMaskingProfileContext (extending IDataMasker) and IDataMaskingProfileContextInspector (extending IDataMaskerInspector) for additional information.

Value Type

Each snapshot defines one or more value types. A value type is a representation of the requirements pertaining to a particular concept of a domain datum. Requirements include:

  • instructions for identifying occurrences based on contextual clues found in a body of text; and
  • instructions for applying obfuscation to an identified value.

A Social Security Number, or SSN, is a U.S.-centric datum that requires obfuscation and for which a value type may be defined. Element names associated with an SSN may include, but are not limited to, SSN and SOCS; the value type is expected to identify all such names used within the domain. SSN occurrences are frequently partially masked, with common formats being to mask only the first four or the last five digits of the nine digit number; the value type defines which formats are available besides the default of masking all characters.

Value types are represented in the framework using IDataMaskingProfileValueType. They are accessed through the IDataMaskerInspector interface.

Mask Style

A mask style describes how obfuscation is applied to a value. It is always defined in the context of a value type, and a value type may define multiple.

A value type is not required to define any mask styles. If none are defined, all value characters are obfuscated. If the requested mask style is not defined, the default obfuscation occurs; the value type will not attempt to guess which of the defined styles is appropriate.

Mask styles are represented in the framework using IDataMaskingProfileMaskStyle. They are accessed through the IDataMaskingProfileValueType interface.

Rule

A rule contains the information necessary to locate at least one occurrence of a value type datum to be obfuscated. It is always defined in the context of a value type, and a value type may define multiple.

maskValue requests do not use rules. maskContent requests rely on rules for locating affected values, with the relationship between a profile and its rules an implementation detail. maskMarkupValue requests, when implemented, may also use rules or may take an entirely different approach.

Rules are represented in the framework as an abastract concept that cannot be inspected individually. Inspection may be used to establish the presence of rules, but not to examine individual instances.

Plugin

The combination of a shared library and entry point function describes a plugin. The input to a plugin is a property tree describing one or more profiles. The output of an entry point function is an iterator of profiles. The profiles created by the function may all be associated with the same domain, but this is not required.

Plugin results are represented in the framework using IDataMaskingProfileIterator.

Engine

An engine is the platform's interface to obfuscation. It loads domains by loading one or more plugins. Plugins yield profiles, from which domains are inferred.

Once configured with at least one domain, a caller can obtain obfuscation in multiple ways:

  1. As an instance of a stateless masker, an engine can provide obfuscation. The default version of the default profile of the default domain will be used, and no custom context properties can be used. This is the simplest integration, appropriate for use when the host process implicitly trusts that the default configuration is sufficient.
  2. An engine may be used to obtain a stateless profile for a specific domain. If no version is requested, the default profile of the requested domain will be used. If a version is specified, the domain profile supporting the requested version is used. This usage supports host processes that are aware of domains and need to use specific instances.
  3. An engine may be used to obtain a context tied to a specific version of a domain. The default domain may be requested with an empty string. The default version may be requested by passing 0. With a context, a caller may manipulate further the environment for obfuscation requests.

Engines are represented in the framework using IDataMaskingEngine (extending IDataMasker) and IDataMaskingEngineInspector for additional information.

Environment

This section assumes a context is in use. Absent a context, only the default state of the default version of a profile can be used.

The framework reserves multiple custom property names, which are described in subsequent subsections. It also allows implementations to define additional properties using any non-reserved name.

Suppose an implementation defines a property to override the default obfuscation pattern. Let's call this property default-pattern. A caller could interrogate a masker to know if default-pattern is accepted. If accepted, setProperty("default-patterns", "#") can be used to register an override.

All custom properties, whether defined in the framework or in third party libraries, are managed using a generic context interface. This interface includes:

bool hasProperties() cosnt
+bool hasProperty(const char* name) const
+const char* queryProperty(const char* name) const
+bool setProperty(const char* name, const char* value)
+bool removeProperty(const char* name)

Value Type Sets

bool setProperty(const char* name, const char* value);

Overview

The abstraction includes the concept of sets of related value types. All value types should be assigned membership in at least one set. One set should be selected by default. The custom context property valuetype-set is used to select a different set.

The rationale for this is an expectation that certain data always requires obfuscation. A password, for example, would never not require obfuscation. Other data may only require obfuscacation in certain situations, such as when required by individual customer agreements. Callers should not be required to complete additional steps to act on data that must always be obfuscated, and should not need to know which data falls in which category.

The set name "*" is reserved to select all value types regardless of their defined set membership. This mechanism is intended to assist with compatibility checks, and should be used with care in other situations.

Runtime Compatibility

Use acceptsProperty to determine whether a snapshot recognizes a custom property name. Use usesProperty to learn if the snapshot includes references to the custom property name. Acceptance implies awareness of the set (and what it represents) even if selecting it will not change the outcome of any masking request. Usage implies that selecting the set will change the outcome of at least one masking operation.

The property valuetype-set is used to select a named set. A check of this property addresses whether the concept of a set is accepted or used by the snapshot.

For improved compatibility checks, implementations are encouraged to reserve additional property names that enable checks for individual set names. For each set name, foo, the included implementations report property valuetype-set:foo as used.

Implementation

The included implementations allow membership in either a default, unnamed set or in any number of named sets. Absent a contextual request for a named set, the unnamed set is selected by default. With a contextual set request, the members of the named set are selected in addition to members of the unnamed set. Members of the unnamed set are never not selected.

Unnamed set membership cannot be defined explicitly. Because this set is always selected, assigning a value type to both a named and the unnamed set is redundant - the type will be selected whether the named set is requested or not.

Example 1a: default value type

profile:
+  valueType:
+    - name: type1

Value type type1 belongs to the default, unnamed value type set. Property valuetype-set is accepted, but not used; an attempt to use it will change no results.

Example 1b: sample set memberships

profile:
+  valueType:
+    - name: type1
+    - name: type2
+      memberOf:
+        - name: set1
+        - name: set2
+    - name: type3
+      memberOf:
+        - name: set2
+        - name: set3
+    - name: type4
+      memberOf:
+        - name: set1
+    - name: type5
+      memberOf:
+        - name: set3

Extending the previous example, five types and four sets are in use. Property valuetype-set is both accepted and used, as it can now impact results. Properties valuetype-set:set1, valuetype-set:set2 and valuetype-set:set3 are also both accepted and used, but are intended only for use with compatibility checks.

This table shows which types are selected for each requested set:

unnamedset1set2set3*
type1YYYYY
type2NYYNY
type3NNYYY
type4NYNNY
type5NNNYY

Example 1c: sample accepted sets

profile:
+  valueType:
+    - name: type1
+    - name: type2
+      memberOf:
+        - name: set1
+        - name: set2
+    - name: type3
+      memberOf:
+        - name: set2
+        - name: set3
+    - name: type4
+      memberOf:
+        - name: set1
+    - name: type5
+      memberOf:
+        - name: set3
+  property:
+    - name: 'valuetype-set:set4'
+    - name: 'valuetype-set:set5'

Continuing the previous example, the profile has declared acceptance of two additional sets using properties valuetype-set:set4 and valuetype-set:set5. Selection of these sets will not change results, but a caller might accept acceptance of a set as sufficient to establish compatibility.

Rule Sets

bool setProperty(const char* name, const char* value);

Overview

The abstraction includes the concept of sets of related rules. All rules should be assigned membership in at least one set. One set should be selected by default. The custom context property rule-set is used to select a different set.

The rationale for this is similar to yet different than that of value type sets. Instead of one set of rules that are always selected, backward compatibility with a proprietary legacy implementation requires that the default set be replaced by an alternate collection.

The set name "*" is reserved to select all rules regardless of their defined set membership. This does not override constraints imposed by the current value type set. This mechanism is intended to assist with compatibility checks, and should be used with care in other situations.

Runtime Compatibility

Use acceptsProperty to determine whether a snapshot recognizes a custom property name. Use usesProperty to learn if the snapshot includes references to the custom property name. Acceptance implies awareness of the set (and what it represents) even if selecting it will select no rules. Usage implies that selecting the set will select at least one rule.

The property rule-set is used to select a named set. A check of this property addresses whether the concept of a set is accepted or used by the snapshot.

For improved compatibility checks, implementations are encouraged to reserve additional property names that enable checks for individual set names. For each set name, foo, the included implementations report property rule-set:foo as used.

Implementation

The included implementations allow membership in any number of named sets as well as a default, unnamed set. Absent a contextual request for a named set, the unnamed set is selected by default. With a contextual set request, only the members of the requested set are selected.

Unlike value type sets, the unnamed set is not always selected. Because of this difference, a rule may be assigned explicit membership in the unnamed set. This is optional for rules that belong only to the unnamed set and is required for rules meant to be selected as part of the unnamed set and one or more named sets.

Example 2: rule set memberships

profile:
+  valueType:
+    name: type1
+    rule:
+      - name: foo
+      - memberOf:
+          - name: ''
+      - memberOf:
+          - name: ''
+          - name: set1
+      - memberOf:
+          - name: set1
+  property:
+    name: 'rule-set:set2'

The rules described here are intentionally incomplete, showing only what is necessary for this example. Four rules are defined:

  1. The rule named foo implicitly belongs to the unnamed set.
  2. The second rule explicitly belongs to the unnamed set.
  3. The third rule explicitly belongs to the unnamed set and to set1.
  4. The fourth rule belongs to set1.

property rule-set is both accepted and used. Properties rule-set: and rule-set:set1 are both accepted and used, intended for use with compatibility checks. Property rule-set:set2 is accepted.

Operations

maskValue

bool maskValue(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length, bool conditionally) const;
+bool maskValueConditionally(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length) const;
+bool maskValueUnconditionally(const char* valueType, const char* maskStyle, char* buffer, size_t offset, size_t length) const;

Overview

Given a single domain datum, obfuscate the content "as needed". The framework anticipates two interpretations of "as needed", either conditional or unconditional:

  • Conditional means that values of a named type are only obfuscated if the named type is known by and selected in the snapshot. This usage assumes that the profile is the authoritative source for obfuscation requirements. The profile's consumer may ask for any value to be obfuscated, but the profile is not required to act.
  • Unconditional means that all value obfuscation requests will result in obfuscation. The named type is not required to be known by or selected in the snapshot. This usage assumes that the profile's consumer is the authoritative source for obfuscation requirements. If a consumer asks for a value to be obfuscated, the profile must act.

Each plugin will define its own interpretation of "as needed". The API distinction between conditional and unconditional is a hint intended to guide implementations capable of both, and should be ignored by implementations that are not.

The buffer, offset, and length parameters define what is assumed to be a single domain datum. That is, every character in the given character range is subject to obfuscation.

The valueType parameter determines if obfuscation is required, with conditionally controlling whether an unrecognized valueType is ignored (true) or forced to obfuscate (false).

The maskStyle parameter may be used to affect the nature of obfuscation to be applied. If the parameter names a defined mask style, that obfuscation format is applied. If the parameter does not name a define mask style, the value type's default obfuscation format is applied.

Runtime Compatibility

The canMaskValue method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The hasValueType method of IDataMaskerInspector can be used to check if a given name is selected in the snapshot. The reserved name "*" may be used to detect unconditional obfuscation support when available in the snapshot. Names defined yet unselected in the snapshot are not directly detectable, but can be detected by comparing results of using multiple context configurations.

The name "*" is reserved by the abstraction for compatibility checks.

To confirm the availability of a specific mask style first requires confirmation of the value type to which the mask style is related. Use queryValueType to obtain the defined instance and, from the result, call queryMaskStyle to confirm the existence of the mask stye. For each of these, corresponding get... methods are defined to obtain new references to the objects. The methods are declared by IDataMaskerInspector.

Implementations

The included implementations are inherently conditional. Obfuscation depends on matching valueType with a selected value type instance in the snapshot, where an instance is selected if it belongs to the currently selected value type set.

Unconditional obfuscation is enabled in profiles that include a value type instance named "*". If an instance with this name is selected by the currently selected value type set, all values for undefined value types will be obfuscated. Values for defined but unselected value types will not be obfuscated.

  • A value of type "foo" will be obfuscated when type "foo" is selected by the currently selected value type set.
  • A value of type "foo" will be obfuscated when type "foo" is undefined and type "*" is selected by the currently selected value type set.
  • A value of type "foo" will not be obfuscated when type "foo" is defined but unselected by the currently selected value type set.

Example 3a: conditional obfuscation

valueType:
+  - name: type1
+  - name: type2
+    memberOf:
+      - name: set1

This snippet declares two value types, with two total sets. The table shows which values will be conditionally obfuscated based on a the combination of valueType and the currently selected value type set.

valueTypeSelected SetObfuscated
type1N/AYes
type1set1Yes
type2N/ANo
type2set1Yes
typeNN/ANo
typeNset1No

"typeN" in the table represents any named type, excluding the reserved "*", not explicitly defined in the profile.

Example 3b: unconditional obfuscation

valueType:
+  - name: type1
+  - name: type2
+    memberOf:
+      - name: set1
+  - name: *

Extending the previous example with a third value type, values of unknown type are now obfuscated. Values of known but unselected type remain unobfuscated.

valueTypeSelected SetObfuscated
type1N/AYes
type1set1Yes
type2N/ANo
type2set1Yes
typeNN/AYes
typeNset1Yes

The name reserved by the abstraction to detect support for unconditional obfuscation is the same name reserved by the implementation to define that support.

"typeN" in the table represents any named type, excluding the reserved "*", not explicitly defined in the profile.

maskContent

bool maskContent(const char* contentType, char* buffer, size_t offset, size_t length) const;

Overview

The buffer, offset, and length parameters define what is assumed to be a blob of content that may contain zero or more occurrences of domain data. The blob is expected to contain sufficient context allowing a snapshot's rules to locate any included occurrences.

The context parameter optionally influences which rules are selected in a snapshot, while the contentType parameter offers a hint as to the blob's data format. Each snapshot rule may be assigned a format to which it applies. Operation requests may be optimized by limiting the number of selected rules applied by describing the format. Selected rules not assigned a format will be applied in all requests.

There is no equivalent to the unconditional mode offered by maskValue. Content obfuscation is always conditional on matching rules.

Runtime Compatibility

The canMaskContent method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The hasRule method of IDataMaskerInspector indicates if at least one rule is selected in the snapshot for a given content type. Rules not associated with any content type may be interrogated using an empty string.

It is not possible to discern any information about the selected rules, such as their associated value types or the cues used to apply them in a buffer.

Implementations

The groundwork exists for two types of rules, serial and parallel. Serial rules, as the name implies, are evaluated sequentially. Parallel rules, on the other hand, are evaluated concurrently. Concurrent evaluation is expected to be more efficient than sequential, but no concurrent implementation is provided.

The included sequential implementation should be viewed as a starting point. Each rule defines a start token and an optional end token. For each occurrence of the start token in the blob, a corresponding search for an end token (newline if omitted), is performed. When both parts are found, the content between is obfuscated using the associated value type's default mask style.

Traversing a potentially large blob of text once per rule is inefficient. A concurrent implementation is in development to improve performance and capabilities. A domain originated using the original implementation and migrated to the new implementation illustrates one domain implemented by multiple plugins and, by extension, multiple profiles.

maskMarkupValue

bool maskMarkupValue(const char* element, const char* attribute, char* buffer, size_t offset, size_t length, IDataMaskingDependencyCallback& callback) const;

Overview

Where maskValue obfuscates individual values based on what the caller believes the value to be, and maskContent obfuscates any number of values it identifies based on cues found in surrounding text, maskMarkupValue obfuscastes individual values based on cues not provided to it.

Given a value and a relative location within a structure document, an implementation must decide whether obfuscation is required, may be required, or is not required. If required, it can obfuscate immediately. If not required, and can return immediately. If it might be required, the implementation must request the context it needs from the caller in order to make a final determination.

A request to mask the content of an element named Value might depend on the content of a sibling element named Name. If value of Value probably does not require obfuscation when the value of Name is city, but almost certainly does require obfuscation when the value of Name is password. When processing the request for Value, an implementation must ask for the value of a sibline named Name to decide whether or not to obfuscate the data.

There is no equivalent to the unconditional mode offered by maskValue. Markup value obfuscation presumes that the caller is traversing a structured document without awareness of which values require obfuscation. If the caller knows which values require obfuscation, it should use maskValue on those specific values.

Runtime Compatibility

    virtual bool getMarkupValueInfo(const char* element, const char* attribute, const char* buffer, size_t offset, size_t length, IDataMaskingDependencyCallback& callback, DataMaskingMarkupValueInfo& info) const = 0;

The canMaskMarkupValue method of IDataMasker can be used to establish whether a snapshot supports this operation. A result of true indicates that the plugin is capable of performing obfuscation in response to a request. Whether a combination of input parameters exists that results in obfuscation depends on the definition of the snapshot.

The getMarkupValueInfo method of IDataMaskerInspector determines if the value of an element or attribute requires obfuscation. If required, the info parameter will describe on completion how to perform the obfuscation using maskValue instead of maskMarkupValue. This is done to optimize performance by eliminating the need to reevaluate rules to re-identify a match.

Assme a value type representing passwords is defined. If given an element value containing a URL with an embedded password, obfuscation is required. But most likely not for the entire value. In addition to identifying the value type associated with value, the offset and length of the embedded substring requiring obfuscation are supplied; use of maskValue with these three values and no mask style will apply the default obfuscation only to the embedded password. Alternatively, if a mask style is supplied, use of maskValue with this style and the original value offset and length should obfuscate only the embedded password.

A callback interface is required for all calls to getMarkupValueInfo. The interface is only used when additional document context is required to make a determination about the requested value. In these cases, the checks are proximity-dependent and cannot be performed as part of load-time compatibility checks, when no document is available.

Implementations

TBD

Load-time Compatibility

bool checkCompatibility(const IPTree& requirements) const;

Overview

Use of runtime compatibility checks may be unavoidable in some circumstances, but reliance on this in every script is inefficient. It may also devalue trace output by omitting messaging required to debug an issue, a problem that might only be found when said messaging is needed.

The requirements parameter represents a standardized set of runtime compatibility checks to be applied. By default, each check must pass to establish compatibility. Explicitly optional checks may be included to detect conditions the caller is prepared to work around.

This can test which values will or won't be affected by maskValue, and which obfuscation styles are available. It can test which rule sets will impact maskContent. It specifically excludes maskMarkupValue checks that depend on the proximity of one value to another.

Returning to the earlier SSN example, a caller might require that an SSN value type will be obfuscated. It might also want to use a particilar mask style, but may be prepared for its absence. A caller may prefer to mask the first four digits but may accept masking the last five instead, or may omit certain trace messages.

Implementation

compatibility:
+  - context:
+      - domain: optional text
+        version: optional number
+        property:
+          - name: required text
+            value: required text
+    accepts:
+      - name: required text
+        presence: one of "r", "o", or "p"
+    uses:
+      - name: required text
+        presence: one of "r", "o", or "p"
+    operation:
+      - name: one of "maskValue", "maskContent", or "maskMarkupValue"
+        presence: one of "r", "o", or "p"
+    valueType
+      - name: required text
+        presence: one of "r", "o", or "p"
+        maskStyle:
+          - name: required text
+            presence: one of "r", "o", or "p"
+        Set:
+          - name: required text
+            presence: one of "r", "o", or "p"
+    rule:
+      - contentType: required text
+        presence: one of "r", "o", or "p"

The requirements parameter of checkCompatibility must be either a compatibility element or the parent of a collection of compatibility elements. Each instance may contain at most one context element, three operation elements (one per operation), and a variable number of accepts, uses, valueType, or rule elements.

The context element describes the target of an evaluation. It may include a domain identifier, or assume the default domain. It may include a version number, or assume the default snapshot of the domain. It may specify any number of custom context properties to select various profile elements in the snapshot.

The accepts and uses elements identify custom context property names either accepted or used within a snapshot. Acceptance indicates some part of a snapshot has declared an understanding of the named property. Usage indicates the some part of a snapshot will react to the named value being set. Usage implies acceptance.

To improve compatibility check capabilities, a snapshot may synthesize properties that, if set, will have no effect. Specifically, a caller may be more interested to know if a particular set name (for either value type or rule set membership) is used than to know that an unidentified set name is used.

The Operations element identifies which operations must be supported. Its purpose is to enforce availability of an operation when no more explicit requirements are given (for example, one may require maskContent without also requiring the presence of rules for any given content type). When more explicit requirements are given, the attributes of this element are redundant. If all attributes are redundant, the element may be omitted.

The valueType element describes all optional and mandatory requirements for using maskValue with a single value type name and optional mask style name. Set membership requirements can also be evaluated, which is most useful when compatibility/context/property/@name is "*".

The rule element describes all optional and mandatory requirements for using maskContent with a single content type value. Unlike evaluable value type set membership, rule set membership requirements cannot be evaluated.

In all elements described as accepting @presence, the acceptable values are

  • r: the check is required to pass
  • o: the check is optional
  • p: the check is prohibited from passing

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/system/masking/plugins/datamasker/readme.html b/system/masking/plugins/datamasker/readme.html new file mode 100644 index 00000000000..512ecf77416 --- /dev/null +++ b/system/masking/plugins/datamasker/readme.html @@ -0,0 +1,47 @@ + + + + + + HPCC Platform + + + + + + + + + + + + + +
Skip to content

libdatamasker.so

This implements a plugin library producing instances of IDataMaskingProfileIterator to be used by an IDataMaskingEngine instance. The library is written in C++, and this section is organized according to the namespace and class names used.

namespace DataMasking

CContext

Standard implementation of IDataMaskingProfileContext and IDataMaskingProfileContextInspector tightly coupled to a specific version of a specific profile instance. It manages custom properties that are accepted by the associated profile, and rejects attempts to set those not accepted. The rejection is one way of letting the caller know that something it might deem essential is not handled by the profile.

Depends on CProfile, an abstract implementation of IDataMaskingProfile and IDataMaskingProfileInspector.

CMaskStyle

Implementation of IDataMaskingProfileMaskStyle providing customized masked output of values. Configuration options include:

  • @maximumVersion is an optional version number, needed only when the style does not apply to the maximum version of the value type in which it is defined.
  • @minimumVersion is an optional version number, needed only when the style does not apply to the minumum version of the value type in which it is defined.
  • @name is a required unique identifier for the style.
  • @overrideDefault is an optional Boolean flag controlling whether the style should replace the default style for the value type in which it is defined.
  • @pattern is an optional character sequence (one or more characters in length) that will be used to mask value content.

CPartialMaskStyle

Am extension of CMaskStyle, this is intended to partially mask values, such as account numbers or telephone numbers, without assuming knowledge of the values. Configuration is generally expressed as:

[ [ @action ] [ @location ] @count  [ @characters ] ]
+

Where:

  • @action what to do, either keep or mask (default); and
  • @location where to do it, either first or last (default); and
  • @count how many characters to examine, a positive integer value; and
  • @characters type of characters to be considered, either numbers, letters, alphanumeric, or all (default)

All four values may be omitted when only the inherited options are needed. If any of the four values are given, count is required and the other three are optional.

A value substring containing at most @count instances of the character class denoted by @characters is identified. Character classes are ASCII numeric characters (numbers), ASCII alphabetic characters (letters), ASCII alphabetic and numeric characters (alphanumeric), or any characters (all).

The value substring is at the start of the value when @location is first, and at the end of the value when @location is last.

The value substring is masked when @action is mask. All of the value with the exception of the substring is masked when @action is keep.

CRule

A base class for rule implementations, this defines standard rule properties without providing any content knowledge for use with maskContent.

Configuration includes:

  • @contentType is an optional, user-defined, label describing the content format to which the rule should be applied. maskContent requests that indicate a content type should apply all rules that match the type in addition to all rules that omit a type.
  • memberOf is an optional and repeating element identifying user-defined set labels. Only rules with membership in the requested set are considered; in the absence of an explictly requested set, a default set with an empty name is implied.
  • memberOf/@name is a required user-defined set label.

rule instances will frequently be specific to a content format. For example, a rule applied to XML markup value may include dependencies on XML syntax which are not applicable when masking JSON content. Rules specific to a markup format can be associated with that format using @contentType. Requests to mask content of a type will apply all rules without a @contentType value or with a matching value, and will exclude all rules with a non-matching value.

CSerialTokenRule

An extension of CRule, this identifies content substrings to be masked based on matching start and end tokens in the content buffer. For each occurrence of a configured start token that is balanced by a corresponding configured end token, the characters between the tokens are masked.

This class may be used with TSerialProfile.

Configuration includes:

  • @endToken is an optional character sequence expected to immediately follow a content substring to be masked. This might be an XML element end tag, or a terminating double quote for a JSON value. A newline, i.e., \n, is assumed if omitted or empty, for masking values such as HTTP headers.
  • @matchCase is an optional Boolean flag controlling whether token matches are case sensitive (true) or insensitivie (false). Matches are case insensitive by default. The implementation is based on ASCII data; case sensitive comparisons of similarly encoded non-ASCI text can work, but case insensitive comparisons are not supported.
  • @startToken is a required character sequence expected to precede a content substring to be masked.

No content type knowledge is implied by this class. An instance with @contentType of xml does not inherently know how to find values in XML markup. The defined tokens must include characters such as < and > to match an element name and quotes to match attribute values.

TPlugin

An implementation of IDataMaskingProfileIterator used for transforming a configuration property tree into a collection of profiles to be returned by a library entry point function. Created profiles are of the same class, identified by the template parameter profile_t.

Configuration includes:

  • profile An optional and repeating element defining a profile. The content of this element depends on profile_t.
  • If and only if profile is not included as a child of the configuration node, the node itself will be treated as a configuration for profile_t. The name of the configuration node is not required to be profile.

TProfile

A concrete extension of CProfile, this manages value types and rules, providing a default implementation of maskValue but leaving other operations to subclasses.

Template parameters are:

  • valuetype_t identifies the concrete implementation of IDataMaskingProfileValueType to be instantiated during configuration.
  • rule_t identifies the concrete implementation of rules to be managed. Creation is assumed to be the responsibility of the value type.
  • context_t identifies the concrete implementation of IDataMaskingProfileContext to be instantiated on demand.

Configuration includes:

  • @defaultVersion is an optional version number identifying the version to be used when version 0 is requested. Omission or 0 implies the maximum version (which is configured before this value).
  • @domain is the required default identifier used to select this profile.
  • legacyDomain is an optional and repeating element identifying alternate identifiers by which the profile may be selected. This enables domain identifier naming conventions to be changed without breaking pre-existing references.
  • legacyDomain/@id is a required identifier of this profile.
  • @maximumVersion is an optional version number identifying the highest version supported by the configuration. Omission or 0 implies a value matching the minimum version (whether implicitly or explicitly defined).
  • @minimumVersion is an optional version number identifying the lowest version supported by the configuration. Omission or 0 implies 1.
  • @name is an optional label for the instance. If given, the value is used in trace output.
  • property is an optional and repeating element describing a custom context property recognized by the profile. The profile can declare awareness of properties without explicit use of them.
  • property/@name is a required context property name.
  • property/@minimumVersion is an optional version number indicating the lowest profile version aware of the property. Omission or 0 implies the profile minimum.
  • property/@maximumVersion is an optional version number indicating the highest profile version aware of the property. Omisssion or 0 implies the profile maximum.
  • valueType is an optional and repeating element describing the value types defined by the profile. Element content depends on the value of valuetype_t.

For profiles supporting a single version, value type names must be unique. For profiles supporting multiple versions, value type names may be repeated but must be unique for each version. To illustrate, consider this snippet:

profile:
+  minimumVersion: 1
+  maximumVersion: 2
+  valueType:
+    - name: foo
+      maximumVersion: 1
+    - name: foo
+      minimumVersion: 2
+    - name: bar
+    - name: bar
+      minimumVersion: 2
+

In the example, foo and bar are each defined twice. The redefinition of foo is acceptable because each instance applies to a different version. The redefinition of bar is invalid because both instances claim to apply to version 2.

The value type name * is reserved by this class. A profile that includes a value type named * supports unconditional value masking. maskValue requests specifying unknown value type names can fall back to this special type and force masking for these values. Without this type, maskValue masks what it thinks should be masked; with this type, it trusts the caller to request masking for only those values known to require it. Value types included in unselected value type sets are known to the profile and, as such, can be used to prevent specific typed values from ever being masked.

The requirement of a type definition instead of a simpler flag is to enable the definition of mask styles. It has the side effect of enabling the definition of rules. In theory, an entire profile could be defined using a single value type. This may make sense in some cases, but not in all. For example, a partial mask style intended for use with U.S. Social Security numbers could be inappropriately applied to a password. Use care when configuring this type.

TSerialProfile

An extension of TProfile that adds support for maskContent. It is assumed by maskContent that each applicable rule must be applied serially, i.e., one after the other, using an bool applyRule(buffer, length, context) interface.

Template parameters are unchanged from TProfile.

Configuration options are unchanged from TProfile.

TValueType

An implementation of IDataMaskingProfileValueType that manages mask styles and creates rules for the profile.

Template parameters are:

  • maskstyle_t identifies the concrete implementation of IDataMaskingProfileMaskStyle to be instantiated during configuration.
  • rule_t identifies the concrete implementation of rules to be created during configuration.

Configuration includes:

  • @maximumVersion is an optional version number, needed only when the type does not apply to the maximum version of the profile in which it is defined.
  • @minimumVersion is an optional version number, needed only when the type does not apply to the minumum version of the profile in which it is defined.
  • maskStyle is an optional and repeating element describing mask styles defined by the type. Element content depends on the value of maskstyle_t.
  • memberOf is an optional and repeating element identifying user-defined set labels. All value types that are not explicitly assigned set membership are considered for all masking requests. Value types assigned set membership are considered only when one of their assigned sets is selected with the request context.
  • memberOf/@name is a required user-defined set label.
  • @name is a required unique identifier for the type.
  • rule is an optional and repeating element describing rules defined by the type. Element content depends on the value of rule_t.

For value types supporting a single version, mask style names must be unique. For types supporting multiple versions, style names may be repeated but must be unique for each version. To illustrate, consider this snippet:

valueType:
+  minimumVersion: 1
+  maximumVersion: 2
+  maskStyle:
+    - name: foo
+      maximumVersion: 1
+    - name: foo
+      minimumVersion: 2
+    - name: bar
+    - name: bar
+      minimumVersion: 2
+

In the example, foo and bar are each defined twice. The redefinition of foo is acceptable because each instance applies to a different version. The redefinition of bar is invalid because both instances claim to apply to version 2.

Entry Point Functions

This section describes the entry point functions exported by the shared library. The library must export one function, and may export multiple functions.

The description of each entry point will identify which of the previously described classes is used to represent the returned collection of profiles. If a templated class is identified, the template parameters are also listed. Refer to the class descriptions for additional information.

newPartialMaskSerialToken

Returns a (possibly empty) collection of profiles supporting maskValue and maskContent operations.

  • The profile collection is an instance of TPlugin.
  • Collection profiles are implemented using TProfile.
  • Profile value types are implemented using TValueType.
  • Value type mask styles are implemented using CPartialMaskStyle.
  • Value type and profile rules are implemented using CSerialTokenRule.
  • Profile contexts are implemented using CContext.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/system/security/plugins/jwtSecurity/README.html b/system/security/plugins/jwtSecurity/README.html new file mode 100644 index 00000000000..8053f09dc36 --- /dev/null +++ b/system/security/plugins/jwtSecurity/README.html @@ -0,0 +1,31 @@ + + + + + + HPCC Platform + + + + + + + + + + + + + +
Skip to content

JWT Authorization Security Manager Plugin

The purpose of this plugin is to provide authentication and authorization capabilities for HPCC Systems users, with the credentials passed via valid JWT tokens.

The intention is to adhere as closely as possibly to the OpenID Connect (OIC) specification, which is a simple identity layer on top of the OAuth 2.0 protocol, while maintaining compatibility with the way HPCC Systems performs authentication and authorization today. More information about the OpenID Connect specification can be found at https://openid.net/specs/openid-connect-core-1_0.html.

One of the big advantages of OAuth 2.0 and OIC is that the service (in this case, HPCC Systems) never interacts with the user directly. Instead, authentication is performed by a trusted third party and the (successful) results are passed to the service in the form of a verifiable encoded token.

Unfortunately, HPCC Systems does not support the concept of third-party verification. It assumes that users -- really, any client application that operates as a user, including things like IDEs -- will submit username/password credentials for authentication. Until that is changed, HPCC Systems won't be able to fully adhere to the OIC specification.

We can, however, implement most of the specification. That is what this plugin does.

NOTE: This plugin is not available in a Windows build.

Code Documentation

Doxygen (https://www.doxygen.nl/index.html) can be used to create nice HTML documentation for the code. Call/caller graphs are also generated for functions if you have dot (https://www.graphviz.org/download/) installed and available on your path.

Assuming doxygen is on your path, you can build the documentation via:

cd system/security/plugins/jwtSecurity
+doxygen Doxyfile
+

The documentation can then be accessed via docs/html/index.html.

Theory of Operations

The plugin is called by the HPCC Systems esp process when a user needs to be authenticated. That call will contain the user's username and either a reference to a session token or a password. The session token is present only for already-authenticated users.

If the session token is not present, the plugin will call a JWT login service (also known as a JWT login endpoint) with the username and password, plus a nonce value for additional security.

That service authenticates the username/password credentials. If everything is good, the service constructs an OIC-compatible token that includes authorization information for that user and returns it to the plugin. The token is validated according to the OIC specification, including signature verification.

Note that token signature verification requires an additional piece of information. Tokens can be signed with a hash-based algorithm or with a public key-based algorithm (the actual algorithm used is determined by the JWT service). To verify either kind of algorithm, the plugin will need either the secret hash key or the public key that matches what the JWT service used. That key is read by the plugin from a file, and the file is determined by a configuration setting (see below). It is possible to change the contents of that file without restarting the esp process. Note, though, that the plugin may not notice that the file's contents have changed for several seconds (changes do not immediately take effect).

HPCC Systems uses a well-defined authorization scheme, originally designed around an LDAP implementation. That scheme is represented within the token as JWT claims. This plugin will unpack those claims and map to the authorization checks already in place within the HPCC Systems platform.

OIC includes the concept of refresh tokens. Refresh tokens enable a service to re-authorize an existing token without user intervention. Re-authorization typically happens due to a token expiring. Tokens should have a relatively short lifetime -- e.g. 15-30 minutes -- to promote good security and also give administrators the ability to modify a user's authorization while the user is logged in. This plugin fully supports refresh tokens by validating token lifetime at every authorization check and calling a JWT refresh service (also known as a JWT refresh endpoint) when needed. This largely follows the OIC specification.

Deviations From OIC Specification

  • Initial authentication: As stated above, the esp process will gather the username/password credentials instead of a third party, then send those credentials off to another service. In a true OIC configuration, the client process (the esp process) never sees user credentials and relies on an external service to gather them from the user.
  • The request made to the JWT login service is a POST HTTP or HTTPS call (depending on your configuration) containing four items in JSON format; example:
	{
+		"username": "my_username",
+		"password": "my_password",
+		"client_id": "https://myhpcccluster.com",
+		"nonce": "hf674DTRMd4Z1s"
+	}

Implications of Deviations

The most obvious outcome of this implementation is that a custom service/endpoint needs to be available. Or rather two services: One to handle the initial user login and one to handle token refreshes. Neither service precisely handles requests and replies in an OIC-compatible way, but the tokens themselves are OIC-compatible, which is good. That allows you to use third-party JWT libraries to construct and validate those tokens.

HPCC Systems Configuration Notes

Several items must be defined in the platform's configuration. Within configmgr, the jwtsecmgr Security Manager plugin must be added as a component and then modified according to your environment:

  • The URL or unique name of this HPCC Systems cluster, used as the client_id in token requests
  • Full URL to the JWT Login Endpoint (should be HTTPS, but not required)
  • Full URL to the JWT Refresh Endpoint (should be HTTPS, but not required)
  • Boolean indicating whether to accept self-signed certificates for those endpoints; defaults to false
  • Secrets vault key/name or subdirectory under /opt/HPCCSystems/secrets/esp in which the JWT key used for the chosen signature algorithm is stored; defaults to "jwt-security"
  • Default permission access level (either "Full" or "None"); defaults to "Full"
  • Default workunit scope access level (either "Full" or "None"); defaults to "Full"
  • Default file scope access level (either "Full" or "None"); defaults to "Full"

Only the first three items have no default values and must be supplied.

Once the jwtseccmgr component is added, you have to tell other parts of the system to use the plugin. For user authentication and permissions affecting features and workunit scopes, you need to add the plugin to the esp component. Instructions for doing so can be found in the HPCC Systems Administrator's Guide manual (though the manual uses the htpasswd plugin as an example, the process is the same).

If you intend to implement file scope permissions then you will also need provide Dali information about the JWT plugin. In configmgr, within the Dali Server component, select the LDAP tab. Change the authMethod entry to secmgrPlugin and enter "jwtsecmgr" as the authPluginType. Make sure checkScopeScans is set to true.

HPCC Systems Authorization and JWT Claims

This plugin supports all authorizations documented in the HPCC Systems® Administrator's Guide with the exception of "View Permissions". Loosely speaking, the permissions are divided into three groups: Feature, Workunit Scope, and File Scope.

Feature permissions are supported exactly as documented. A specific permission would exist as a JWT claim, by name, with the associated value being the name of the permission. For example, to grant read-only access to ECL Watch, use this claim:

	{ "SmcAccess": "Read" }

File and workunit scope permissions are handled the same way, but different from feature permissions. The claim is one of the Claim constants in the tables below, and the associated value is a matching pattern. A pattern can be simple string or it can use wildcards (specifically, Linux's file globbing wildcards). Wildcards are not typically needed.

Multiple patterns can be set for each claim.

Workunit Scope Permissions

MeaningClaimValue
User has view rights to workunit scopeAllowWorkunitScopeViewpattern
User has modify rights to workunit scopeAllowWorkunitScopeModifypattern
User has delete rights to workunit scopeAllowWorkunitScopeDeletepattern
User does not have view rights to workunit scopeDenyWorkunitScopeViewpattern
User does not have modify rights to workunit scopeDenyWorkunitScopeModifypattern
User does not have delete rights to workunit scopeDenyWorkunitScopeDeletepattern

File Scope Permissions

MeaningClaimValue
User has view rights to file scopeAllowFileScopeViewpattern
User has modify rights to file scopeAllowFileScopeModifypattern
User has delete rights to file scopeAllowFileScopeDeletepattern
User does not have view rights to file scopeDenyFileScopeViewpattern
User does not have modify rights to file scopeDenyFileScopeModifypattern
User does not have delete rights to file scopeDenyFileScopeDeletepattern

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/testing/regress/cleanupReadme.html b/testing/regress/cleanupReadme.html new file mode 100644 index 00000000000..31699cd414e --- /dev/null +++ b/testing/regress/cleanupReadme.html @@ -0,0 +1,24 @@ + + + + + + Cleanup Parameter of Regression Suite run and query sub-command | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Cleanup Parameter of Regression Suite run and query sub-command

The cleanup parameter has been introduced to allow the user to automatically delete the workunits created by executing the Regression Suite on their local system.

It is an optional argument of the run and query sub-command.

A custom logging system also creates log files for each execution of the run and query sub-command that contains information about the workunit deletion.

Command:

./ecl-test run --cleanup [mode]

./ecl-test query --cleanup [mode]

Modes allowed are ‘workunits’, ‘passed’. Default is ‘none’.

  • workunits - all passed and failed workunits are deleted.

  • passed - only the passed workunits of the queries executed are deleted.

  • none - no workunits created during the current run command are deleted.

Result:

The sample terminal output for hthor target:

./ecl-test query ECL_query action1.ecl action2.ecl action4.ecl action5.ecl -t hthor --cleanup workunits

[Action] Suite: hthor
[Action] Queries: 4
[Action] 1. Test: action1.ecl
[Pass] 1. Pass action1.ecl - W20240526-094322 (2 sec)
[Pass] 1. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094322
[Action] 2. Test: action2.ecl
[Pass] 2. Pass action2.ecl - W20240526-094324 (2 sec)
[Pass] 2. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094324
[Action] 3. Test: action4.ecl
[Pass] 3. Pass action4.ecl - W20240526-094325 (2 sec)
[Pass] 3. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094325
[Action] 4. Test: action5.ecl
[Pass] 4. Pass action5.ecl - W20240526-094327 (2 sec)
[Pass] 4. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094327
[Action]
-------------------------------------------------
Result:
Passing: 4
Failure: 0
-------------------------------------------------
Log: /root/HPCCSystems-regression/log/hthor.24-05-26-09-43-22.log
-------------------------------------------------
Elapsed time: 11 sec (00:00:11)
-------------------------------------------------

[Action] Automatic Cleanup Routine

[Pass] 1. Workunit Wuid=W20240526-094322 deleted successfully.
[Pass] 2. Workunit Wuid=W20240526-094324 deleted successfully.
[Failure] 3. Failed to delete Wuid=W20240526-094325. URL: http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240526-094325
Failed cannot open workunit Wuid=W20240526-094325.. Response status code: 200
[Pass] 4. Workunit Wuid=W20240526-094327 deleted successfully.
Suite destructor.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/testing/regress/ecl/README.html b/testing/regress/ecl/README.html new file mode 100644 index 00000000000..a062c7dfb91 --- /dev/null +++ b/testing/regress/ecl/README.html @@ -0,0 +1,87 @@ + + + + + + Test Suite for the Parquet Plugin | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Test Suite for the Parquet Plugin

Running the Test Suite

The Parquet plugin test suite is a subset of tests in the HPCC Systems regression suite. To run the tests:

Change directory to HPCC Platform/testing/regress.

To run the entire Parquet test suite:

Note: Some Parquet tests require initialization of Parquet files. Use the ./ecl-test setupcommand to initialize these files before running the test suite.

These commands can be run on any cluster, including hthor or Roxie like the example below.

./ecl-test query --runclass parquet parquet*.ecl

To run a single test file:

./ecl-test query --runclass parquet <test_file_name>.ecl
+
+example below:
+
+/ecl-test query --target hthor --runclass parquet  parquet_schema.ecl

On the roxie cluster:

./ecl-test query --target roxie --runclass parquet parquet*.ecl

This is what you should see when you run the command above:

[Action] Suite: roxie
+[Action] Queries: 15
+[Action]   1. Test: parquet_compress.ecl ( version: compressionType='UNCOMPRESSED' )
+[Pass]   1. Pass parquet_compress.ecl ( version: compressionType='UNCOMPRESSED' ) - W20240815-111429 (4 sec)
+[Pass]   1. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111429
+[Action]   2. Test: parquet_compress.ecl ( version: compressionType='Snappy' )
+[Pass]   2. Pass parquet_compress.ecl ( version: compressionType='Snappy' ) - W20240815-111434 (3 sec)
+[Pass]   2. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111434
+[Action]   3. Test: parquet_compress.ecl ( version: compressionType='GZip' )
+[Pass]   3. Pass parquet_compress.ecl ( version: compressionType='GZip' ) - W20240815-111438 (4 sec)
+[Pass]   3. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111438
+[Action]   4. Test: parquet_compress.ecl ( version: compressionType='Brotli' )
+[Pass]   4. Pass parquet_compress.ecl ( version: compressionType='Brotli' ) - W20240815-111442 (4 sec)
+[Pass]   4. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111442
+[Action]   5. Test: parquet_compress.ecl ( version: compressionType='LZ4' )
+[Pass]   5. Pass parquet_compress.ecl ( version: compressionType='LZ4' ) - W20240815-111447 (3 sec)
+[Pass]   5. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111447
+[Action]   6. Test: parquet_compress.ecl ( version: compressionType='ZSTD' )
+[Pass]   6. Pass parquet_compress.ecl ( version: compressionType='ZSTD' ) - W20240815-111450 (2 sec)
+[Pass]   6. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111450
+[Action]   7. Test: parquet_corrupt.ecl
+[Pass]   7. Pass parquet_corrupt.ecl - W20240815-111453 (2 sec)
+[Pass]   7. Intentionally fails
+[Pass]   7. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111453
+[Action]   8. Test: parquet_empty.ecl
+[Pass]   8. Pass parquet_empty.ecl - W20240815-111455 (2 sec)
+[Pass]   8. Intentionally fails
+[Pass]   8. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111455
+[Action]   9. Test: parquet_overwrite.ecl
+[Pass]   9. Pass parquet_overwrite.ecl - W20240815-111457 (2 sec)
+[Pass]   9. Intentionally fails
+[Pass]   9. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111457
+[Action]  10. Test: parquet_partition.ecl
+[Pass]  10. Pass parquet_partition.ecl - W20240815-111459 (2 sec)
+[Pass]  10. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111459
+[Action]  11. Test: parquet_schema.ecl
+[Pass]  11. Pass parquet_schema.ecl - W20240815-111502 (1 sec)
+[Pass]  11. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111502
+[Action]  12. Test: parquet_size.ecl
+[Pass]  12. Pass parquet_size.ecl - W20240815-111504 (3 sec)
+[Pass]  12. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111504
+[Action]  13. Test: parquet_string.ecl
+[Pass]  13. Pass parquet_string.ecl - W20240815-111507 (1 sec)
+[Pass]  13. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111507
+[Action]  14. Test: parquet_types.ecl
+[Pass]  14. Pass parquet_types.ecl - W20240815-111509 (7 sec)
+[Pass]  14. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111509
+[Action]  15. Test: parquet_write.ecl
+[Pass]  15. Pass parquet_write.ecl - W20240815-111517 (2 sec)
+[Pass]  15. URL http://127.0.0.1:8010/?Widget=WUDetailsWidget&Wuid=W20240815-111517
+[Action]
+    -------------------------------------------------
+    Result:
+    Passing: 15
+    Failure: 0
+    -------------------------------------------------
+    Log: /home/user/HPCCSystems-regression/log/roxie.24-08-15-11-14-29.log
+    -------------------------------------------------
+    Elapsed time: 52 sec  (00:00:52)
+    -------------------------------------------------

Project Description

This project focuses on the development of a comprehensive test suite for the recently integrated Parquet plugin within the HPCC Systems platform. The objective is to thoroughly evaluate the plugin's functionality, performance, and robustness across different scenarios and configurations. The key deliverables include defining and implementing various test cases, fixing any identified bugs, and providing extensive documentation.

The test suite will evaluate all data types supported by ECL and Arrow, as well as file operations, various compression formats, and schema handling. Additionally, the test suite will measure the plugin's performance across different HPCC components and hardware configurations, conduct stress tests to identify potential bottlenecks and bugs, and compare Parquet to other file formats used in the ecosystem, such as JSON, XML, and CSV.

Test Files

The test suite consists of 10 main test files that test different parquet functionality and operations:

  • parquet_types.ecl: Tests various ECL and Arrow data types

  • parquet_schema.ecl: Evaluates Parquet's handling of different schemas

  • parquet_compress.ecl: Tests different compression algorithms

  • parquet_write.ecl: Validates Parquet write operations

  • parquet_empty.ecl: Tests behavior with empty Parquet files

  • parquet_corrupt.ecl: Checks handling of corrupt Parquet data

  • parquet_size.ecl: Compares file sizes across formats

  • parquet_partition.ecl: Tests partitioning in Parquet files

  • parquet_overwrite.ecl: Validates overwrite operations

  • parquet-string.ecl: Focuses on string-related operations

Test Suite Overview

Type Testing

Covers 42 data types including ECL and Arrow types Examples: BOOLEAN, INTEGER, STRING, UNICODE, various numeric types, sets, and Arrow-specific types

Data Type Tests

The Parquet plugin test suite shows that the plugin supports all ECL types.

Arrow Types Supported by the Parquet Plugin

  • null
  • uint8
  • int8
  • uint16
  • int16
  • uint32
  • int32
  • uint64
  • int64
  • half_float
  • float
  • double
  • string
  • binary
  • fixed_size_binary
  • date32
  • date64
  • timestamp
  • time32
  • time64
  • interval_months
  • list
  • decimal
  • large_list
  • interval_day_time

Compression Testing

Tests all available Arrow compression types: Snappy, GZip, Brotli, LZ4, ZSTD, Uncompressed Compares performance and file sizes for different compression options

Parquet Read and Write Operations

Tests ParquetIO.Read for creating ECL datasets from Parquet files Tests ParquetIO.Write for writing ECL datasets to Parquet files

Additional Tests

Read and Write Speeds Comparison with Other File Types Schema Handling and Compatibility Behavior with Corrupt Data and Empty Parquet Files

Test Evaluation

The test suite generally uses key files located in HPCC-Platform/testing/regress/ecl/key, with a ".xml" extension, to evaluate test outcomes. These files store the expected results for comparison. However, some Parquet tests do not rely on key files, and alternative evaluation methods are used in those cases.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/testing/regress/ecl/analyzers/corporate/tmp/README.html b/testing/regress/ecl/analyzers/corporate/tmp/README.html new file mode 100644 index 00000000000..6eac39e6eb9 --- /dev/null +++ b/testing/regress/ecl/analyzers/corporate/tmp/README.html @@ -0,0 +1,24 @@ + + + + + + HPCC Platform + + + + + + + + + + + + + +
Skip to content

This directory is for the function kbdumptree. The engine should create this but sometimes permission problems arrise.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/tools/esdlcmd/README.html b/tools/esdlcmd/README.html new file mode 100644 index 00000000000..84d1d147f7b --- /dev/null +++ b/tools/esdlcmd/README.html @@ -0,0 +1,330 @@ + + + + + + esdl Utility | HPCC Platform + + + + + + + + + + + + + +
Skip to content

esdl Utility

The esdl utility tool aids with creating and managing ESDL-based and Dynamic ESDL services on an HPCC cluster. It consists of several different commands.

To generate output from an ESDL definition:

xml               Generate XML from ESDL definition.
+ecl               Generate ECL from ESDL definition.
+xsd               Generate XSD from ESDL definition.
+wsdl              Generate WSDL from ESDL definition.
+java              Generate Java code from ESDL definition.
+cpp               Generate C++ code from ESDL definition.
+monitor           Generate ECL code for result monitoring / differencing
+monitor-template  Generate a template for use with 'monitor' command
+

To manage ESDL and DESDL services:

publish               Publish ESDL Definition for ESP use.
+list-definitions      List all ESDL definitions.
+get-definition        Get ESDL definition.
+delete                Delete ESDL Definition.
+bind-service          Configure ESDL based service on target ESP (with existing ESP Binding).
+list-bindings         List all ESDL bindings.
+unbind-service        Remove ESDL based service binding on target ESP.
+bind-method           Configure method associated with existing ESDL binding.
+unbind-method         Remove method associated with existing ESDL binding.
+get-binding           Get ESDL binding.
+manifest              Build a service binding or bundle from a manifest file.
+bind-log-transform    Configure log transform associated with existing ESDL binding.
+unbind-log-transform  Remove log transform associated with existing ESDL binding.
+

The sections below cover the commands in more detail.

manifest

The manifest command creates an XML configuration file for an ESDL ESP from an input XML manifest file. The type of configuration output depends on the manifest file input and on command-line options.

Manifest File

A manifest file is an XML-formatted template combining elements in and outside of the manifest's urn:hpcc:esdl:manifest namespace. Recognized elements of this namespace control the tool while all other markup is copied to the output. The goal of using a manifest file with the tool is to make configuring and deploying services easier:

  1. The manifest file format abstracts some of the complexity of the actual configuration.
  2. By allowing you to include external files like ESDL Scripts and XSLTs into the ouput, you can store and maintain them separately in your repo.

The result of running the manifest tool on a manifest file is an XML artifact suitable for use with the ESDL ESP. Supported output includes:

  • binding: The output is an ESDL binding that may be published to dali.
  • bundle: The output is an ESDL bundle file that may be used to launch an ESP in application mode.

Example

A simplified example showing the format of a bundle manifest file:

xml
<em:Manifest xmlns:em="urn:hpcc:esdl:manifest">
+    <em:ServiceBinding esdlservice="WsFoobar" id="WsFoobar_desdl_binding" auth_feature="DEFERRED">
+        <Methods>
+            <em:Scripts>
+                <em:Include file="WsFoobar-request-prep.xml"/>
+                <em:Include file="WsFoobar-logging-prep.xml"/>
+            </em:Scripts>
+            <Method name="FoobarSearch" url="127.0.0.1:8888">
+                <em:Scripts>
+                    <em:Include file="FoobarSearch-scripts.xml"/>
+                </em:Scripts>
+            </Method>
+        </Methods>
+        <LoggingManager>
+            <LogAgent transformSource="local" name="main-logging">
+                <LogDataXPath>
+                    <LogInfo name="PreparedData" xsl="log-prep">
+                </LogDataXPath>
+                <XSL>
+                    <em:Transform name="log-prep">
+                        <em:Include file="log-prep.xslt">
+                    </em:Transform>
+                </XSL>
+            </LogAgent>
+        </LoggingManager>
+    </em:ServiceBinding>
+    <em:EsdlDefinition>
+        <em:Include file="WsFoobar.ecm"/>
+    </em:EsdlDefinition>
+</em:Manifest>

The tool is permissive and flexible, copying through most markup to the output. Recognized elements in the manifest namespace may be treated differently. They are only required in order to take advantage of the automated processing and simplified format of the manifest file. This example highlights the recommended usage of manifest elements to use the tool's capabilities. Although you could replace some of the elements below with verbatim bundle or binding output elements we won't cover that usage here.

  • <em:Manifest> is the required root element. By default the tool outputs a bundle, though you may explicitly override that on the command line or by providing an @outputType='binding' attribute.
  • <em:ServiceBinding> is valid for both bundle and binding output. It is necessary to enable recognition of <em:Scripts>, and <em:Transform> elements.
  • <em:EsdlDefinition> is relevant only for bundle output. It is necessary to enable element order preservation and recognition of <em:Include> as a descendant element.
  • <em:Include> causes external file contents to be inserted in place of the element. The processing of included files is context dependent; the parent of the <em:Include> element dictates how the file is handled. File inclusion facilitates code reuse in a configuration as code development environment.
  • <em:Scripts> and <em:Transform> trigger preservation of element order for all descendent elements and enable <em:Include> recognition.

XML element ordering is significant to proper ESDL script, XSLT, and ESXDL content processing. The platform's IPropertyTree implementation, used when loading an artifact, does not preserve order. Output configuration files must embed order-sensitive content as text as opposed to XML markup. The tool allows configuration authors to create and maintain files as XML markup, which is easy to read. It then automates the conversion of the XML markup into the embedded text required by an ESP.

Syntax

These elements may create artifact content, change the tool's behavior, or both. When used as intended, none of these elements will appear in the generated output:

Manifest

Required root element of all manifest files that create output:

  • bundle output is created by default. It can be made explicit by setting either/or:
    • command line option --output-type to bundle
    • manifest property @outputType to bundle.
  • binding output is created when either/or:
    • command line option --output-type is binding
    • manifest property @outputType is binding.
AttributeRequired?ValueDescription
@outputTypeNstringA hint informing the tool which type of output to generate. The command line option --output-type may supersede this value to produce a different output.
A bundle manifest is a superset of a binding manifest and may logically be used to create either output type. A binding manifest, as a subset of a bundle, cannot be used to create a valid bundle.
@xmlns[:prefix]YstringThe manifest namespace urn:hpcc:esdl:manifest must be declared. The default namespace prefix should not be used unless all other markup is fully qualified.

ServiceBinding

Recommended child of Manifest that creates ESDL binding content and applies ESDL binding-specific logic to descendent content:

  • A Binding output element is always created containing attributes of Manifest as required. See the sections below for details of the attributes referenced by the tool and how they're output.
  • A child of Binding named Definition is created with attributes @id and @esdlservice.
  • Recognition of <em:Scripts> elements is enabled.
  • Recognition of <em:Transform> elements is enabled.

While possible to omit the <em:ServiceBinding> element and instead embed a complete Binding tree in the manifest, it is discouraged because you lose the benefit of the processing described above.

There are three categories of attributes that can be defined on the <em:ServiceBinding> element:

  1. Standard attributes needed for setup
  2. Service-specific or binding-specific attributes
  3. Auxillary attributes recommended for read-only reference
Standard Attributes

First are the standard attributes used to setup and define the binding:

AttributeRequired?ValueUsage
@esdlserviceYesstring- Name of the ESDL service to which the binding is bound. Output on the Binding/Definition element.
- Also used to generate a value for Definition/@id for bundle type output.

Note that Definition/@id is not output by the tool for binding output as it would need to match the ESDLDefinitionId passed on command line esdl bind-service call, which may differ between environments.

Service-Specific Attributes

Second are binding-specific or service-specific attributes. This is an open-ended category where most future attributes will belong. Any attribute not in the other two categories is included here and output on the Binding/Definition element:

AttributeRequired?ValueUsage
@auth_featureNostringUsed declare authorization settings if they aren't present in the ESDL Definition, or override them if they are. Additional documentation on this attribute is forthcoming.
@returnSchemaLocationOnOKNoBooleanWhen true, a successful SOAP response (non SOAP-Fault) will include the schema location property. False by default.
@namespaceNostringString specifying the namespace for all methods in the binding. May contain variables that are replaced with their values by the ESP during runtime:
- ${service} : lowercase service name
- ${esdl-service} : service name, possibly mixed case
- ${method} : lowercase method name
- ${esdl-method} : method name, possibly mixed case
- ${optionals} : comma-delimited list of all optional URL parameters included in the method request, enclosed in parentheses
- ${version} : client version number
Auxillary Attributes

Finally are auxillary attributes. These should be thought of as read-only or for reference, and it is not recommended that you set these in the manifest. They are set by the system when publishing to dali using esdl bind-service, and they are present on binding configurations retrieved from the dali. If you set these attributes in the manifest, the values will be overwritten when running esdl bind-service. Alternately, if you run as an esdl application, these values aren't set by default and don't have a material effect on the binding, but they may appear in the trace log.

AttributeRequired?ValueUsage
@createdNostringTimestamp of binding creation
@espbindingNostringSet to match the id. Otherwise the value is unset and unused.
@espprocessNostringName of the ESP process this binding is running on
@idNostringRuntime name of the binding. When publishing to dali the value is [ESP Process].[port].[ESDL Service]. When not present in the manifest a default value is generated of the form [@esdlservice]_desdl_binding
@portNostringPort on the ESP Process listening for connections to the binding.
@publishedByNostringUserid of the person publishing the binding

EsdlDefinition

Recommended child of <em:Manifest> that enables ESDL definition-specific logic in the tool:

  • A Definitions element is output. All content is enclosed in a CDATA section.
  • The manifest <em:Include> element is recognized to import ESDL definitions:
    • An included .ecm file is transformed into XML.
    • An included .xml file is imported as-is.
AttributeRequired?ValueUsage
N/A

The element is not required since it is possible to embed a complete Definitions element hierarchy in the manifest.

Include

Optional element that imports the contents of another file into the output in place of itself. The outcome of the import depends on the context in which this element is used. See EsdlDefinition, Scripts, and Transform for more information.

AttributeRequired?ValueUsage
@fileYesfile pathFull or partial path to an external file to be imported. If a partial path is outside of the tool's working directory, the tool's command line must specify the appropriate root directory using either -I or --include-path.

Any XSLTs or ESDL Scripts written inline in a manifest file will have XML escaping applied where required to generate valid XML. If an XSLT contains any text content or markup that needs to be preserved as-is (no XML escaping applied) then be sure to use an <em:Include> operation. Included files are inserted into the output as-is, with the exception of encoding nested CDATA markup. If the included file will be inside a CDATA section on output, then any CDATA end markup in the file will be encoded as ]]]]><![CDATA[> to prevent nested CDATA sections or a prematurely ending a CDATA section.

For details on using XSLT to generate unescaped output, see this section of the specification: https://www.w3.org/TR/1999/REC-xslt-19991116#disable-output-escaping

Scripts

Optional repeatable element appearing within an <em:ServiceBinding> element that processes child elements and creates output expected for an ESDL binding:

  • The <em:Scripts> element is replaced on output with <Scripts>. Then all content is enclosed in a CDATA section after wrapping it with a new Scripts element. That new <Scripts> element contains namespaces declared by the input <em:Scripts> element. The input <em:Scripts foo="..." xmlns:bar="..."><!-- content --></em:Scripts> becomes <Scripts><![CDATA[<Scripts xmlns:bar="..."><!-- content --></Scripts>]]></Scripts>.
  • The manifest <em:Include> element is recognized to import scripts from external files. The entire file, minus leading and trailing whitespace, is imported. Refrain from including files that contain an XML declaration.

Transform

Optional repeatable element appearing within an <em:ServiceBinding> element that processes child elements and creates output expected for an ESDL binding:

  • All content is enclosed in a CDATA section. The input <em:Transform> <!-- content --> </em:Transform> becomes <Transform><![CDATA[<!-- content -->]]></Transform>.
  • The manifest <em:Include> element is recognized to import transforms from external files. The entire file, minus leading and trailing whitespace, is imported. Refrain from including files that contain an XML declaration.

Usage

Usage:
+
+esdl manifest <manifest-file> [options]
+
+Options:
+    -I | --include-path <path>
+                        Search path for external files included in the manifest.
+                        Use once for each path.
+    --outfile <filename>
+                        Path and name of the output file
+    --output-type <type>
+                        When specified this option overrides the value supplied
+                        in the manifest attribute Manifest/@outputType.
+                        Allowed values are 'binding' or 'bundle'.
+                        When not specified in either location the default is
+                        'bundle'
+    --help              Display usage information for the given command
+    -v,--verbose        Output additional tracing information
+    -tcat,--trace-category <flags>
+                        Control which debug messages are output; a case-insensitive
+                        comma-delimited combination of:
+                            dev: all output for the developer audience
+                            admin: all output for the operator audience
+                            user: all output for the user audience
+                            err: all error output
+                            warn: all warning output
+                            prog: all progress output
+                            info: all info output
+                        Errors and warnings are enabled by default if not verbose,
+                        and all are enabled when verbose. Use an empty <flags> value
+                        to disable all.
+

Output

The esdl manifest command reads the manifest, processes statements in the urn:hpcc:esdl:manifest namespace and generates an output XML file formatted to the requirements of the ESDL ESP. This includes wrapping included content in CDATA sections to ensure element order is maintained and replacing urn:hpcc:esdl:manifest elements as required.

An example output of each type -bundle and binding- is shown below. The examples use the sample manifest above as input plus these included files:

WsFoobar-request-prep.xml

xml
<es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+    <es:set-value target="RequestValue" value="'foobar'"/>
+</es:BackendRequest>

WsFoobar-logging-prep.xml

xml
<es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+    <es:set-value target="LogValue" value="23"/>
+</es:PreLogging>

FoobarSearch-scripts.xml

xml
<Scripts>
+    <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+        <es:if test="RequestOption>1">
+            <es:set-value target="HiddenOption" value="true()"/>
+        </es:if>
+    </es:BackendRequest>
+
+    <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+        <es:if test="RequestOption=1">
+            <es:set-value target="ProductPrice" value="10"/>
+        </es:if>
+    </es:PreLogging>
+</Scripts>

log-prep.xslt

xml
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="xml" omit-xml-declaration="yes"/>
+    <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+    <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+    <xsl:template match="/">
+        <Result>
+        <Dataset name='special-data'>
+            <Row>
+            <Records>
+                <Rec>
+                <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                <request_data>
+                    <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                    <xsl:copy-of select="$logContent/UserContent/Context"/>
+                    <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                </request_data>
+                <request_format>SPECIAL</request_format>
+                <type>23</type>
+                </Rec>
+            </Records>
+            </Row>
+        </Dataset>
+        </Result>
+    </xsl:template>
+</xsl:stylesheet>

WsFoobar.ecm

ESPrequest FoobarSearchRequest
+{
+    int RequestOption;
+    string RequestName;
+    [optional("hidden")] bool HiddenOption;
+};
+
+ESPresponse FoobarSearchResponse
+{
+    int FoundCount;
+    string FoundAddress;
+};
+
+ESPservice [
+    auth_feature("DEFERRED"),
+    version("1"),
+    default_client_version("1"),
+] WsFoobar
+{
+    ESPmethod FoobarSearch(FoobarSearchRequest, FoobarSearchResponse);
+};
+

Bundle

The bundle is suitable to configure a service on an ESP launched in esdl application mode.

xml
  <EsdlBundle>
+    <Binding id="WsFoobar_desdl_binding">
+      <Definition esdlservice="WsFoobar" id="WsFoobar.1">
+        <Methods>
+          <Scripts>
+            <![CDATA[
+              <Scripts>
+                <es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                    <es:set-value target="RequestValue" value="'foobar'"/>
+                </es:BackendRequest>
+                <es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                    <es:set-value target="LogValue" value="23"/>
+                </es:PreLogging>
+              </Scripts>
+            ]]>
+          </Scripts>
+          <Method name="FoobarSearch" url="127.0.0.1:8888">
+            <Scripts>
+              <![CDATA[
+                <Scripts>
+                  <Scripts>
+                      <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                          <es:if test="RequestOption>1">
+                              <es:set-value target="HiddenOption" value="true()"/>
+                          </es:if>
+                      </es:BackendRequest>
+
+                      <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                          <es:if test="RequestOption=1">
+                              <es:set-value target="ProductPrice" value="10"/>
+                          </es:if>
+                      </es:PreLogging>
+                  </Scripts>
+                </Scripts>
+              ]]>
+            </Scripts>
+          </Method>
+        </Methods>
+        <LoggingManager>
+          <LogAgent transformSource="local" name="main-logging">
+            <LogDataXPath>
+              <LogInfo name="PreparedData" xsl="log-prep"/>
+            </LogDataXPath>
+            <XSL>
+              <Transform name="log-prep">
+                <![CDATA[
+                  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+                      <xsl:output method="xml" omit-xml-declaration="yes"/>
+                      <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+                      <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+                      <xsl:template match="/">
+                          <Result>
+                          <Dataset name='special-data'>
+                              <Row>
+                              <Records>
+                                  <Rec>
+                                  <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                                  <request_data>
+                                      <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                                      <xsl:copy-of select="$logContent/UserContent/Context"/>
+                                      <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                                  </request_data>
+                                  <request_format>SPECIAL</request_format>
+                                  <type>23</type>
+                                  </Rec>
+                              </Records>
+                              </Row>
+                          </Dataset>
+                          </Result>
+                      </xsl:template>
+                  </xsl:stylesheet>
+                ]]>
+              </Transform>
+            </XSL>
+          </LogAgent>
+        </LoggingManager>
+      </Definition>
+    </Binding>
+    <Definitions>
+      <![CDATA[
+        <esxdl name="WsFoobar"><EsdlRequest name="FoobarSearchRequest"><EsdlElement  type="int" name="RequestOption"/><EsdlElement  type="string" name="RequestName"/><EsdlElement  optional="hidden" type="bool" name="HiddenOption"/></EsdlRequest>
+        <EsdlResponse name="FoobarSearchResponse"><EsdlElement  type="int" name="FoundCount"/><EsdlElement  type="string" name="FoundAddress"/></EsdlResponse>
+        <EsdlRequest name="WsFoobarPingRequest"></EsdlRequest>
+        <EsdlResponse name="WsFoobarPingResponse"></EsdlResponse>
+        <EsdlService version="1" auth_feature="DEFERRED" name="WsFoobar" default_client_version="1"><EsdlMethod response_type="FoobarSearchResponse" request_type="FoobarSearchRequest" name="FoobarSearch"/><EsdlMethod response_type="WsFoobarPingResponse" auth_feature="none" request_type="WsFoobarPingRequest" name="Ping"/></EsdlService>
+        </esxdl>
+      ]]>
+    </Definitions>
+  </EsdlBundle>

Binding

The binding can be used to configure a service for an ESP using a dali.

xml
<Binding id="WsFoobar_desdl_binding">
+  <Definition esdlservice="WsFoobar" id="WsFoobar.1">
+    <Methods>
+      <Scripts>
+        <![CDATA[
+          <Scripts>
+            <es:BackendRequest name="request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                <es:set-value target="RequestValue" value="'foobar'"/>
+            </es:BackendRequest>
+            <es:PreLogging name="logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                <es:set-value target="LogValue" value="23"/>
+            </es:PreLogging>
+          </Scripts>
+        ]]>
+      </Scripts>
+      <Method name="FoobarSearch" url="127.0.0.1:8888">
+        <Scripts>
+          <![CDATA[
+            <Scripts>
+              <Scripts>
+                  <es:BackendRequest name="search-request-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                      <es:if test="RequestOption>1">
+                          <es:set-value target="HiddenOption" value="true()"/>
+                      </es:if>
+                  </es:BackendRequest>
+
+                  <es:PreLogging name="search-logging-prep" target="soap:Body/{$query}" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:es="urn:hpcc:esdl:script">
+                      <es:if test="RequestOption=1">
+                          <es:set-value target="ProductPrice" value="10"/>
+                      </es:if>
+                  </es:PreLogging>
+              </Scripts>
+            </Scripts>
+          ]]>
+        </Scripts>
+      </Method>
+    </Methods>
+    <LoggingManager>
+      <LogAgent transformSource="local" name="main-logging">
+        <LogDataXPath>
+          <LogInfo name="PreparedData" xsl="log-prep"/>
+        </LogDataXPath>
+        <XSL>
+          <Transform name="log-prep">
+            <![CDATA[
+              <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+                  <xsl:output method="xml" omit-xml-declaration="yes"/>
+                  <xsl:variable name="logContent" select="/UpdateLogRequest/LogContent"/>
+                  <xsl:variable name="transactionId" select="$logContent/UserContext/Context/Row/Common/TransactionId"/>
+                  <xsl:template match="/">
+                      <Result>
+                      <Dataset name='special-data'>
+                          <Row>
+                          <Records>
+                              <Rec>
+                              <transaction_id><xsl:value-of select="$transactionId"/></transaction_id>
+                              <request_data>
+                                  <xsl:text disable-output-escaping="yes">&amp;lt;![CDATA[COMPRESS('</xsl:text>
+                                  <xsl:copy-of select="$logContent/UserContent/Context"/>
+                                  <xsl:text disable-output-escaping="yes">')]]&amp;gt;</xsl:text>
+                              </request_data>
+                              <request_format>SPECIAL</request_format>
+                              <type>23</type>
+                              </Rec>
+                          </Records>
+                          </Row>
+                      </Dataset>
+                      </Result>
+                  </xsl:template>
+              </xsl:stylesheet>
+            ]]>
+          </Transform>
+        </XSL>
+      </LogAgent>
+    </LoggingManager>
+  </Definition>
+</Binding>

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/tools/esp-api/README.html b/tools/esp-api/README.html new file mode 100644 index 00000000000..5536273f930 --- /dev/null +++ b/tools/esp-api/README.html @@ -0,0 +1,24 @@ + + + + + + Developer README for ESP API Command Line Tool | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Developer README for ESP API Command Line Tool

Overview

This tool is designed to interact with HPCC ESP services, providing four commands: describe, test, list-services, and list-methods.

  • describe: Allows users to explore available services, methods, and their request-response structures.

  • test: Enables sending requests in various formats (XML, JSON, or form query strings) to the ESP services.

  • list-services: Invoked by the auto-complete script, this command provides a list of names of all ESP services.

  • list-methods: Invoked by the auto-complete script, this command provides a list of names of all methods within an ESP service.

Usage Notes

  • ESDL Directory Location: The tool gathers the directory of the ESDL files from an environment configuration variable; gets the install path from jutil library and appends /componentfiles/esdl_files/.

  • Server and Port Defaults: When using the test command, if the server and port are not specified, the tool defaults to interacting with an ESP instance located at http://127.0.0.1:8010.

Ideas for Expansion

  • Custom ESDL Directory Argument: An additional argument could be introduced to allow users to specify the directory of the ESDL files directly in the command.

  • Template Request Generation: A feature could be added to generate template XML or JSON requests. This would simplify the process of filling out requests by providing a pre-structured template.

  • Credential Prompts: The tool could be expanded to prompt for the username and password upon a 401 Unauthorized response.

  • Selective Response Extraction: Another potential feature is to allow extraction of specific tags from the response using XPath expressions. This would make it easier to parse and analyze responses.

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file diff --git a/tools/tagging/README.html b/tools/tagging/README.html new file mode 100644 index 00000000000..ad199593a5e --- /dev/null +++ b/tools/tagging/README.html @@ -0,0 +1,36 @@ + + + + + + Tagging new versions | HPCC Platform + + + + + + + + + + + + + +
Skip to content

Tagging new versions

General

The file tools/git/aliases.sh contains various git aliases which are useful when using git, and may be used by the merge scripts.

The file env.sh.example contains some example environment variable settings. Copy that locally to env.sh and modify it to match your local setup.

Before running any of the other scripts, process the contents of that file as a source file

. env.sh

to initialize the common environment variables.

Pre-requisites

The following tools are required:

  • git
  • helm

The following repositories should be checked out in a directory reserved for merging and tagging (default for scripts is ~/git):

git clone git@github.com:hpcc-systems/eclide.git
+git clone git@github.com:hpcc-systems/hpcc4j.git HPCC-JAPIs
+git clone git@github.com:hpcc-systems/Spark-HPCC.git
+git clone git@github.com:hpcc-systems/LN.git ln
+git clone git@github.com:hpcc-systems/HPCC-Platform.git hpcc
+git clone git@github.com:hpcc-systems/helm-chart.git

The following are required for builds prior to 8.12.x

git clone git@github.com:hpcc-systems/nagios-monitoring.git
+git clone git@github.com:hpcc-systems/ganglia-monitoring.git

The files git-fixversion and git-unupmerge can copied so they are on your default path, and then they will be available as git commands.

Tagging new versions

The following process should be followed when tagging a new set of versions.

  1. Upmerge all changes between candidate branches for the different versions

You can set the all environment variable to a subset of the projects (e.g. export all=hpcc) if there are no changes in the other repositories. The only effect for projects that are upmerged with no changes will be that they gain an empty merge transaction. If multiple people are merging PRs to different repositories it may be safer to upmerge all projects.

For example:

./upmerge A.a.x candidate-A.b.x
+./upmerge A.b.x candidate-A.c.x
+./upmerge A.b.x candidate-B.0.x
+./upmerge B.0.x master
  1. Create new point-release candidate branches:
./gorc.sh A.a.x
+./gorc.sh A.b.x
+./gorc.sh A.c.x

Taking a build gold:

Go gold with each of the explicit versions

./gogold.sh 7.8.76
+./gogold.sh 7.10.50

If you have merged changes onto a point-release branch you would normally create a new rc before going gold. If the change was trivial (e.g. removing an unwanted file) then you can use the --ignore option to skip that step.

Creating a new rc for an existing point release:

This normally happens after cherry-picking a late fix for a particular version, which has already been merged into the .x candidate branch.

./gorc.sh A.a.<n>

Create a new minor/major branch

A new minor branch is created from the current master...

./gominor.sh

Released under the Apache-2.0 License.

+ + + + \ No newline at end of file