From 6e9949ccedb779fdc369ef1325ed3a68dc6157df Mon Sep 17 00:00:00 2001 From: Brian Olsen Date: Tue, 26 Sep 2023 15:40:54 -0500 Subject: [PATCH] Add new docs deployment --- .gitignore | 1 + site/.github/bin/deploy_docs.sh | 57 + site/.github/bin/deploy_docs.sh.orig | 67 + site/.gitignore | 119 ++ site/README.md | 182 +++ site/docs/about.md | 30 + .../assets/images/Iceberg-logo-wordmark.png | Bin 0 -> 4874 bytes site/docs/assets/images/Iceberg-logo.png | Bin 0 -> 11068 bytes site/docs/assets/images/favicon-16x16.png | Bin 0 -> 1496 bytes site/docs/assets/images/favicon-32x32.png | Bin 0 -> 2101 bytes site/docs/assets/images/favicon-96x96.png | Bin 0 -> 5456 bytes site/docs/assets/images/favicon.ico | Bin 0 -> 1150 bytes site/docs/assets/images/favicon.png | Bin 0 -> 5456 bytes site/docs/assets/images/iceberg-logo-icon.png | Bin 0 -> 17608 bytes site/docs/assets/images/iceberg-metadata.png | Bin 0 -> 140290 bytes site/docs/assets/images/intro-bg.webp | Bin 0 -> 96626 bytes site/docs/benchmarks.md | 132 ++ site/docs/blogs.md | 389 +++++ site/docs/catalog.md | 48 + site/docs/community.md | 106 ++ site/docs/contribute.md | 397 ++++++ site/docs/docs/latest/docs | 1 + site/docs/docs/latest/mkdocs.yml | 70 + site/docs/docs/nightly/docs/api.md | 256 ++++ .../docs/assets/images/audit-branch.png | Bin 0 -> 39065 bytes .../assets/images/historical-snapshot-tag.png | Bin 0 -> 17755 bytes .../iceberg-in-place-metadata-migration.png | Bin 0 -> 31816 bytes .../images/iceberg-migrateaction-step1.png | Bin 0 -> 16223 bytes .../images/iceberg-migrateaction-step2.png | Bin 0 -> 29566 bytes .../images/iceberg-migrateaction-step3.png | Bin 0 -> 19704 bytes .../images/iceberg-snapshotaction-step1.png | Bin 0 -> 39814 bytes .../images/iceberg-snapshotaction-step2.png | Bin 0 -> 42512 bytes .../images/partition-spec-evolution.png | Bin 0 -> 224020 bytes site/docs/docs/nightly/docs/aws.md | 686 +++++++++ site/docs/docs/nightly/docs/branching.md | 115 ++ site/docs/docs/nightly/docs/configuration.md | 191 +++ site/docs/docs/nightly/docs/custom-catalog.md | 268 ++++ site/docs/docs/nightly/docs/dell.md | 130 ++ .../docs/nightly/docs/delta-lake-migration.md | 118 ++ site/docs/docs/nightly/docs/evolution.md | 101 ++ site/docs/docs/nightly/docs/flink-actions.md | 35 + .../docs/nightly/docs/flink-configuration.md | 158 +++ .../docs/docs/nightly/docs/flink-connector.md | 141 ++ site/docs/docs/nightly/docs/flink-ddl.md | 206 +++ site/docs/docs/nightly/docs/flink-queries.md | 489 +++++++ site/docs/docs/nightly/docs/flink-writes.md | 265 ++++ site/docs/docs/nightly/docs/flink.md | 393 +++++ site/docs/docs/nightly/docs/hive-migration.md | 55 + site/docs/docs/nightly/docs/hive.md | 590 ++++++++ site/docs/docs/nightly/docs/index.md | 52 + .../docs/nightly/docs/java-api-quickstart.md | 317 +++++ site/docs/docs/nightly/docs/jdbc.md | 70 + site/docs/docs/nightly/docs/maintenance.md | 157 ++ site/docs/docs/nightly/docs/nessie.md | 160 +++ site/docs/docs/nightly/docs/partitioning.md | 95 ++ site/docs/docs/nightly/docs/performance.md | 55 + site/docs/docs/nightly/docs/reliability.md | 68 + site/docs/docs/nightly/docs/schemas.md | 44 + .../docs/nightly/docs/spark-configuration.md | 188 +++ site/docs/docs/nightly/docs/spark-ddl.md | 521 +++++++ .../nightly/docs/spark-getting-started.md | 138 ++ .../docs/nightly/docs/spark-procedures.md | 698 +++++++++ site/docs/docs/nightly/docs/spark-queries.md | 403 ++++++ .../docs/spark-structured-streaming.md | 121 ++ site/docs/docs/nightly/docs/spark-writes.md | 442 ++++++ .../docs/docs/nightly/docs/table-migration.md | 74 + site/docs/docs/nightly/mkdocs.yml | 70 + site/docs/fileio.md | 44 + site/docs/gcm-stream-spec.md | 85 ++ site/docs/hive-quickstart.md | 115 ++ site/docs/how-to-release.md | 516 +++++++ site/docs/index.md | 108 ++ site/docs/multi-engine-support.md | 116 ++ site/docs/puffin-spec.md | 143 ++ site/docs/releases.md | 777 ++++++++++ site/docs/roadmap.md | 47 + site/docs/security.md | 33 + site/docs/spark-quickstart.md | 342 +++++ site/docs/spec.md | 1259 +++++++++++++++++ site/docs/talks.md | 71 + site/docs/terms.md | 61 + site/docs/vendors.md | 62 + site/docs/view-spec.md | 329 +++++ site/mkdocs.yml | 96 ++ site/requirements.txt | 23 + site/variables.yml | 26 + 86 files changed, 13722 insertions(+) create mode 100755 site/.github/bin/deploy_docs.sh create mode 100755 site/.github/bin/deploy_docs.sh.orig create mode 100644 site/.gitignore create mode 100644 site/README.md create mode 100644 site/docs/about.md create mode 100644 site/docs/assets/images/Iceberg-logo-wordmark.png create mode 100644 site/docs/assets/images/Iceberg-logo.png create mode 100644 site/docs/assets/images/favicon-16x16.png create mode 100644 site/docs/assets/images/favicon-32x32.png create mode 100644 site/docs/assets/images/favicon-96x96.png create mode 100644 site/docs/assets/images/favicon.ico create mode 100644 site/docs/assets/images/favicon.png create mode 100644 site/docs/assets/images/iceberg-logo-icon.png create mode 100644 site/docs/assets/images/iceberg-metadata.png create mode 100644 site/docs/assets/images/intro-bg.webp create mode 100644 site/docs/benchmarks.md create mode 100644 site/docs/blogs.md create mode 100644 site/docs/catalog.md create mode 100644 site/docs/community.md create mode 100644 site/docs/contribute.md create mode 120000 site/docs/docs/latest/docs create mode 100644 site/docs/docs/latest/mkdocs.yml create mode 100644 site/docs/docs/nightly/docs/api.md create mode 100644 site/docs/docs/nightly/docs/assets/images/audit-branch.png create mode 100644 site/docs/docs/nightly/docs/assets/images/historical-snapshot-tag.png create mode 100644 site/docs/docs/nightly/docs/assets/images/iceberg-in-place-metadata-migration.png create mode 100644 site/docs/docs/nightly/docs/assets/images/iceberg-migrateaction-step1.png create mode 100644 site/docs/docs/nightly/docs/assets/images/iceberg-migrateaction-step2.png create mode 100644 site/docs/docs/nightly/docs/assets/images/iceberg-migrateaction-step3.png create mode 100644 site/docs/docs/nightly/docs/assets/images/iceberg-snapshotaction-step1.png create mode 100644 site/docs/docs/nightly/docs/assets/images/iceberg-snapshotaction-step2.png create mode 100644 site/docs/docs/nightly/docs/assets/images/partition-spec-evolution.png create mode 100644 site/docs/docs/nightly/docs/aws.md create mode 100644 site/docs/docs/nightly/docs/branching.md create mode 100644 site/docs/docs/nightly/docs/configuration.md create mode 100644 site/docs/docs/nightly/docs/custom-catalog.md create mode 100644 site/docs/docs/nightly/docs/dell.md create mode 100644 site/docs/docs/nightly/docs/delta-lake-migration.md create mode 100644 site/docs/docs/nightly/docs/evolution.md create mode 100644 site/docs/docs/nightly/docs/flink-actions.md create mode 100644 site/docs/docs/nightly/docs/flink-configuration.md create mode 100644 site/docs/docs/nightly/docs/flink-connector.md create mode 100644 site/docs/docs/nightly/docs/flink-ddl.md create mode 100644 site/docs/docs/nightly/docs/flink-queries.md create mode 100644 site/docs/docs/nightly/docs/flink-writes.md create mode 100644 site/docs/docs/nightly/docs/flink.md create mode 100644 site/docs/docs/nightly/docs/hive-migration.md create mode 100644 site/docs/docs/nightly/docs/hive.md create mode 100644 site/docs/docs/nightly/docs/index.md create mode 100644 site/docs/docs/nightly/docs/java-api-quickstart.md create mode 100644 site/docs/docs/nightly/docs/jdbc.md create mode 100644 site/docs/docs/nightly/docs/maintenance.md create mode 100644 site/docs/docs/nightly/docs/nessie.md create mode 100644 site/docs/docs/nightly/docs/partitioning.md create mode 100644 site/docs/docs/nightly/docs/performance.md create mode 100644 site/docs/docs/nightly/docs/reliability.md create mode 100644 site/docs/docs/nightly/docs/schemas.md create mode 100644 site/docs/docs/nightly/docs/spark-configuration.md create mode 100644 site/docs/docs/nightly/docs/spark-ddl.md create mode 100644 site/docs/docs/nightly/docs/spark-getting-started.md create mode 100644 site/docs/docs/nightly/docs/spark-procedures.md create mode 100644 site/docs/docs/nightly/docs/spark-queries.md create mode 100644 site/docs/docs/nightly/docs/spark-structured-streaming.md create mode 100644 site/docs/docs/nightly/docs/spark-writes.md create mode 100644 site/docs/docs/nightly/docs/table-migration.md create mode 100644 site/docs/docs/nightly/mkdocs.yml create mode 100644 site/docs/fileio.md create mode 100644 site/docs/gcm-stream-spec.md create mode 100644 site/docs/hive-quickstart.md create mode 100644 site/docs/how-to-release.md create mode 100644 site/docs/index.md create mode 100644 site/docs/multi-engine-support.md create mode 100644 site/docs/puffin-spec.md create mode 100644 site/docs/releases.md create mode 100644 site/docs/roadmap.md create mode 100644 site/docs/security.md create mode 100644 site/docs/spark-quickstart.md create mode 100644 site/docs/spec.md create mode 100644 site/docs/talks.md create mode 100644 site/docs/terms.md create mode 100644 site/docs/vendors.md create mode 100644 site/docs/view-spec.md create mode 100644 site/mkdocs.yml create mode 100644 site/requirements.txt create mode 100644 site/variables.yml diff --git a/.gitignore b/.gitignore index 818dcf3a55ac..e7a6d21205b7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .cache tmp/ site +!./site # intellij files .idea diff --git a/site/.github/bin/deploy_docs.sh b/site/.github/bin/deploy_docs.sh new file mode 100755 index 000000000000..9b43d3821cdc --- /dev/null +++ b/site/.github/bin/deploy_docs.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +while [[ "$#" -gt 0 ]]; do + case $1 in + -v|--version) ICEBERG_VERSION="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +GIT_BRANCH="docs-${ICEBERG_VERSION}" + +# change to branch +git checkout -b $GIT_BRANCH + +cd .. + +# remove all files and directories minus exceptions +find . \ + -not \( -path . \) \ + -not \( -path .. \) \ + -not \( -path ./.git -prune \) \ + -not \( -path ./.github -prune \) \ + -not \( -path ./docs-new -prune \) \ + ! -name LICENSE \ + ! -name NOTICE \ + -exec rm -rf {} + + +# move the nightly docs to the root and change from 'nightly' +mv docs-new ./tmp && \ +mv ./tmp/docs/docs/nightly/* . && \ +rm -r ./tmp + +#delete top-level directories +rm -r ./docs-new + +# update versions in mkdocs.yml +sed -i '' -E "s/(^site\_name:[[:space:]]+docs\/).*$/\1${ICEBERG_VERSION}/" "./mkdocs.yml" +sed -i '' -E "s/(^[[:space:]]*-[[:space:]]+Javadoc:.*\/javadoc\/)nightly/\1${ICEBERG_VERSION}/" "./mkdocs.yml" + +cd docs + +# add exclude search for older documentation +python3 -c "import os +for f in filter(lambda x: x.endswith('.md'), os.listdir()): lines = open(f).readlines(); open(f, 'w').writelines(lines[:2] + ['search:\n', ' exclude: true\n'] + lines[2:]);" + +cd .. + +git add . + +git commit -m "Deploy ${GIT_BRANCH} branch" + +git push origin $GIT_BRANCH + +git checkout main + +git reset --hard origin/main diff --git a/site/.github/bin/deploy_docs.sh.orig b/site/.github/bin/deploy_docs.sh.orig new file mode 100755 index 000000000000..71a2be66a341 --- /dev/null +++ b/site/.github/bin/deploy_docs.sh.orig @@ -0,0 +1,67 @@ +#!/bin/bash + +while [[ "$#" -gt 0 ]]; do + case $1 in + -v|--version) ICEBERG_VERSION="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +GIT_BRANCH="docs-${ICEBERG_VERSION}" + +# change to branch +git checkout -b $GIT_BRANCH + +cd .. + +<<<<<<< HEAD +#remove all all files and directories except the docs/ folder +find . -type f -exec rm -f {} + +find . ! -name 'docs-new' -type d -exec rm -rf {} + +======= +# remove all files and directories except the exceptions +find . \ + -not \( -path . \) \ + -not \( -path .. \) \ + -not \( -path ./.git -prune \) \ + -not \( -path ./.github -prune \) \ + -not \( -path ./docs-new -prune \) \ + ! -name LICENSE \ + ! -name NOTICE \ + -exec rm -rf {} + +>>>>>>> e5cc60e09 (Future squash) + +# move the nightly docs to the root and change from 'nightly' +mv docs-new ./tmp && \ +mv ./tmp/docs/docs/nightly/* . && \ +rm -r ./tmp + +#delete top-level directories +rm -r ./docs-new + +# update versions in mkdocs.yml +sed -i '' -E "s/(^site\_name:[[:space:]]+docs\/).*$/\1${ICEBERG_VERSION}/" "./mkdocs.yml" +sed -i '' -E "s/(^[[:space:]]*-[[:space:]]+Javadoc:.*\/javadoc\/)nightly/\1${ICEBERG_VERSION}/" "./mkdocs.yml" + +cd docs + +# add exclude search for older documentation +python3 -c "import os +<<<<<<< HEAD +for f in filter(lambda x: x.endswith('.md'), os.listdir('.')): lines = open(f).readlines(); open(f, 'w').writelines(lines[:2] + ['search:\n', ' exclude: true\n'] + lines[2:]);" +======= +for f in filter(lambda x: x.endswith('.md'), os.listdir()): lines = open(f).readlines(); open(f, 'w').writelines(lines[:2] + ['search:\n', ' exclude: true\n'] + lines[2:]);" + +cd .. +>>>>>>> e5cc60e09 (Future squash) + +git add . + +git commit -m "Deploy ${GIT_BRANCH} branch" + +git push origin $GIT_BRANCH + +git checkout master + +git reset --hard origin/master diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 000000000000..9764cefbe101 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,119 @@ +## Temp remove for first phase +.github/workflows/ + +## MkDocs +/site/ + +## Vale +.github/vale/ +.vale.ini + +## MacOS + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +## Linux + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +## Eclipse + +.metadata +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +.project + + + diff --git a/site/README.md b/site/README.md new file mode 100644 index 000000000000..3d050d954ad6 --- /dev/null +++ b/site/README.md @@ -0,0 +1,182 @@ + + +# Iceberg site and documentation + +This subproject contains the [MkDocs projects](https://www.mkdocs.org/) that define the non-versioned Iceberg site and the versioned Iceberg documentation. The Iceberg site MkDocs project contains a plugin that builds all the static sites for each version of documentation. These subsites are all weaved together to make the final Apache Iceberg site and docs with a single build. + +## Requirements + +* Python >=3.9 +* pip + + +## Usage + +The directory structure in this repository mimics the sitemap hierarchy of the website. This aims to help contributors find the source files needed to make their changes faster. To understand the layout and naming, it is helpful to have some basic understandings of the MkDocs framework defaults. + +### MkDocs background + +In MkDocs, the [`docs_dir`](https://www.mkdocs.org/user-guide/configuration/#docs_dir) points to the root directory containing the source markdown files for an MkDocs project. By default, this points to the `docs` directory. When you build MkDocs `mkdocs build`, MkDocs generates the static site in the [`site_dir`](https://www.mkdocs.org/user-guide/configuration/#site_dir) becomes the root of that project for the generated site. + +### Iceberg docs layout + +In the Iceberg docs, since the top-level site and versioned docs are contained in the same directory, they all live under the `/site` directory of the main Iceberg repository. The `/site/docs` directory is named this way to follow the [MkDocs convention](https://www.mkdocs.org/user-guide/configuration/#docs_dir), while the `/site/docs/docs` directory is an analog to the "Docs" navigation tab. Under this directory, you'll find the `/site/docs/docs/nightly` directory, which contains the state of the documentation in the local revisions. + +The non-versioned site pages are all the `/site/docs/.*md` files and the docs are the `/site/docs/docs//docs/*.md` files. Notice the location of the `mkdocs.yml`. Looking at this though, you may ask where the older versions and javadocs are. + +``` +./site/ +├── docs +│   ├── assets +│   ├── docs +│   │   └── nightly +│   │   ├── docs +│   │   │ ├── assets +│   │   │ ├── api.md +│   │   │ ├── ... +│   │   │ └── table-migration.md +│   │   └── mkdocs.yml (versioned) +│   ├── about.md +│   ├── ... +│   └── view-spec.md +├── README.md +├── mkdocs.yml (non-versioned) +├── requirements.txt +└── variables.yml +``` +### Building the versioned docs + +All previously versioned docs will be committed in `docs-` branches and mounted using [git worktree](https://git-scm.com/docs/git-worktree) at build time. The worktree will pull these versions in following the `/site/docs/docs/` convention. The `latest` version, will be a secondary copy of the most recent build version in the worktree, but pointing to `/site/docs/docs/latest`. There is also a `javadoc` branch that contains all prior static generation versions of the javadocs in a single tag. + +``` +./site/ +└── docs +    ├── docs +    │ ├── nightly +    │ ├── latest +    │ ├── 1.3.1 +    │ ├── 1.3.0 +    │   └── ... +    └── javadoc +    ├── latest +    ├── 1.3.1 +    ├── 1.3.0 +       └── ... +``` + +### Install + +1. (Optional) Set up venv +``` +python -m venv mkdocs_env +source mkdocs_env/bin/activate +``` + +1. Install required Python libraries +``` +pip install -r requirements.txt +``` + +#### Adding additional versioned documentation + +To build locally with additional docs versions, add them to your working tree. +For now, I'm just adding a single version, and the javadocs directory. + +``` +git worktree add site/docs/docs/1.3.1 docs-1.3.1 +git worktree add site/docs/javadoc javadoc +``` + +## Build + +Run the build command in the root directory, and optionally add `--clean` to force MkDocs to clear previously generated pages. + +``` +mkdocs build [--clean] +``` + +## Run + +Start MkDocs server locally to verify the site looks good. + +``` +mkdocs serve +``` + +## Release process + +Deploying a version of the docs is a two step process: + 1. Cut a new release from the current branch revision. This creates a new branch `docs-`,. + + ``` + .github/bin/deploy_docs.sh -v 1.4.0 + ``` + + See [deploy_docs.sh](.github/bin/deploy_docs.sh) for more details. + + 1. Make sure to add the new version to the list of versions to pull into git worktree. + 1. Follow the steps in [the build process](#build). + 1. Push the generated site to `gh-pages`. + +## Validate Links + +### How links work in this project + +As mentioned in the MkDocs section, when you build MkDocs `mkdocs build`, MkDocs generates the static site in the [`site_dir`](https://www.mkdocs.org/user-guide/configuration/#site_dir) becomes the root of that project and [all links are relative to it](https://www.mkdocs.org/user-guide/writing-your-docs/#internal-links). Note: The default static docs folder name is `site`, so don't get that folder confused with the top-level `/site/` directory. + +``` +./site/ +├── docs +│   ├── docs +│   │  ├── nightly +│   │  │ ├── docs +│   │  │ └── mkdocs.yml +│   │  ├── latest +│   │  │ ├── docs +│   │  │ └── mkdocs.yml +│   │  └── 1.3.1 +│   │  ├── docs +│   │ └── mkdocs.yml +│   └─ javadoc +│   ├── latest +│   └── 1.3.1 +└── mkdocs.yml +``` + +Since there are multiple MkDocs projects that build independently, links between them will initially cause a warning when building. This occurs when `mkdocs-monorepo-plugin` compiles, it must first build the versioned documentation sites before aggregating the top-level site with the generated. Due to the delayed aggregation of subdocs of `mkdocs-monorepo-plugin` there may be warnings that display for the versioned docs that compile without being able to reference documentation it expects outside of the immediate poject due to being off by one or more directories. In other words, if the relative linking required doesn't mirror the directory layout on disk, these errors will occur. The only place this occurs now is with the nav link to javadoc. For more information, refer to: + +To ensure the links work, you may use linkchecker to traverse the links on the livesite when you're running locally. This may eventually be used as part of the build unless a more suitable static solution is found. + +The main issue with using static analysis tools like [mkdocs-linkcheck](https://pypi.org/project/mkdocs-linkcheck) is that they verify links within a single project and do not yet have the ability to analyse a stitched monorepo that we are building with this site. + +A step that hasn't been tested yet is considering to use the [offline plugin](https://squidfunk.github.io/mkdocs-material/setup/building-for-offline-usage/) to build a local offline version and test that the internal offline generated site links all work with mkdocs-linkcheck. This would be much faster and less error prone for internal doc links than depending on a running live site. linkchecker will still be a useful tool to run daily on the site to automate any live linking issues. + +``` +pip install linkchecker + +./linkchecker http://localhost:8000 -r1 -Fcsv/link_warnings.csv + +cat ./link_warnings.csv +``` + +## Things to consider + + - Do not use static links from within the documentation to the public Iceberg site (i.e. [branching](https://iceberg.apache.org/docs/latest/branching)). If you are running in a local environment and made changes to the page you're linking to, your changes mysteriously won't take effect and you'll be scratching your head unless you happen to notice the url bar change. + - Only use relative links. If you want to reference the root (the directory where the main mkdocs.yml is located `site` in our case) use "spec.md" vs "/spec.md". Also, static sites should only reference the `docs/*` (see next point), but docs can reference the static content normally (e.g. `branching.md` page which is a versioned page linking to `spec.md` which is a static page). + - Avoid statically linking a specific version of the documentation ('nightly', 'latest', '1.3.1', etc...) unless it is absolutely relevant to the context being provided. This should almost never be the case unless referencing legacy functionality. + - When internally linking markdown files to other markdown files, [always use the `.md` suffix](https://github.com/mkdocs/mkdocs/issues/2456#issuecomment-881877986). That will indicate to mkdocs exactly how to treat that link depending on the mode the link is compiled with, e.g. if it becomes a /index.html or .html. Using the `.md` extension will work with either mode. diff --git a/site/docs/about.md b/site/docs/about.md new file mode 100644 index 000000000000..b377ca1d1a22 --- /dev/null +++ b/site/docs/about.md @@ -0,0 +1,30 @@ +--- +Title: What is Iceberg? +--- + + +Iceberg is a high-performance format for huge analytic tables. Iceberg brings the reliability and simplicity of SQL tables to big data, while making it possible for engines like Spark, Trino, Flink, Presto, Hive and Impala to safely work with the same tables, at the same time. +
+
diff --git a/site/docs/assets/images/Iceberg-logo-wordmark.png b/site/docs/assets/images/Iceberg-logo-wordmark.png new file mode 100644 index 0000000000000000000000000000000000000000..6ee6cb4a995659161de7b806d3aa37c320a6084a GIT binary patch literal 4874 zcmc(D=Q|sY_jW{R#12C2ttHg1(W=<{V@uE)5o&K*s`d;iTC-?Tw2c|NEn-uOR&6zF zw05;N`StrxJkNP?&bh90pXou%NzgzqW<;CVCsK)|AkKE zzXA`^Mg>{;x(0Y~tuFXQnawySzhB4OE z?Q-j?J&D1%=S1QA1Uj!{_-upwAN~KA`18z09{KRQz#(X$qrUX|PV;0^r77ISJx|EX;=hlka2 z#Z3`|W{NDX+kW>Wm(1Y{TnL~19jh11$NC`k<%L2r(*^Ze=#TvKjy6lmd#>|ZU)7rL z9Gcb0?l=^mcaEa2YOie^rhm z?b^FbB%HU>C<$~s^8Tb}2+a1&vZIcT1ts>!@BuB$51-qMO)P=aX%er`AZ_(#$GEh~ zJINSbPD68n_+$*5T^6Lu83P9r9y|S&$n@9KHZ0l_K1_lAdDbQ>U~13699MIVYWi1I^Mt3dGax{7 z+gx1Sp)IY5jl184V>b}#UwOc&-KYxm43C{Z%(1MhzSmA@E((CivdW41At_QPGVtz@ z0rZ1MT$h!&7X~R9Xtyg>Umdmk@f_3{4)jzlT#T7eNrY`Jm;)zdIae7SH86{| z4=zOGyW_!?Pf`ebg1BY&>q)I6iJ*ad|G+k8?&4^4G6FQYnD+ZgfHz~0mGua~(NHYR zKu%p?P@U*lGqKPvGWyx$)XVu6aBDBAC8bcr=Bd3*o*6tU1{x#q86c8WskP#fyXjlQ zn~bR&WqBSt>e?=<+Di7yb(6aAr*IAmXrHLRmhukD_e(DR_UDf?c~=05P5Q?B*?39o z_3ImD#;0-V%Cc0e)90tSBpJjcc-UYSRaWQKhDC7s^bk!)uj@}6Gw5#d*FNEhdc1Gi z%?KU%@i3mf5K~5O8U%3)W$`@JEC`TtQ%j>&+WRStwEm|3W4oh1D#HC0K8Tx?kwSUr z#?qV2OlWq&T);B#2!;G%@Y#o&5EbyrHu1CG@FDF6#bQOs-4V=Vvw$N*#0S&6GLtSh zw3gKqAcSvf!05mAP6>Xt8`kht&Y`?p1+;9ox_zC72834HN;bENwa3%;4;x z$%vTrC{0|crvx9Df@QpJnr19%tWFF1of{%lgPO9wjeiToh2O0GLx~CnytU;?koxcJ zyvEW6&3Tv5!2i50rM@X&wO16f@*6Dj1{mPC7C6f zPpwy~;4pi)v0Y=4p%paVyiM1aDx32|7V%!+{MHYe(#pc`S}Q`=nDAWr0Lb$ErkX%P zqvYiuq>0_j&3e@E4EO8Vf|gM>goHm93$oU^F=OrRC&4fopI(~-XN?xEhOya<^ZVZJ zT@zlbU1IURxpN;rFFx3iqqY|t?D8PD)%&X$+-A=IG<@PErXtn^RMpsglM`sLJi`A| ze$j7gQ_YG&Ncz8CRBTfXGb&>&V3{-^%?_uoiI42Jhz;d7a|ZyV4`L%Z?mn}!S{L3( zm=f+7Q2I6{pdrzHd-Q}y(qNYFfWUNp5Dr&bNXd@VL#3M2(VsuH%5dzwP6Zs|#GPte ztutJSiX{RZz3dc>!F2SOmDYw34TkChJ7N72PT@HhTb9d2w2QggNZ(yaxm?f zHA6;kYS3h0D&20~i2v>cMv#^zk?t8c4THA?%Igpy;~nz+OZ}e`y)mjYC9MbkdfU)( zis>6lNwxXo*NPKSR`;q>Kz{^4*~+j!{Uf?sJGQ0CMu+P50Y6VxexKX@4mzX1i$G;` z|CW`cTXf@p0=y(EUylwp39yvf$;z-hW9mxYD~7>vy}GP4Oem!6W>e)r5k(W!N{HSl zy8>7sC&Lb%CdR+1BiiAVbTRGScp@rnZXby|U=2-PC^!rs#~e%4>- zdI5pfDaD-4zIx)Yp4)?OdyNsZsNSM@-x9qj(kFArh)%Ce*&8AcP9@x;6(HB+Vv-~x zvzm4W+>xUxq%ixj)U?NVD`%68SNvy+gO`$hG^X6gD=C#+f;b5RTTM1FTM&sf?F>^^ zdKC3=z@y^E3Iv)Yfn%$ow+=T!_tdIr%zc)AzevY9irm<<#XuZnV=2b6Ib!H93yND}yi zD0&yq?4~Z~n`SW)nRXbW$TgsZ4HF9Bl;P1eea-Tx=Y|OPJBChpiyvEgx}`AWa$UiA ze+2;stbit3*=-$YXHYOlmb|-4WpE}`?VaIeVA9P7pSFSgt$BD9RB1R^s@SI#07e9g zt0}8z7+F3LAo7QnQ*0=sy^huSG#n`a7d=cFRCz363ri02GC)E-2Tv0XAPKPv8`oeE z5=+o7@g;ygg;pzNc&-jb@AU z<#0RTu6>H536&vwSIMVc{QYugxRmI;vKR5}Q1&6&t46whL@2PNocU>2-HEF2sTa7? zeu5l6Ozr5IArN7rHTIQOa3;uX6}vTpjtw+foR=T-Qq zSfu2E&sYxi)qNI4q4)uWeTh)vYx8w)wPOy^odDj=g$fZk1h}=9x_=W;726X{e%VYt za%D-$tL_#*A>?0a)p2m?%kA;--rNSFr|mM3U8FXwF~wAFb9PBchzOWH)@DX~C*G#^ zDgbA+47wbe;xv#BjA{6@%d-~a*m(aZJyU?(Bm}P8ufIW5i$7-^)IR1Ga-?iVWzEMJ z-YzFyT{pDm1rv0By5DSV+QX4jTnap=p5&cm3|)9>l8gGi7wgjtlZ(A(cmHb^)4Zkf1HAu+?@;mg z7roRWQ>U!y1@WmT4$3ozyRTn88E-O=U_Xyu{L|1ONuO#Vn}_TZE63I5H-7wlNer8p z(*nc13ACF1PP_Z^vQ?{-OKNGp#^}pde?_iiUiKfWb0#~-0TwvC#AGFV;InZM*Ke^|B-SeRhhEWn20SDFK@d zL<(2$pqg(4NY#4uXUK_dx*1Ts3D!O1qg35699C9eD~ZgX*d#YG!Jw zo$GyNUn2T!1wKI|nwkPP-m#7@xJ6^J1)9D9x4gC&+7%9KDjjqx*EYg6j4~SKjd-70 zq&aWGXsx@PvR4z@bOMv(B|z5ptCjSsd8wGEaxw#$(iBn_Wzwp;NDsTIC%aaK9vwAPclKOzvJ=u(D91W{6Ym2c40YqR299pZBQBRQP8c`_i$0UvWKlZ zmZ{UV3TW|RcTG|`a`G{zQMUxB?*D`LU4RDG;ZLAAdp!HEC zMTw>`Z%Xo*+++_`H!`ixJzZ_ZKO8j6YRdVZZo0V``sV`(Dr8!SbJvt7!a`mAv;- z)2iO(VXwo~h%0nSbR(A%)PVM%eo5aup|aNMH%5~oqKM`?yL5^PMN$3!{#(sKQ)(sC zBLvXFZ(m;Y;xu9Ruk`YlNXHv&4qS`x;iJ_(Cz9ftT!+@86#35je<79i=Et=LrX$R> z%dgoRWI6b+7CjgU!fkx!VD0Wh*sYA*z@0rw!Cmv!o>|f@I(a+~oSmisvX1+v6HhvG zDljB>=0wLtfpRm7qNE~M*F`j3F{@)tg&W}_q?w#NP5zih;qgp?saU#~o_Ezx$(ISG z^Ry2Lcd>8bxFIszI`!yzSj&eK47p91pU+Ns@@q4+5i7*1X=Kj#?qPxZ7j=ArU7HJS zTw2_gg(F$|vlCY4pU3V~w(RANi4Ix9u+d1cDgHj6>gxz6kQJ@dJqkS2X#OPVG~^5e zUr-tHZKgKS`!i~Ke%Z0;!IM9TNo~iS-;vZxwUYlXmU%|L2bXxgXw@IZ1H-@ z4(QoD-yoo1=j(SUa6-U=7lQe;Ucy8o+wqpleSM~2{xPwqR-5rzb$7u95V&<*=rzj{ zdz;@B>11CUzyx$|eRp9g7FYpATRrWWK>AovGodXgfaD1Q8}!O~hfrl~*a1Z`=9Q^;bOpT_m7I-$$t?pNhMJ`x9yF*=)d- zJKB@c^@~aJWk|v5SMpL!Hcap#19xJvMjFOqs!dw}jgtUP9`)VGOZDPz{+0AUl<;e{GIk2q|g`BBa}EBpVz=yO9Y>$h`oOA!uX618l`B_+$OK=I<; zz>xmUR83yx!x-mIRkvnySt_F2Y9bWoCQy6d=apDK)wlTDzb~I4i7%80RK>4c)VKep zr)ddLA`;6t0*>ylaEKlfU4EZCA^nX|?p?vwpD2ZDLxzDDB4|?U(1-c=evDQUip=p) z)Vo@hB_pGO!9~>%`YUg3*nHZjBH~-3lqOke)SfE>V@V!vuS#AAvf8HAI?L%?M_TGz zo$%S#X`qmJ)NWeV<2Qan2~~1ig!wPC~v$ubcVi)Cbimx2j~30C|T z!so1W7c0DYHw`1C%#(#P4jyG`L-K?aaxx;TFz857xHJR11YkP4V z?9O9KHKLKTiobWl((S3x_d-JyI3-mYSR&XSh`*F*m|jGzO;M#>sikz7?mb?J_Z3jH zW;Acg*Hjq(GGaOH)mImBN$(wg>&iCdt@?h-yf8wg*#FH8DLj%sg!NwI{m``95_2j5 zAj5e^4>RS;J*Vwu&Dq`Fink#$qkhIu1y<(j{u}4@5xwIk^Ce^c-+g#qZCp4+Q2-9^ VFI(wp9+Ce?tgmgNRjuI|^M7+}q|N{U literal 0 HcmV?d00001 diff --git a/site/docs/assets/images/Iceberg-logo.png b/site/docs/assets/images/Iceberg-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..82f18a2ef1bfd14d52d6f23dd5113e4f67660bf2 GIT binary patch literal 11068 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 CKb9Z> literal 0 HcmV?d00001 diff --git a/site/docs/assets/images/favicon-32x32.png b/site/docs/assets/images/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..0f61caf936f7be15c8bb57e8fd49e443fa89612e GIT binary patch literal 2101 zcmZ{k2~g8n5{Dl-xxtGgpvEYI0=Wz%1SEk8N(P940U_#25(9~VL5P5enjkPiTn^>X z;g&%}5sfHEBp5&uR|f|bK}QZ}bifl-MARtz$J(lOr)uj}zpmH)^{@NYt9n_1ob@J# z7()Pn3EP(y40-IF>7$@MAthxBa)>Ce051SkxkfrZ5<2T81h4l74ZXI{=18G$Z~y=) zD*=$F1Mmq7$;ScMO$6Y11OW7709eMAs)IbB1WMqyo&{#-R;8x)C=@Y>^9@gcyze(# zcO%*p0OTn)%ZnS~;p<<7C6!l~4Q|-8+K^*}M*L*yGu&I| zY2IN!KfT$gVYst!e4Q84=URr%^$hq9TU^ct-28p(`UW=!_xBIEJUXpzejS&StuEx! zL$;n>x&{AX|A^b{?8gHm$I}`7iQc^cb4)K%5nFf0fGZEds`uS~QuMjNIZds7s`VV5 zAAz^a^Xq~|AZdoWrO9ZTar&P3o})zzWsadpPf!l`L>&oYWzP5QNP>M< ze4a&25^)5O3xCRA(TmMX+lKH!J<2~V|6(*Q8M8>XoH5f(5q(*?;DAMoYJ?{@IP0J` zwBGEZnZc?korvgpqf43dlEKWbQ?Lm_lX?E)EYfXwl3tim&Ec-zwn+MmyxoWgmEA){%?97rza~D=`^x@dfAT?=Qqbi; zTr)~Xpq^LNSn%3 zkW-G)+U2IEXV^4Zt~uAjd)1lN5&!gB$BS*W;Iw@>RV5iKTEwWbez>vDT-=O%ehGV)>8Ba0vF@(hoT!X58~h;wZJPN7 z*S4@F0DJnm1}-ZEHG}mvY-Vs<6py= z*1L@MiymvT{k`!Jd#!C-1;biZl%;>4*T%6SOj6a&@PVn?Dj42#NcvIK+L19f5wUtK z@Lr${5p~S)2QK??)$p`9kvC&tl_VIP(pdC7FZk8R<^iE_t%c)~Po=APD!U10P(pD2 z*YVGbv-SDr!Ql;o<%GLoXM|;xkjHH!0v;U3`_9A_{o}=HQ(4tWB{hz~x=4D6CDSj5 zGugF*0j4<*lP~I2e9FSyyi)K+W9ml9cxG)IL=lMXVcNS3nN$_N#=@`AFu2}T#6 z(e6UtlUGo)7b<9^?e$f^WRx!7URVi)PDlDqrS0X}xbKHEVBCcoFC7if%Od9ARmzv= z1?2^$wP5-TUGja%nnNEtk0llyzByaW92|HR8?mQRyvgxo_Jh0E?XWrCWIShCHvXyP znt|hf-x$}B2xmKHh1N{FxB7&0Wud%Ud8@8J@Q%((IEe}1)HgKVu{JWDw3sxJHztnz zL^fO*I9b0F{-aGbQn|pdKik3I@;vWPn{igA^gvj|;{oL9Y|+E^4&@SbyP*e6mpIi| zV(m0^``O}aw@wvZFM827`xKV_LhrqP=)v?$@rB(xOb%wZxHU!G1{Kqch1$OL($|h( zR!n|6RfOyO;|;1}|Jn^_A0NFTe*4k*1m(!8^BK|IC95*oB)8VU+PX=F^x3N%%N$(a zq`IC8>n&e(LH)4uA;p2}ZmPph5{V;dMjdk``1(2RLG799JIioR#U-h4-+TPZta2?L zhDpI}WKYT(ZeuGPUDdpKE%3Oi%+9)W*!|=tMXjP%{;$yq>X5rNs(qEhg!QTTZnWZz{3h*PJP+B9{)$f3OGT$w%bWcp^pC3s-tDS9IE^-A zi@$U)zPKFvyzOK@VY2OfnTRfo7eNk?i6ojciR4UjPRHgi9{mos@?w- z#7ehIBKQ3N0QEYyouewjXmSbv8ENMc2@ zJ#ixNZ7M?C&M3x(P|n|U0=N(bNG=qztIHZ9k*a1GhCqnfcRCv-DI!4h_AWT#`4UQn zewPXfW`jscj0n0uA$F%MMjAzkJ*~Lvjrc!CHB0iB% hAqyyEk}FL}!9x{zXg(!}uzfhJ2JeWpe*;=yXz&04 literal 0 HcmV?d00001 diff --git a/site/docs/assets/images/favicon-96x96.png b/site/docs/assets/images/favicon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..5fa8e5ee9671105c88813d1c1dbd31afdbd557e7 GIT binary patch literal 5456 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 Fe*r5N&W`{9 literal 0 HcmV?d00001 diff --git a/site/docs/assets/images/favicon.ico b/site/docs/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2a1daead1ba7c8503e6e07dda5cb57ccc2081ac4 GIT binary patch literal 1150 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 Fe*r5N&W`{9 literal 0 HcmV?d00001 diff --git a/site/docs/assets/images/iceberg-logo-icon.png b/site/docs/assets/images/iceberg-logo-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e4a99c3951e6aa99d6e44575f236f32aeb5710eb GIT binary patch literal 17608 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 literal 0 HcmV?d00001 diff --git a/site/docs/assets/images/iceberg-metadata.png b/site/docs/assets/images/iceberg-metadata.png new file mode 100644 index 0000000000000000000000000000000000000000..48a1b0cee80ef44a43d233f04fca307e5cd6e268 GIT binary patch literal 140290 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 z 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

(K#ir*OG>Apd(oWd6xh5O<6ow1 zN78M=S<(MkWc$Wq-t*Eahi@NsEWeikt;4HACV$+1RzOwEU>tcYPjv%!E?76~@B3e# z^8&_<=<((y;&HI=vAuHR|*U_kiW%ljKqRs>Co} ze+WrBu>eCgYiPrJBmHU2Fg_L$Qk_{xdf&f;G5(Jy%FMW{50$Cvne*{c$!fGiL;kTr zKPzrI1YA%_$#1!Sa9*;}3uqw?* z$wyeP7x2B=iC{BR(5-mp=COU*^Y@DfJT*fNBgl5)p9+ZqULf~Lnqor3j&B>rT z67bMhvAef+JtKAer>Tv;R){A5qNZ_*FV&hyPHJuYbMCfhLk%8hl}wR)X%r`PC5+$N z(_wrs#&|@doai0Gvz7vCRC8S=;9K|d_MM!wVh!Wk)Uy>69!eRzaS#Tmks9qF@ASQz z3pZ(yGs{>39jOpoNyr}L&b>6g^0DI+Pth*5+NLX=+WV`@gk!!fl}v#gBciAsz#wSD zx9*@b3u`l!wVZyY`rCj53m`T#qG82_Aa;4@=Pj69UNx5`zM6aALsHD%^?19hM^{)w z!z>iksLi}O9>A>6{RC$Qd+xIl2IPL$V=VYfHyGV9Ae3)cR-B#yP<}wI{5-`d8bRpn zVCNv?6)sW+7qWpJ)b8<+cbG}JA5h*%Vb{ebLR9_tQ}k1c4t$ipJ;MDaOr(~c+GFe* zika9#CJtf{l;SY#gbk_3I#iJv$?VZ6+xO0J7R+lP=U)Hg&ZF!AD{FKwWy(JJRY6iD z`2t#sAKK_hw4g>oBAi&MFz`TWSYhoIPs!;d%Kb43@8Bmciv&F&jKF4nH z@t?L!e%WJN)J#cs@N)8Em`4nVjg-U*9M2IqWar-GekG(&_-Zpo`{au-*h_?z4DRDk zG(OmrxhRm;f4lXtLW*Yb5k{PZ;L*o(Z8Gv`NCg`YmHlWFM$W{Cxd6%J3)||EWhvMQ zZxbQPt4?_E#hf2oB@W|`HC!}v~V3oqSa_fTM1jZN7n@szwkWy`|o87(yzF?F? z!}P29z&1-Zf7$}zTt#T`J7Fd>2R%}M%av65pa@rF6 zA+tusEiSGR5#1o>6;74}ut0D{;u{W7`2_M&w2zMjbEwxwa1~zVCqx#^RREs~XWMfA zl1tUGf(1SP&kVWMb@WdjHRE@JjfHc-G32%IQ;PiF%r+76z|Id~eWhBfpg#m!``P%o(lBp|-r&RR@+0LPPI-#VdhpDBt(c)$rsv*4;*P-`K&2e4-1c@#3;0WGp?5Y( zL*~ebZ6NH5u^al9(VC`STd;Y7oezLq&CEpmHX{6X7TUg27uvQQ!Ct^}t91l=WHARg zz&PQZ2&9n#y)5jBeY+SilKs|Y#lHI=$$1w-p3M>KNuxZrxU?DfszgM0`U9b}8 zNV(4@Hy=Rs6x1ElV(zRru{aTdok(aV`T2$ccEg$Mv>-p){MQHhqJZ#Jdq?XX=pM8R zoXW|DVmcdSWUcGG#HJNshL>1l2QW{<^m$ykTo~jg)qG}Yw%fnpg1GR6VR61feJHmz zVKh-rwfHr@m20t4-wj8m?haHn21=NVaJ9TQV7($axYGqxUF=sfqBE%%!35Yxg^|v*#~KHfyQpSCSlCY^ zfg?54cPWl;MW=-({WlA}wg@e-7Sg2EPh=44fBtM{5OmJt6Hcjok?~p$MTLDthI&*g z?>C?#G-fDdCT8DXNTN3Akbt+9B@k!y{KUV2ew8xW^Rl~lZ1aM=MiT+yaCEHFLemZ3 z)O#?3p*{3vt=ki1GzZO7%BI&ma9JgHlarZCB>Fd$l+DysTftVeX>(tJz6~T>AEfo( z$e-@F3J|GInt(v0ViGTX1_t{WQ=@}<)8sMhtw)aLQ_?Ol$QqX_wgXSgi?AdDXooCq zxu--U!}6ZcZYZKGf96)y7^DS|)Wn%1m9QS|$Myx}IFA!+ zyrjg&{mR?SYZu=nAFuA$h^iT%UK$~3UWAi@1>9ML7v?6#EeV5Hye9$1FSJ|=ec%`$ z&p1-QK(iD_ zJBxNdLh^i3VM)Z;B`Is13aI2q>6>;2Oe@~pTGJ3$-f)QC!2LL(dEN&i(>%84DbBvt z`2b|qp9xTB%O#WF?L0uYG7HYYgf1=^RF!V_oP}`OLU^+5M(Kt@c}Cz#DwpcGj$5_e^Jpqv=_I6$wCB*Ao!k0ZOaM~Kh+`n_lih?xMb$W9 z&+eb#JTn}?voc|1SPESOHZbc#gxPfr z^fCkYDtjGni->1aoQEL(}?Z~sxReMjME8m37@+}lo%WM-8j$%-g+**7EETMh4 zl>kX^HGc;jwG6b$(^>9kTN5-C(%+lXw(umfhB|+P6dgsSHr_F(997{Ac<^n!hTD0^ z`C2>M#vBGh$r@N~PUAoN(=?p`dv+d#dcCW-!ljPq=!NHekt{HiAsZL zO>VmfI2b9Bdc5v?snNCvwnFFC5YO51b~oDP+cTso>nQFyt<0C#wJoYNp{X;%pM;17;6OYVSNY1yxQW7-!DP>h1OyZ~J5M;&)!u{^j z%dhW__!qwnq5jmx?q7uIGg?or{^Vo>$dUyG1E?}ixser%jjl{e$Lto*+p!y%u^X(;u7QBNTWm8^}fgGhuugs zP>6=q_Wpez49pgckT@Ef7bNtDI}_2~OxF*Fi`)%ZDrdYajV-E>K;!w11i@rHXfPR4 zo|FE2g1^BsK3YM>>X|C|^n)UTWjhX;OP2Oe2HpXC6^N|6U?_Hk^Am>O$RP8)p*T88 z#o!2c3?pkEpm?S4CTDwJymZA{?m1z$a6RcKB3}0B;Hrj`b*sW3aOXzrYCEFkN49!B zP6z9-BsXb8eK!zz&(+LOlNgCgi@doHC&Q%j9%6qKsV6lc%+iYrvoB6zP7UTi%#HQg=q%NnGlvde$vYHE5|7!e+X*fG+`X%i6|<${+ZU|#J|7sS9%w5eqn$NMr3mk=FD zhW7HReOV~*ae>Y!aZb8}ryZuh>Oi8na)h;-fMUlRK|&1RCyT!ER+pzO-<*}5k7hE5 zWG4Jkb_?O*sG-LZ*8lZ5>1J7)^|1A~HvJ0U`j`8e+CwoGZUP_S&=1tk zjI%u*is1woQVwm9`=ZBH=GD0X*1Ka4e5m z1W;w62a?{Hqdy5N5H5$CI+Qso0D9M{r-@v79PvYD=3 zf{vNsN-f<_t_pq z31Ivf4bypZrkty zeLaY0#m{IZtx_q}H~nuB=M{(bLwFa;4+rOrQu!J0+UMaSYU$#$Sr*1y{eQr)yDXoW z8s0A3DTK?s&=E~h4YW0LbTu6mk94wv{n*rgs=3A)&hN`pOdsWzXGnk-^X8FRXPS(~ z;+|gx{NhpLPUiFLY($O=h)o3oP>Q@}c!)!7r?W(h=oGKpfHSLI&%drC+FO?2f*ObC z6Kp=!cCg6UV1x1todv7MIRw^@vBUnj^|oE5vaw{Ovg$V8feZp(1sv0vl&MaXZ*3(} zE&qV2yBnG+h-?_$!y4SxwsLCVR9E4!oUE*^maGEe{|swqZIsoK(jC-jk5p)#KLa)I zoZwrqhfT{^2A$KRqSXLb8Rp`zc!kGEVhSC5UNj^T#KUmUSkR<>*O~)c+ro1}T-WoX z_XOmBTqTv=k_5MA8P?`U7{BNbHvl`bpp-ZU)$ttWAcE@Lh8@~Qb4yadc0tVO4zbok zdLDLZ2D%K|jfFSsaQx(~87x4b&I#!WwcB|#+dGeMH*u#1>3V!#?S;acaD%_a6o3Y92ap3RLqaG=Y+#-82F9c9efcBtp0?~iiJ@`O-QLX9V!VNzZ zgXkCMcBEVfE4`{pEfZqR)lbI?X|e%XrR5fr!EJa<3$lVEI=1>OyfdB`Z3|vV@Th(6 zxSU88%|PkDA`8)r{GREc!ck8c5UaRKdQy726Xy2P{Fth1&g8t)e( zSGU7;8|ATv&1aR9m=~%x)Q%D%%VH|J26IUp)h|2PIv(wjRVhlPtcx}>n2d|$Q_+{) zeM`tqf7->~$rO4L%=sFc&3kKC*y=4K?Q%}40oSB5V<4s1QVr^H=#Fa@aa?SmEYf82 zcZZQh#OW&P5Rl4&Gq5Vj?65<3f6+TJouKqCwYYwyd8mW^%6R0@r1}Y^#JUXy6_UNJ%ZuZ9-weZdl;2XReR`+`j4&uZ- zyW(ad|9mdz$+eM;n)RS$=rmo>V}aG+((fv)~&|rk-OjP&CuOQrc&29q{P%z zc*(juG}}q%z?9{QOBhhn_esNriVT}(fME%+4$VII-Df9TcruFm)GAWuU#nxhZoLi_ zl0JA$amT_wuPyFW1iC5b9kAQi&V2ZwCErwQvaC-3cTF>*72Sx3!RKkfLNYmBQU9kz zLwo9*#l2fZOqD|lRafccexE%Zf3;CGtZ>~K-l$b<6bQ#QHwF*i*wDgr`Jb2`{sD(8&Pu$2P>Si33 z9S8{G_T`}nI6W*EKllTC!F^lNez4PMXx}Af=+m!T-d;0K9QxB%gqvcr4&F=BtWfXX zWnwcj8=CrMSRhhG4s#17{$Yt>K6w{|h4Lzq!A6}l~T3FrW8rnB;AcAi+ZOO6#;=w!4I zdtApveV2{dy4x4-xywaz#!se$nMI`9P;Qf$F-f&+Zn46Mz)h$^3Y9R?^6F^AZ_-X0 zFVSc{Zp>0x7gPK|CP0L+C^nuzaTBm1?xiTTAj*=@7+V;D&XUI$=J0k}l72>pXV#~3 zeM?P6s;QuMD~s6g;bo$WYDLu4id*7OS1M?}CEv1{>M%=FqyE$~y$A0IPr>oJFJ8$s z3_EDjNI2K^cv8>M26OcA=snAy!I&D&VLODAy!$6_MEu7R&3ha}1Q=eqSl`-GHc{&c zoX22Q@V*Gd?v*lRXjLP+uNU&3j|CUClYgtc0EX)}92sEMm^pimeGu*@)O97d2Z#Xy zj-={Hx+A$pt~u-q(L2l(m`XP}>s0X_i0^iLTn~mspYY-+t=WwIV_JcO_r;8_M07mY z9lQ2lZ|TRGWQxDc-FLaSN;iB&RiZdG$)>saY^|6u)k1eR`wh`r&bL$L9s=dgQ8qyhiuSr!MwaFx>w= zdv#W)U?w2(V|De;nNOKSPx{NX+f%C3^sy1bNP>VP24`0Jh*hDW@YT{szzNlgiN{ee z2&I0l7G{I9Ln}05ShGB+KTv5Yx@)MyYC3GM3gm_Qvf5A{a5yq%GpaW1!OT>KBMCg} z)hEL&4=VGN(PGrT8U^035FirwP(-Aj3_}`_S(#hDNwLngt_~**F8V{pOIfR*c?j`IHO6^`9R8 zmMn1aF}UA5OkT7zP^W*?w8PEgy%v3#!#~cP(jvmdg-Fp8%pr4?6H&q+PpN++S!4ZD z3kt|zaYA1qZxcpN_ZNI`^4(vVBu6`r3gxl$d>D*pu5oBk|( z5d9Mt02jwY+_iGS?@o>l;Wv2oM+JFLs|8=8!yTE*FqKvovz}>4Jrl6VI5Z)&ev(^E zM94;FA-;=eSzPw!UBk~Hoa`65ALREy%9vM~|3LHA6PZRhJD`$!ZreQ5D(PEi6@k<_ zg)c2gLalo8)4!c{nvb^mJy;Y>evNL+QPL-bTQyy)Gu7!4qSUW`dd>^V1^*+=5otJpX(1}47_I)apIyHKMA-lZ(t?tQ}SP6j= z0x5hl2EKLnbF|t1iI@AyVKdP;X!QzUHVKE)2l!%!kn^_twl1ZsQL6 zxQ&k6ZuKP-Vqs+sq0ZgDOD=3s+rE=@?4ZwzkbJr6W??l2RGLdVYsHf?(Mw41P%K{G zwTFQZl?!yuUo=Q{vM9$m>X_u!JtsOHXN&SRj?Y=*#KMrcY3z(S3BQi4;ta~{px!|l zq;VcIk@L293!BqXe6`6Ja^)k4DBjVpBk2UXFRdbwiOMFgs0ALq_1lUN5bt zMcjNrRqxJn^~ug^QrEr(f&8a-n)t6KrHh>O><20@S>vo%3>a@q<(GLktOvmaZPjB< zn!4B5BHa;Ygol;(yfb=1f=2e|JM~Z$Vl`2U%IQ%=avOe<&Lr6N*ql8aQcLgb@!`Vt zi$L@GiEaFO7to><#(gj$bk%!D;AH`0eP* zF4i#sXiiZr9YfiKdc%uvj=xzDIcNmtJ!XxIp`p!}Qhss=dVp(4=fU;ogyu{bY4V?u zrcRMM=GEjeb`tB+9~rEB!)s6=EU0Xar?#VI-!FXCWD-muCSXv)Rf%?d-A+f#DEFI~ zY~@Mb99`BItkPO^meQ6m4ntlEDSUbdf?Jf*uG*KTCIEGPO&8+&!8$&pkeX0Y=Yr2d zUH<*JwEP$p_1s+6himSJRg4NxTI@WnYgos zZc{6p)OkFFy>M9eOgGDnANA?D8j=g zFHn%rVU&KGTLheEE31l}%aFohNUrc&0THA8T&2x?g2yoQzg7Qa9=^;z88^<3 zm^3+mKLqknL*_?jAwxd)+>2~P3ZHO}7+0VQs0_})rh{^Hl^&5Oezslq?DqSH<3(26 z79x*)D!P)>?o_yeoFxKLZ491sF8rFeFu6!F6#C^0B9-+Cg+fofg#TQgzSq#<43T>{ zzg4%t@gz>YB~kM*>WAgMGN2E4&G$WPMrtxcYR*PYkRQ1j^QC)cY`4^Cr$RMn87qr8 z=ai)oij90l7OFVi_IovnoOt0MuR6~7{Z#(ec9`kkgjH5=na9IS$J%Aad-B}@`*?l?uriR zoAToZ!~|n!5kiHjF6^3))f=~QWJB#d#3L7MoFHE+l*&B%EVLGx2+U>q-eUs+NIx<1 zWTb<;tsjI^J^2;iU)$8R+q5wB$lIzoi5uw4$vBjSRYzh=WweY5lt)FwA;$PUOEbT? zq;HGAXQ6U9zNBL#hI5j;)TTK9S2feRzVvcM6_Rydyn#ziH}Vz%P}^ihh#VqJlgX8g z*@*a&M7%HG9&Bcm)VQe2T{*#|Px{kHD{R18HYATJr;{%<;DbgS=3J^v+3;LfRbt^; z55xZ4^8Dc23B$7Hhd=XVzaUXcf&kLVrz|ok;2i^qTX{DKYbeZrGIe-2>uYQ(1|oeg?iT^ z1y-SA_coZR8UoDrt!%a~D&=4nFwN-e$Olh)oSlg1K`dB-k}R8MqRLZdA&)7|t7-2O zqkWBSos{w873_vdHFoOxA+{irs6FJVVMoQKP=xCmEo*|X;W~2}S8#BC@IP$~(Q_`!#fp8bHA*dNd1|RPq#lLG zhzebj)t|@14U9yrbkDT%w{qV#C$2{6E4^c6T1=8BvX)@sH;J-b4K7@(|7s~5c$9rg zfWNlSEn`&ifk;x>da8a%1iaC**Ufl(6_tII#nnSv!M0(qsC6!UalZNSly`2E`L+ws z#(~FusqygliTO*r+bNSc*aQ4Ti+hB1DcQhMy+VCTVxlRBUwaBUx7=chN9+a$1W--%6*@A?ZqI#P_=IX%h?J zQnf!Uq8_^9b&N$PRCqcPx^yE%_SpgBGs?bp4E^Dv0~gx1()DlH^mRKq!USsKNl?!W zhdt>@o4IkAF0Sm59;w-0CU22vOmUGNp-%pn=AUT*U*Ib z3TTZ58F!vfyePXPDWTboeVu2<6BU=St`0AxTYJeOXW}}F`sSj$f`%{Qm5V0K=<;vN zYY^~aDI@wNJ2J^KG(NCTST4$KFlrCP#nDV9_^+aVYLiXoFYuN(*OI82{{)b3dV*PF3t5NL7+uEaT?7L@1S?xoKBXg{2y!$v7A`Hpw$NLkS@NpymAup|dKQtlYd4HN(A^W{WVaFqjc*sb^ZBEj z?M>Yje$*0}3BLK!9nn70t1*DrcY)kfABo&E`m?6t0CBxBKnVR;+mnom{Y{X!PzkA@ zknm_*v)Pcl&k_iw)G?)$2eYlOq9;CU4CYwv2UsFbqg8)1Iq_DrPjTig5(q6k!EW=Y za^f<$cxP$+Zc8j`Q~ni_w&>@%Qr-!&x3YfEWnnLR!~*&W_`XBQznxaHFm^tK0~Z>A zN;J?Jv^aJa2F2Kn%JfybS=LybiH!_-`MRarjO|C@*R%$8;A+2-=Gs?g@h5y8m{|5~ zXaZU7E(xK-^{zb`*nX#+45&_TU`v_k9K}d%pC5oI2EngejHgemAR*-13dz(thdqX| zkoQ&cJN}671b0mSA#LML^h9y(ag!`AgrrOCbV-n-HtBG0iS0R_NL7Jo&{t*?;JzG1 zIH}o!u3oO5bPH#r{Px>in5V&rZ=FCF9f;~r@v0`i3gJx93&5`#f71vmCW9zdUF=cS zmjM&=vHcQQrT5URVz*#Nzoj<0L=U~OWH9qaEy1#QLdo4eQjBAsw2_6C%QB0dsmeqZ zDL(#jvkpCXLok6-ReOHx%@~ zmf8ffRe7)Wy#hbT3AB9)l^@u?yWg&Xmc_@}CiZpR-dj2J%CIW*(%=nGH5i`e_b zdcw4d5lywi_{t3yZ`w`2m(o+97d+Jt8( zh@aH>%%z(k*UOc)*~Bw1Zr$|;9>C*ld76KTiO(2^dm9v^Aje}xe%@z-`=dm=Kl`Fk zav4?|g8_t5+XtF{cZHG5Vj;csf?fu@8 zZBskO=se+w3Co)98Ij}zS#g|fNTDw)d~4sy!{L6sk7^x)GIx0QcH3mQCqcY=*V6zU zTm}!!^OmqL3M1Wf@UoX6EneSrdH#ivCH!pBgOTHrL3AtvTFOq7me@I0Qs>LKy&?md zg(CR3cy0C5^32B$KYE&ti9Y*+e1Aw~K;OE4-#^6?zwWUTd(IeSJ$1`VG7nq48ZT*( zuLWOcF7w3ql|W_A9?LR<0ir%gLydO*I!?r`-xd;PQaQ$l@o~!}(dcIR^Uop#Ho&S?&ivZM1-+}L(igX3y^UKjE^zLk$VgpP{!@Gp@c zr}PypUe}4sH)TiEdq5Kl=B(?eW(-M21K;aO`$B4Ly5KYYW8$DhNCZF`Jr;faH%3!a zhSH+RPe~&BOs>xjOJ9o8BB7t?6~PI~n@k>LBd6aPM43IvTA35@ySB=QQ+e%mjQ!un zp*mV{yyJl~{nXK(VDH_K%U&=y>CZRv7M}3SCLKP4PWWQgBOyXE^ViWq;s7M>R1)R- zzd}Y;^b$erw0HEnck`N$P2l^g;ftw%X6wHmCb>kQyHmbS-%i%i_Qzw$S6 z6ab&!&A0L*3eW5=T)?osn_S#%b$t+*T~C@NhMoW&_#uAU*g^Z~&$N9tf zA%yOZ{4y5TeR~r0t5nIGm}?c6K4+3*+0&zKsO$Zn`7A;N1_M5?#?r(-?8Cp$a9vz` zAYFV3P~r!O*>w00)^-3XS4RK|N9HO!AJ-RHH3l$@cr0hiEYJOb_@8o9`^(&pJ3smz zf^U-QK`70Lm%ZtlJZRv%_BZEsd{=1w6fOt65WCl?2SeCw^FM(W{Ho5amq{>+LNDpiH?!yN zy?|S3&LfZWU+2O39`f(vf=`ymPWF^+ZUS5v5pW>*!QXZF@BivQgZsvPoBr?X{U3h` zKavB2yA0h~s{j1Ce?P4RlnN|lK@orPnE&?qUW!n5$roGyZ`r(K$Ps!~RN`C|^#A!@ z=btZ#*wS-UEC082T64$~?|geNO<1|6A?L6qf zW2a8uTm((&rvu19lO_<3jgrI)r2)GN8Po09^_9EtgxR}Oc8^K6yrmSv4 zjhMfjY}Yyf{`jCV7D;dngk&sq2E$CHeL{vH4dDc9Z#H6z?gm?p4%og)IbMOLac&OW zjgk9@koiKcwG2&hc2Fas3hL%r6rxdFWmgG3gzUjAXJDiA+Ctq1z!x!sFp8G0YG8Zp znzZY+$o%ly)DFI9%N0Pv!@T_<)v0m+LxdyZ%X#7j5HH*5ojWj`Fa<{>F;+jpJ9%x+=~Nb;^A}+AVB93bs1hv z`AERRxwaIsvpJS5Rn_tM-=zn4oVw|2lh`>p~pvM}6#;Jmf4 z?=+Mpn}lPh6#WxPF4IGNb=o^FR&R}uzd?dqHPpvi`sj zUwzJm>>bnW{aU(5S0F*DoGRqqu4UPU1&@^?j}x;5i^)nGLjbV?8U#RtvxK-Pj`*!- z8u$5trWAC!(U+>pC{}s03va&@k#oH!5{34-XWu!nq4g&Ee?SSPY+k(@?I3zkok3bkT~}A%$xM9k3+jWM(~}=7jpAL_^^+d^t0uT9hHQ zP+m9>WSF0CgvJ`^aUbd1ydz{G!K^gaFwP|Rm;deSomOqYge{lz(j|W`9W*@BvLbh& zlZc51NnLZ2p?d@-A~#-7&FfeW_kOI%aJJ`h20^g&VPo)Em(hjWY#u?IL-oL*gWw^5 zTl5^L0rlMXzfg<}xvItesJOp)VE(DwaG_WHTFa-1{57oB*xCxO_RA41x;C-k^UrOf zYDmCQWE+&?;Ps!*uTmbqh4(D^68hnpyvG^*M|n-2Z1loy*xpdT-|HH*ry!ij20`p= zv>Ou;om%z2FvhHg3H-jal)io7me5{^L9uFViQdNyc7Fq~P)_!NpLQk4NggGcbTBrE z9BPV((i+W;Gq7Z!=(aYpr%P7)0{q`eRF3>{vB(@w@ql#tars%ehF)A470e^ zQYW;Kd8~tqWJ{cp-!wmDLn3fd{kU)c0g^Vc{p=d=*JId@S=FNmuFxh}dv`J}&cK-1 zpG3M}mF9;4UNchxVh~Y%>5mMy*lp=!;w*G9NLlh&uAR+A#c`}*{p(I z6ji?&+J%(|=#GoG|7|QdeH;5yp4~f|&OPGHKzlD?1=+EcPwF*i`|JN9ob*_XRKbz> zh+kz4G9VT){H5!?1<&T%)IGl*fOzq9>H*}fM)PPsaro*>z)L@Tsq6%aMdbC^^w;`o z;Wp!Sd2S$|ou%ILUQqMs$$M}gtF>;}#D$2yss}5Ph-2AzBmZD-bPIjjOoQ!-Y(GR{ zN49J>@_r-yw1<)KhzfsCc6nGL?<6zV!sF$NnKO}dnE^~Ole1;oT;Fc}U9a?Q&$t6N znYI0mugmm~rL6Tfr-;2=zaF7mZN}Bt{=ucQl`p({7$^z)_JPU*&|XVDH6ayn^MKgJ zF>e<~n|q`H?72>7Ns<$5+V{7yR1z!<69+2%p{_J{&*Kctcd!dS4y@OXyj@tpr+n{& zDA#;AYr}V&j_5&?R`n!d^4s}%yy`#$OP=0YdpL7;&3s{-y!&HETJ~gZ<8s9W&-`&* z*8ZA_C>gk7HO%hVpb5)#ls#P^NSK2N7yk)U;vF1`PoK%=Tp-ra@$(b6{#QAVP28>& znJ|@&^gP+x0Pm*3_UajW4c*{9t2EXjzKU$Fqzx^`lT6q6)hC8E*IQ0AKuLDae>>j) z4UrDKoea~Ck5)Jv4u^{p93quv%luF*f=wVrv4As`O~ps~!FB$9ZKKH4$Uba*np?bQ zW#K|jT6v!j%|Cv=^s6Q@9qHo-d`2uwo(~eKs?9p*WL?t|DNeI`!=zYiFah%a73IoE z(GkPlf0X;n#@Cu<_Pde%j(ksjXJf6nDdxN(4&@c&U*H)_8oDz~w9zS?_th^sTj^C^ z-vh`uu$nYH^0q>9S3iOHooHbh7b8wukci&z(ybtB(K`+ipAN&g-sbBW_!51WEkA(u zWIZ3+P`HI8ju|!IeK&`=X%40~+?TE5!GJT8kEqFNj&qTLc0Ia_P!ZXUA~u3g{UT&l zc1I=4L7;5Kq1z0$dZT(9@svuGNy=}G|UV8nl+*054#oPb!NQ0jU6b|XD0&DJO^S}+x&brT++&71mZ z#@Nn#o?b8W5B&8ZDL9==i6|9@=>?tjMmFc3_LfOJB7QQ&P}BuxEd1%w4G{-Zmo=!M z)>}v{RCc2r;}q_^n(-=RP)n0o&G|z3Vvb_^5_M>EZqlRMkT@bzgL|J8pC5)>I=x&M za18D3W@}1TLPQpy43!(mU4K8_2BZ3S!n}jD%G;2kXTt@gdLG%v3Fe}vULaS%BpzMS zue@&3JWeP17o_RWu!izJ-$RB-LC>D7jJ{hKFsyE+Uf-80l-Gi|-=4BRI#|&u!&7fu zfmF3gQKH-2tIAK^-9nX=jqg@K#MYkJd}kk9X^28o{^}1olR59i`z>xHT&9Y1mITAP zXta{yAFUj5C4y~}!1;vUg6#hZEr;5E+x|sL$}M}x?*qS)W)*u;9WQ3yar4-9dYLn1 zrZynakRx$4`Hy|sWBki@2$&{rSQSXc{ycoNUPSLBpsH1bSU&iR(01)L20(rZAH4f*G*xZ}s`4 zkHHvHNHO=*H6X^TH@ppyBP9In1hRJOD| zWg0mhVI@l>>e@J_b@4BfG$lQwb#@s2No1Q~L5q1MU6n{>3tLf2D2c{Vcg^+n{lN|o zzhBPu28(lpKCL1{H%Fou|6hAw{tspM{~tH^Z7CIlh{(Rrt?Y%7ePkO9gD6p!tl6`d z${uDYgfOy=bYE; zxm2NBYwelMNVl}(?cTM;^GU&WW=8`*&%I3DpG02+rW*L3*iXXoqz{n+d8>eaj z$C;c}p3a~yodljj)k6Cycm~+;tW=57)V2lRM9t6}UEr||7g~}l+|(ABx|0gY1?3)( zy#b+fAFl*7JK1v>w77n zC9NCOZQY3yD}6Xh_od!xEJ;Y{K9S&W_?_CBIuX^^3CuNIAsge$82w0EYHtL050VOY zu_cNK&yCoRWe&kVHWSsHV^NhjL$D?d*rqhi%TD7{+%^2z4Ij8#Bxx?~h)d|v?V-L& z0JdAJob#q-x!JrOV5HxcKoaz|gWEYPYOjRe&Uv-^#(|FUJ6@c2^0yb_T&I7DC5zDt znWrb0B|o))?=L(H-%!xZ3S`SEpnW)Qc{{ z+hXt!{aVx_xehARFZ0$vQgY1567H+Ne zzrY+?gVkbNX5NZ?yD%q}GSrrl#Zq&LzQ>$U&TgoguSD{ImnRISxd2of+&r2`xKUkyeS-OkIoIAqrn^Rv{4A4Rd~$p@#O5qZ7p(os=47Y2M<*vgD-KB!N^E-+ z4o)8G=x7~p`j{+IJaPvN=n)y54woJpM;u^zZHqxkY_ihG$CA8RsUv7+!Gd>wTdVx* zyKN&$u;{ovzIPjx+Yxyj=0H~nNqjyQC>-4l1T;2;F$SEnr-6)6*HXO6_3QH|0znZ$ zBLk^McNso{7IC_9+HcU>LsPtFKHK@)$VVsM(+ba5g+9>%EL1xc?434+Y7ML`VQ>%N zu^R++6g$YPfP_;lL}TJe!EAX;jgwg_yMS-X_gS=3k6P8G971Aa-*&7NRECdwXnygi zI#MCfb}!enbVi&=E%&a~ZwI8Whm_t&CLN}T5qeaO%$67|mQnX%pW0Rq2idSV!C|fO z#@!2YIucvL1?r%u#lMOqYr@N(39*~w8I_J>j%vRVrcT4UkFY0U0m8k*{&Y!J87IJ- zw2uZnK=gyEG)YH(X$TdM08sOV1SJMLS`krkb!G3^rG9NxdwF!T^_3GIBWB~8#;(oMyK*WZJN3<3|wsFp^mjzE?#KZ5VC{VW(P@?|EY;VJXj1h>y})Th zfu2x~yLoVlQ1i^o)C^s+sxe?l_~!q@DQrPqv>MfbGjBI>qsCx`VzAj{RzbT|Vsm>& zdRmUtQgmONYoZb)seMO<0mtM|ar~~rri|M?#Ce)~!K?lyCYkqaScImcT!%?O=jh^( zD!$p&Fa;=Ub&b(S!uqn(%Mc0U^CYRP*fYnv=fgN~J0WoL3K~fv$b}}q&zZ|wQ6qVb zye&w+>MYq$QdF-e$w@eI1;WPEr;`KL{M08vTInIBME0Dfc5zU}wJoE7*uAF;+>jw0ECPG{+uu=-XBZv^HA7 zdJ7*5A%`kD#( zI=c>U3`a|W18Y{8(|(kgQY|=ZE97^`jt_O-epO)=i8p9m5#|66DTNA++w5L1BpW+H zlIj!8asQ6*fKCbVy|y2GdSu=q!-xh=V&x80$QWdUZt49I7}a`N5=q zB8pPf5uvP&f09u@KxTxMx+}h->DmKMv|CrOp>3bRu6OkJ6swSm0yYf;9Q9>XxyM)j z@}$mE2J0Z+xG6PM_jEtcMZNTkAHD!Wn0whwr*l3&$zx(xv?XfyTsTQ_zdB7`Cv;97 zK1@Sp9udOfc&FEuC%QR*3DwbA6)d?zVycFB(m1o~c0igOFs&&s%%l5^v4V7-j7T(~ z?8q!yl3K0Gg|eKlw6g}+HlG2=k`n6FzRoKlYS&{~W9p~EBI;l40@;L|gGH;%X*lRZ zYMM5#BGwotGCT+s zzFB@F#X;+ZuQayqV)55d zX5pk5e-^JEou??Gx?>cKX>h3U05x9;$~h2rzFE!eCS}_im3N3nMF4TJhH;dV@KAYa zzW5D)Og~*dQSCmX$ndV0^BwZKRwzV-PX#c_st`)d;M9`i41|dFhF1c?{FjB^fH?6d zSs0Q%WZQD72GpddT+9dL=O_{up_Nn6lQ~?2s z+VX`KQ_@j?cc7-0S4*A}I7p1lWffUSbjInI8rb410Ced>l%exYe;_>D1r$G61l;HR zF${LG@}D2pcU)3M`}m(d^wF3o+sswWuZVpat?{(;X$Ub}XGK7MeZVM&IDlLc?yP{M z2q%!wT1q#8p94Xnmf!*+!K!a4f}V2ZhXj2Rls<1OKpUntsk6ka9uA7P5rENo{tW%h zag+Mr_Uc9H#ng`tE!REfJ6IlAM%16B_UXNp{8ng7ag}AUE)9I@=8Wp=a@}o3|8S?4Oax#G3Mc-neH(7Ox3aOgiK-1=)=cpR%tM6$=%c)Txt7 zg}R3bOcR28PfbPQfM(N}=2KJAq93UR#63TJ@hoO$kY(?4lY4We_{Bxp<$8;7eHA0Z zCxyDXe!jR4*P`lAp57J#Dr{&&&I$U}5uqlDD=c0xd1U&~ovCy)nxV_H@!mlPB9m_~^(McD6Z^d{1Py#$8TE-Lh>AtLfUb zVdb^7@FHjg1Q)Pn1Z=Ix8r7xgT_A0XkteS*tH!U3qshy|5?5O`q^<7R;51hh4=NKt zE3ZPW+UB$nt$g{oZCfvTd1qI~Em|khgp-;%H-L7>fmMVKH9V>7zC$1rCeyDBL@hrB ziLb*`Z-K^Q*|KR#wp{qC`fNo~Rs1m_6Pe6*Yf+F$@oq9c0zkkobvTI@Bt!ZM z8P(YYNl)v2M0QgT!+naaw7o@6e|v`p!mh;+)6hSVSf>tr%EFzPWmW8r4$o#l3HK)IQ)) z(!9|)fs^PE$$EU=Wp>D00ORW*w!%Xn<+hV7R%;tmFmYL^^hbiNp{AM3h~F?=K2{$o zN4z(OWDPpr*{kSWj0;x8@Cl0hd0J+hIX9ul~O9~pvU~&*ZQiu15;Al$l~d0 ziOqe9n=C2kv?bRO*pT8aG1L`(O9DD(6roM!dJuE9TmWdmY*RUX*m~D(kIOGT5OG3 zQ3eL|TV9C;XSAH^Y@Coddci3euc>gi!MJbIOT%=Y`sYsaG{qJop}j6=aQ{NW?Q6~p z3HnUBVF~a8yqRbyb=G zToDFz)oz{0d>MzR;(!OU-!koRoGhZIj2(~i{I^@iITT$d%x!d01doZdYC?bG5M2Ke6X}$ z0AO=|lg*qksHmrKE*U~^LMlk=cE@lQm_fq=F29L>vjV?tY!vxI{#LXSdunIIHe89_ zsaY$aV$%F;O^K!A`dG1-i=mU!1PQ&TyY*wxV>f7_E@^D$SN_~nh~g()e?TR}UoCMI z1)s(Y80znowu#z}vO5tBjSR^}f-QMUG*lzFkljfBy=s#YR*b*)Lau%h4}UZa*}@m4 z5Q!VlTk@x{a&KWCe?Fp6@%`Du2iFFg96Pxcq0DG-Yw0$j<(y??DbPl34qRM#fc@CQ z(q3ASo&IAm)%1EkHg(%rbg`Z=8Hs5$u{g6}k$Qv}U9=Zq&-t(DBt$9%H~)P65o93s zGj;qAh*7x=1u>KOWA&NWU%vz(x2-=CU7s7gjX8bOv-UAxKdVmbp%rR6!ei`v9rfPc zjN!&%bQnC*vuj75X?JJovhys$UQUxM8Td-9c|iG}Oahc|?5Bk^v7UZZ-v)|3QcprC z&u(4S(siL51Wn?+oC(C`q&M{TQ5V#KtXW554%c5TK{PNzFwA#Aj_@cwRKlxbJay6c z{+x_!<$# zsq2=E7Z`iaY6xT{An(>KvH-P;FD7$mfzWMFEtMnNO|+$Z%Qxzzd3m~5R43~G2ET>Y z(AYCBTKnR!issrbfpOS-$FCyB160|&xqAvti+y){e6v^hbfxx>vYq4m%5un7#`6CO zO5k!`;NbLW^}E{j6VvZD)C?K|--rFgn?i@ONAjV-8;4~3B*9Nr2&mT1n^F3oe9_WJ zydK&k31D`tl!mZbluIQHdE&l{U*FnK<+=YGvj^BfK*^r^0`Bhy|6{Iu(t~pNI5}rQ z@IM{>Usy%(gwSJP%t&vFs*d!w7H6IA?MaRz+R6wLe?^0E!ot zCICPi9n~Y3?Vg=>lwB&1Tbq36jV>h2>#T}E$`se=pIKUH23Crv;>1%%H@9^2&m~S9 z@~>v1X5HVP`Y&@Q?j9K`DK=c3H0SyXyb0&sppR(Mw%h!~ZdF`a6kG)mz$Eu+-|8zS_^CUsbt=!^~Kj^yx+Vot(Zhs;?QrrjbrL|Ef7vF--7(} zxGa!=imLJmaDZwkcXO`?4FktpE@EH`Ac3BiHP!H9wZD0Sx zCQAn-|2e5@O1nhY_G)r1X3{PI_Y6by)MvrmXIZ*)hii@pRZU}; z=#TZ<$G&!{Tuv{~32Q_`)B&wb_V84~NTa02Txq&`HALU}=G(pY>#W@4Nf{I-(#aGU ziAkU5H-p!0+uML2p@aki4~*2gq#dzF-&o5o#@i@!-1Llv=20Ft1iKY<((rbQ4Uf~F z8$sm*I@;LBQj=nc^ZgU6U(q7vQf2~e0s8LVj3?08B2dZBY8whujU*%ZJN68wa#u7u z0uLV|P1X2ysRg_9tWo#+G0MB}jzb=GQY*LADNuO`N!XtA;S zeB)OSUZ%ksRXc<&GP@}7K>t<1Z=kpExoB)R>Q9T>0{+YXz%q=gLwG!AYyup>$9Q-; z0{3qeKrw~gJm;@?#w`FYdP^m((E?;M40tK(W3D8*(B(XWVRy87AX>TOS?I3f8~91h zDr@>{AW!3T45Ni-wqKMD%h1#bYb#~D|* zQ&KM_9^rTnK8FdFLA6PER5Kfd@TO3U{MALC=O|o5vy!Kzo;L}ND{L+F%hE{2f_noJ zn>uVQ$wg?KedA&tE#*_K28(4Ug7&s&zl?W%_!UhmK$ie_k~V}iPoZW3vk})&)VnwG z$Vvv^HctvlaRg!%sTspQi*;#FzJ_%X{~lsbNu>}H!on!st+kMJqqZJoD_SEf8RjLf z*k1z?-F$PZN9t!pM`Y7(em#|vBcKj%Dpl%z@D;DE0CkH9|D?D<&ZK_b(L408nDdbd zkW2N6yzmg%wiR^CIQ>fVyoBdg;BJfjTEP4oS~$SQ^E*$Sk|6$77- zr+ik;jG)S~?>7T1(z!K*>U<2?@XIw3iv^VU3hsl88vN_KxURBFaZy{cUbLj^@u^~t{bB~cGjN;|9e*fdMr+sy9_&E1EAs=j&kLa*2pj#A z?nTd_H4!F@1H%j&IVqZ%*goANn+qDN>Bpp1GmU6`>0y{^yzuyUt`EZcDM5(LDk>yq zd0M%&>izwZDA3`iX~%qi4FYHV%rt5`WF;}U*%Khq1SfGWEn&n-<~F(*V9^@Z)M-7uIMad_TjsgSB1EoiCKkKwi9VY+krx+Y`b}pcS1VWh z4JE(!y=BmE6;w4I*4HmfDpy!d-9Kn^Tu7*6gI?jTg4f|hMI1le8ljFpJFys=`-NK9 zpyjhI2$HBq@BUr{F+WD9*dOrvFko#KAd@gB{Rc2|45ZVc%En#{l_u( z8LN+Ll&w_6Bl^27`UZOiZUMYZXdIk6oVbXf>+> zYJfsy2v%$4_a|vD1un4F3HdBhp$W~}bh3eKio&Hz*TPbA8hvO^$#x4Nt%T#r${bGB zhFixLI^$5&*Oopy2{pp^|_Syo01w($WiLpvk6wW zbp3~)5$7cY6I}V^YAcG#%*z5EOg~ z%LlKn_tw+soXyl16z1GJKSL9$aZ*dA~wzNP%ibsf2n^DQN~O7(nK^Wh$_B(ZZw zMQl&EvJsP5vYWkVj5QGT%E}qNC28b|eZDwz zwWF<1&}UQ=o#!rqzz8BKi{sCeaA@v{9yN(wdk@q0&Qe!`KvBxr*5HjR8$;x@fo3ivyR+V4?E@ZTIPMFsHRm^-IgIi%ih>X(#Tg5bxl*$^i_<;x^e z>r8TwiOmc;jnV_m=JHFM|*8 z-ICqn^>3ecjVDE0`Gwb6U#4kIy;1%^;cQO_G(*V!&j8UFJnPYgPy^xTs|LIDUcOx! zOD!YyW7|$sl4JMLUoKvLFAo}1MYXF_=Kq+(boUgNbmze4isPtbo`I-$I$8!CP(r(T z9vOFplWt9&!hWgv*8cPX%kNRZm&~s?j_=|IQr71#+OI2!T(B4C$_RpL2z8)=%G~3sh93V6$7l79W=FoLADL%v}$cR=n(gz8&(m zihk9rVn35&ZUL;{TKDy3J}F-aPyOfo>jB6g<_{0!zu3LI@A6OjW&wU*RiB6e!bQmF zd{%(q+0sG#e4i0O@N4eV|7-5H4*&psEdZ7QV_!EtH)n#k{)+>h_y^Cw zIJgh!`vOA0-CtBb39rp}&A0!Wfb^p8mhcpC?j7ha@ZJ1f|Iv42{;prq_xL*IXZ?+N z)!xte;D5FI=s)XU0GRu3KX?Ap|MI`~-}XE4Vg5@068vU-1YG*t{2J-Y1DO3C9rg?H zJOA#D6Zxg16M2p;>ia@%uvZ4zj2>$)m z*ge+ca(&bk`YFkblhWx&WK;M0ARtqP zE^Y)Notd~3yhK%2n=#SvAL41wsSd=G9q{UkaBDsF)LeQ)vUD2uJib)~G}YlJCI(n73oGin)aUI< zk&Zub%c~L_yqFDl2o}d<&xV;0hVg`Kq6>@##w)vTI%#z<+^@@{y~|t3wVRk-vUfT- zdmZfPX2Mij$zXan1-I*p{Yp6(s~nd!ukw&B+o4)EgCIt$p9$inmMl)L>q#*=?-CZ^ zBAJ1vi}2t&NRS1W*Kj+FpmEos7wx_FcAf@$|1{9w2qgp8Us9Gkfi5>hsD812v*y|i zObo;6#&I9nQBTEAoTADIrDML+_!D-fBCVkNl{5M(=)LFkKG6#_z4`te>HLYg@tbAo zZij4%(CZPHvkA3i=c$$C>@rr?w>d0NcimF#=UDHoygnd~0?F{EDj$t*%`K~J`A3La zE2+GLHwE|cP)o~75B#IBh&Ir^P3|oxhF3d#_83Hin@nEer^e}Il`)MCy;D5W__?nA zq|mvB9nqGM4X?b4Rrz4v12+I*h=5CMTF63j@mQXg<8hx=c_%WHIPHxfNDDB{zx7xv zKOaf&zh$l?6{anpaQv?&|207?wjLJK{!R7q&1y7ls$kAW7kXNfy?@O?h2NjtYU2{@ zHUe%h#QWmb>kP7nyQz8qRQ!LNGhHl`-lV`#dUxE%B|+GwT|jA*E`wh1XT?=`+*({y zyR4^diZ7m(oB!A6Ph=0W67dQ66aoe@lZ<^MlEbh`L`)JEIfq7M2L?Xaw_t;apf-2o zu!T0{71d%?JFwx>ql0OJ2yt6p$Ty`;M7-Yrx8e9odn(^>1qk_=l!G(L^4rn+SN?C17H=OwtD%d#k0npQgF z36%DwJ__A)2iyng??Fi)1%!LQi9V)TpNq3v%@w}ZTMKg&3pDGDdj*VKrbv|g> z1Qh@uZdqlk`l^g?)I_~^U98j zI-m5=Pf}GDj?9>|>ds5!DCWng*~yS7Mg8aFIxM(^NxxVl5vLc0$&G2hKa~U6-g!b% z>glt0_@Ux;m6R>mUDsQI24bjXnWM@_u~{qMy=iC`frtYyf5GOKp$Tu0b7-T8*Ygw? zVH#CKXaSVt0a*lsGpUqmog>AWL&n`5f*+K?eM3f+>!Rr1Pc_0#!IO+X%SqFY-CO~B zi$^lvWV2O`DHFvS!#fn_VV&<)Z7N{R&*hzr{D8_mm5JE@VOSiqt(LNG6-eNISiNm(qsu z54oe?GjYIVY1XQ}=f$Z|vCJSnb?K9W8U?|gfi^7#9GnIk>1JJPcUE2#Ca@#AqFeH8 zo)xLpS~kI?^wQIdL!>Dpp4nngL zmQ+%NTh}f7#ywtHcVL&chMjBRdpyTJjlLTS&Hiimtf%%H9=WSZT_NU#Isg%v) zhsn{59~*&P5fW+A;y9~$cfv9lLMO{FQm9%jG9tGiz|p9sWpc46a}C0YXCtVSGN=@f z3*OYC^*4E?v$KesSh`^P%Q&t70v8Dy^pyBku?_G5t!Ibiu%Qyd#+jFArB(blL)4Hz zFn1FN$!nxRWLfTXd_z=|gma5eoFnio5y+_~GLAPyd^X z>c&6mGTgFI*g$03ComOvl|L@YxiTgM@_;5tWb|3&-+XDq;P6s4e)InAlOhB8{yZOD ztBO-=C#RXwE83wl55Q@Jsr6od!2MmTHh`)|}4uA_|BGD9fVCIlZKry4wZdb3xT zVfGR~G(w+i3Tw&GYa`!GocaB@TZ}!HKhn5|Z5t?{Ql$Z zgnQ+#XQZ9kh7|*J(}LN!4*r)s{$DffoB;i$rKJ-D(m~xB0V=JAD>hol%?SA+jXtsz zZ;>HV)uOz!^pv6KGStPTdHz`WgOF4F36<1iQiVWE&Zrue6)ISbnNvbWwaLOxQ4B^6|+eD|Wk-lt1Na5dTRoy5<(bI1C z{>NdsID)Ee2a^aQB22sT(Fj>}`_KSQp?~(TJ;XJ9l9e3uTaG;}`=t+bDsWm&8}-q! zwZ-gkdfbA@L3!Td6RyU&H#3=8mY|{nq<7m6xcN0<<)jLGRKp@-NY2v-~KoXE=HSH3#$%Sb4q z_UxNiB%@H-gAcuz!17Sg#Q?22uB|4z8#R2Np^K-@dzo=lF??j7lj{paf;lk+w-(X= zOY?f3!+&+mNo7nZzEdOS$kAC5<#TK>59YjFWDqswAS`S>Mf;_%?;~q<1l)($3*)c( zoRWNJj|!4!)To~HMk0DQ|j6_in8n^JZ-zIcxiuxYp+)$d54{rzJl|@gg;`-laEt8r8{l`bn zk)pGl6kGnNgykVo2=~EV8b~{^k-(ObXq-U@LfS^+@OvEz{|x5WvX}8L2=*UC{Y4Wv z(~M5-54dXw>^yD^pIN(O*EPsrXcE2zC8a$X&g2dDbzyP^r%(P9JM6B|JHlUtmQ~h! z1x}f&;j-bS0@%JITa8nA`&YZGAJty;oK<9X^l%%vbw4CUmMLK{{;!w+H_R;FN6N3 zuYIboL}7W=SLf9Le>4=xlnatKbWgnR+u{rXbq0ADa=WJh-V|W*MeHYZcKoUsVizBxb4ZRv!)(19SBRx(M`?w-d_d63R+i# zxlp{&!nRePO^;oc&hl3SvxP<~v;K%e&mD)*rTo*_iDR^`JN1cuY2kqLRcM?cfh~V@ zWN>ZDB?TUZ*;Dq9#xX;u(2kOms3?vA1zJ|Of!z`)Cz+_CKb%r0iXA6GhRWe6&S%v3 zhhq|FaBc6IG7!t;%Kq*};SAZ&B#?4i3vc4fRa9HM^Fpq}W%?d`1CTRI8isz_s?bwp z8v(V71{GFzNvpmVb4GPKc4{1Ii6F;-%^NM8Ie?qV2B#&~ERP=P_gZD5Ef%&F8*W%2 zfPCtw7ZKXoszk;kMwP^<-8km|jM`+)E4!P$r1i8B&hE@x4#=%r0Dwm5R*?}<#ArjGF59@Q>^^e>)VfcM51-&#`AO3(Yu(xu_P7}}T zP(x;SCwS(-9q|9Do94e>Soj+kTW*Rc*l>nw0rd`h5Y527C=Poaq9c{7p$> zYvq+5^EQLl=J>F{hUqI3&l3gQTD@kB>j&&I7*!?zH%o8n_iWeQPdve5Y}X3$bC5}^ zj2nlBXF9cZ?I9imHqQ_&ZOqMH0gat?o^@gRd&fO2!O6HakE7H7)3{@5&cIfSD1#Q4 zdj6;TL}CkWQi_+C%6@vEIu_XrhQROq0W*GuA$dJw(yqRs!Kr|{v}hsLQR@_^3B2dY zw$y)(LNlMk0%i5B$S*-i@`Atw4nn}u!8z~`mOBPM8?>rK@FGTx*SoDsYI5w4G4}eV zdP|Xj3@#FduDM9lqe(DeHrna~nl(*tcCPn8D1XUqJ(t z0cB~)dDIrL1SZazO~<~eXezSOb7(E#@hsfa>(2Z(3jc*xauTHtGM0&VJR1*~0TuZp z*raD#FLU_?$$;RJHhU7grgPesO<`+#WNEc0Qk=GLd&qan?f&OKYNs83w&wInsUto~tb4RTkxB zFmrf>t@SwMeX(aF=}ft&yaseKr(k$lZA`Pwa7p%jBIds_BFt6k`+r0sY8dIP0iOu4 z{Wb5L8xif6pAe5od3dI_z<4B)8JgrZB>OW$6BjNkZB>3lWz-hxD|*#iAz<@9sD742 z1Q)G|H$2h&i7tPmt`e`#x>%&@p1cww+?h9%=_2CCROpGeL?aSrvShas2 zm;*)8M9_*5f8)oi**Cf``SahxJ6wng{C~Cme@gsN(-5%~`9qt=Ea}5!;LvtP;c?eU z_R)n(DUQ)cEt@rN4aK$eys+?BXq=!WpdDE1M&>0k5gd-~%h-Dwp(}Q#qTGJo4*eGa zQaGu;0GoL_1M$i{IbHjdu1hm%ay>Y$-oEpJ3zWZE{I4{}1>q$hYTL29;LOqTsw4Q{ z3Ir+)l=uIZ1zzpDi?cFxlr^Y$##lTqbod=V-Zy)4xdgy8xPcV5lQ)};%Nj6riT|4U zHdtbA8Adfl>qd+p3Vdm}i#Sx6K+jwCb)zT*?(#1W$YTQw07H)ed{N==(o#iQO{A7k zCRD+G6MD(G{JV4`SBNO<;9F7Bx!3r+G&gqauQdD%bd?=wCo*d`)yXO!jW#H&^0ZB{ zb=wDW>;Erp0|IEV@_b5voBr|-i{72)KozS4K&gLKQ4^pDteaK5+3WueL$`@S z#@GCC1Vc&rsN3g=KQ&ial`h24`|s_+b-o1pejS#D@JQN!Sgh@&Ycs4aM( zP@8O}sNewP+D?Cr?-3TMFE6(JK|lE0;3c8jmsqj`u2PpOCXGog~|g zxGw)i;Q9~zkeL7Di>#-kDnxv?T0eHM^^1{NVEWYx`d0MJbU-+Ef@98qII1xr)Zt21 zUh~1*j9&@=f)H?ck`!LZR(lYEaX^e9D?&K$Z32Ny$>I|!D|I<#=)J&wO!Om*b>i0G zRmhL_uGl~2^Vc287>40W-R!aDk#A&sy0#f7!;UCsev>V5? zCwTtudAc*s`va4?Ra3mgSxRn~F5h6I*HHk8dYtsz8H0no<&d`ghUn-Tq8fD4^cwsj z8l(hslt{}ZO(U}co(?ABm$E`M8{sNjJT~E|`AeZRSC&AlFt&7TBIK`qq}{x=1=^PM zAI`B0Lm&P)d5Vm(!V}|Epixz$6M=u3r;{6H@!YQ?3^DOxtFM{q=c72?yz9rus#Ch-=LX0O_|WUqOR^37$5~-NNalfbEc0l z)dVl+Nsi~r9JS`am%yc?+;!>!FV+WI}^A17UCUL zYVAYxxjqM5effuW&~aByBrLx9wf?5y+mM>s8iAj5-cI*h8C9jS9mg%uV@sFuNZ6>7 zmj}f!QgCjE2Yuk%D4_Iw(h;R})FXe(n#T1YpG6<`B3#n|2p=L*9Z`LTqr=Wq*C1X0 zg+x@Iahg~mppy`RGYyALqD@jq^f@b_{*K^=1>qOHylrUOKS&DeIl$BTs!k9<{Sxk1 zA*)Inc(ICM2p<@Wy~)EHtN2|o(gR8=F2R$Nak&H5sQV<3Iae$CXgw4t4gKDpExE){ zMb!a_y{q1v5w{qr9mkKFoGV}07OW3L+9jAwCGigZBW9l}-L)Lx$zS$w^E>l8zP)(- z_8d7=kOF3carIS0jC?9(j4#79L>$cCci+9hM#T>B!oG~O`5yMNUo>cPzcC=IGJ&~?g9HU^^QrHTgqa#P0GWt1VWVr2e9|uQ3uxLp2YIYCrZz`1GB zS&;6QO4;`G?N0#jaZ@wwAKL@IlzT@;;T;eWl8fslN_9xZX}K6xYRpjIm_(y!m;Esc ziwzlSlnM~9rJ#m_W@eOE>RUYNPxjxUY~jYvba}a1LxKfe?R6MhDb&MfV_w&USD^k` z+CVcHxITh8T^tZh2|S-40ZHQ=FTyjPu(B-2v7D%0#SEO&`fJ0Qns!hKVE)E^V=)Gr zOm-JbOxE$X=!okdvMW>-1jKkJDkTREGf6c(aA0`p;?>#WT(E^FBcJbtyc?DFtn`+q zh6)|XXg|C&*>+dB(p0%O%;sI#yq+DQ!N%e@K!vda>c$%xW~mQEl!{2|O(0$llYayy zgDVVxj*?nLA_L5?8N$CBUhoGBJw02Z5WmG@4ioindw+wd>`%_k5Iv%`SPTOFdQi~` zv#ZrUSb5xjMV!dsTjZ=D)2QQC~|9$V>B;KW>wn@dkc1z)w3y}1?r z{1P#Ovrw>f^JB=kx7F}tFk5pw47-Fid!Ql4kV-k=9TT@R7Y4yINLehAmS?jO1}CD+ z+vqZ;qpve;(Lay%b#1uXgAQE_{Rq#>8y}aG%~MCS@1z_LZv2iJ32snmP@8=0a>2E- z`T@*B|F{=^hLD|*_oF?=c~R8IT|>0Qh)QvlsGNI7KjMft5f$AXS?9LSq}z!)xV45; zUCaj?moEpktgeD&)H_iFRbNDVf&oKBZY~&Q*!2^8=M?6If92U5R?2n0^4w3>kqat< zx%P%UfU3CLaiv|-`93OH5jvK0;^H;9PyQ^NdN&sWXr&ZQ`xQ^ z!G~3;q+Z-Ok->xKMi@7>jqFmD4y(FH^+eRjV(5`*pm3Ny+u_dSdVp9eU#EK~XsDZr zu|(mq$CcmF^k;iGFz?h*3oReVVsCpGJn(S;oIHc=WXWnXBcU6KGhl%;GObF|j*#F>!>7MHm}uQAnBU zBhaJO@=s*;)b*5+Ar}OX0-)e3WIL|58VZ*OU7_T4?ds@G zRD@^(O$)O9-0<5oJPj(0ixsPnGq|6r^wT;r$eX73j_p`x5xRGS!%L+_Z-I=a->#vF zF3Bk4u8}=A9a`}iz;|1UlQS7n7wL2;wX#&_lImeq&JM%B^}RID=%M;@$gbq>yHA8< zqFpFFqMFuGtwzmH(&^mIR7016*^|W~6i7fHz^Cd^B5%ch%2?AE3*D1e1jmxU8V=kX61{Q79 z{v6Q_{*pO-D`*$06U?}_6C^gZzi9NW(Y zAasTnX%xSBC@k~TZjj-hlb;lWlgN4bQwQD$Sk3ztY~rex?G2!>{JME~hwB~*Dr2r= z(oFKvBHkiDQygwf*bY;=@{}I~4V(xlUqY`)cZkej)wh1$>+YdO&7+%^N`>gb&A{in zZ=18D*3h3E_}RH;v@)!!ItIN-*b=;o`bS|Vkm6GPh8x56?i?k)eWI$%vIip(XyU2l zMOWG0j@|8PwMRZ1Qy^o10LPBe8Z;aOQTfTMEnCMFQ^+rYf)fXE0GAIHt1$}-a>NNz z)qQetmlBpPE3-P|99ytJxvR?i$M z2BvqZ+};HLR#j)AVn?duh;j|YH!N`qv(wYgHnMi#ePD)=fv(CWuQF5|Lz3_RnrGOf z@eU3gy*T*GM#9yp;rd3tGxJYWGkXQFre+~lF>7(O%{0VvjpoK$;u;1I+}Vn09UP-I>cN znaWvucKF0Ht9s`!35bI|umWw1;IP2K8S)d5MOm{Y0qY5@a!=Cef*Pv1ri4XfJmIJs z6cW?eQF-{D?wAVDaG8G~H}TA|^Zi+U1lq)*1Z6#^^fF3(9XYzc+}i}|dNZZLZP>WM z6fvBcywN;`VPqNx3}nV6@tZr=BSv0CvJ|u$>de zg+O}dN2^b{QH_V@df4?L|J7Bwuc`qOC*aUZ@u5i2n7rS!<>@A%bp9up@qFP~(*^t5 zFxWd_Dm-C|f+`ScM>w9UT5XgM&FW=l9Q7G8%J9(KZabI~7%@^eGG`*%=}-d8*{3AqN(;>6}z8@XQCg*ToY1rm=eG&&ZK95 zY`@E$pJp#e47RLyz=Hpd-?tEp%!f0BCi%r#C}urtDefqWfF8S=Pc!yS6%oP>Pgn-Ydzl}tsnlieL$BlXg8 ziLo#}XaA?5wz-{xt3yQY*qAf(hXJ~f!WQ?&0fD&4_`3x!;*8@M@;1uFEq*)|Eoi-m zXda8(*?W~3+*^@#97qHcB?ksum;Vf^PCP}Q^u+kQm22jOVJnQG_mZO6u=HW}PU~2e zO8jYS0#4SFN+A|+r$TPGvTwS0o%*2>5;~krP4@wQv}LpCoI7sX4IEfgQrdn+u$Q@QTvMit?#H$M%8%g>z%U zf4t4Z_&p&n;67v&*J>b zeWQ8@f2aguEkK>IKkv+)2KCE?pPnc0em_>M0?uewot6)q(|(}t8Sb8I6bpYN9*ZkD z3q7=61G42b@2q^}a;3(15~IVoj4Cg*Bot1H##y5m=G3RBy}FV4v3X+oHD^b47wF&c z5TtE8NKd@nJa6vvIhU=w&PopFO0zEWTY;mDmlTp(2)U3K1-I*yUssZW0ISECh{o|v zn&q=Mu|#H%G-b+B-IVRxy(3#lY2z?rj!S$F|NNxl2Deq2qz!EGx=WP*AM&dU8nJ z(p$5{<}m#nS&-a??UHzBvDkE+MJrU+5Co&7;r{VTrqZ9z$zkV70>Gtm@chmqBj2zS zl|8kHN8PL`R^;TH%zY#7eZsOiN{#Q{IL(0WV31R(k*qJGR)>JUIWx$CH0S;C)PFj* zO{YF>jnI&sUxeapub?u#a7){Y*njv)SO|Nc7so+|+ z&U5MKq2H4P)y+SulVyq8^j` z*N?mxJzE%Y24M?soXBi**&zOI{x!<>kbnGxV;@8D0-#+k7n6$(k1H9vH!RG6*7D$t zjd$_bOP4Q zz+Ka9{6M558WChl?)GqIgkI4tG-?0lz1(OC$)uN#1@Fl@;Fr7S_XLWy zjoC6n?ky5MQd$ZtEqYPlMwVt!)>mb;#mWWIkJ60T zlD--Um9@0+v7%Q33ZMFE;+Ik-1>fqMXnRBaA$S%;^lz`(5q@){yym#JmZ)tB`XB}_ zjUXMR16s8cb;bmR{s1EX$YsdiV0}CjD+?-K&NGZ&!!0CGh5R6iSOO&1eu}xaKZWln zCjM@}BjFE)$$2sYH4Nk3(z`Uqow*830_F9ei##-~$4Fbgaz4ymu#^cZzPyJlW2*{F zdp#?SZc`U~)xxhY0imWydvYtVB{4_Ljd{J-e-WuPG#Gi!()8 zdK0Du2h;h!ruBfZOEj}Jp%hkKnzOHQ*!PYL@&d-5{%GfqVF~<8Se%;0HMLMHM z*uv~b8@M$Y)LiCDB^=p0M`jkLxx#nc?XXnT(AdHg*+w(m*xOF)Au)N`uojsWqCB=dZj+e}8=J@*V){ zltuYO^0qp2;Dk@j-E5xSL*y)UYgOBRn3!hF-Tv|<1~J%+I>rpcN&2x4g=uCS+cmJ6 ztjm}6yY*fRT$(P-UecRMxQdt&Y9AJE{|{KL;FmPmHiU>0AL1YedZp<`x*6H4*{CB& zuuiVr)$zP)`k2^v@AC>cb}1L@JM}@R1WmiW=QV_d41n={;~;^Uj(Fd0uZ@{L^d4W5 zo6SQn_s*yl;m{uRKsQ-fv5~%kR(7_v+qEpv0}SW1OgtGh6)5+tGt^-Lo%J+o5btJ` zeJ>Uu^OHs8q0=lbckA8D;H$a4>-Oi6;b(w4uJ{lVc%IESR|R zjHn+pAOV*fseg>N^{j-83*@JLugtugyK*-2SK8QOdr=f-4i_NXt86cOY2rf7%W$>< z$S6HI$>gVKIK%0XXzk||-Kli0!_RXS`YrQJ zaQliE#ioKPLRh(7+{!M4Q7jLbv@|qkx2MN7(vtn7rs%P+Mhgpl^PA!8_w^N`ed{mS5-1U!t?7^yA(|gNV zm4OSEPlJiMaSl5Osrj!tDfSXYUCG(AR6Snph!vDrguEt*J5|%-U|P<7c;UZ)qc@vF z-bg7-H0|u^aS~PxW1QkYX`QllF-=ka5fs;XV0Ia+waE`uL`(`+Sp|#}c0LGkvqWU4 zT9#lXXV+dSTb+2uJK84y5W&2ufC;)&CWch^D-*2Mc+B+U!coV`GY3qVhS&;Mace8K z3t%_64erNA3vw-2beaj>#0bJ)d)_EeX^UVnJ@bk zfg&Bt0In3PWR-sYvf|PR_bYVC@lze1KwWmKk6OQ3yuA zD|^sEVl?`|3w0}K^yJeh9o^qb62{mD#di=X7T<^xt-ZuZsbhHMWYWm6r~(m*Fm zwM6BqQ*`!y)9%`P)M){-owgIB6oJ&4Ur78+aV9wIv>HNU5r$Qum465G8N|#)La~;9 zs=Re>q5uJ25$c2e?(04PL%=^GBq#yK&X+QBWM9aTiI`Y%y8lrs^;l)!|C(21KCLHE zEvFtWqD(X)qTIM2vuiWZR0r{(5})Q*_7eH?I06<`QMOrg4hEDk4=E=0@Ze=s_%$Vb z%WN3eP!)X$^D4aAiXhjT9QF!TVvH621*f!-LQdm;z9khx4{f=4xXY$xRl>9P;dhH> zzNOq_lubOV8`z`ypRruG8kPg-g4@S=9s2?#$fdXeCe6+uQVruNqAm@&6x&auq>xGz zl6`o%?)n6q;}J$Y{WDZ?^!F&HK9{lickpKjXaj%L2#vg7e~!XIk+2^m7^_vGHe3#- zywVw88=AViv;3%;+}iCL901ks+-RFMc!C!!zVM)U-yylwfhEP)Cyq3~0-Ex`F^O?= zB>IHNrw&%PhQn#W*+O@r z`sB&JZG0OP+r&J5@^to(Nm3Kl>=_rQB&a(uFOFEv;if^7c8p8fDtSKLS*!!wiIT@j z8$U~JJu3bt2yn8p-H<>V&;!C6CfqIda8IA_9%%dZ(WWu^;l|)XlI&o#{0hP%QYM;; zJJ>WCuqPPwRWzuhdZ}$i1ICMXb5gbo5qY{OI~>aXf%N8MB}%(JMPAb?wlt3ibSO}6 zTb8r=eePNoF>fEq2c+M^;o=_LskWUC4ESdFonpf`q5E zbV;TKvv(oKy=#3;ygy5&6a62T?jXEM+Sl^+S_rv-*{afIe?z~fQ|v~I7->d_;Oj4j_}}> zqT+sVt|4F}w4~~Pza0W1!8>ICRXq599I>CuBK95Fuv{9%2V$S`SOi%J(Q1w!CCkT= zH=?CV(N=jS={ZA6$I^AGe9kbrVniMKPGg*HI&TYyA*rQva`7Q*Bs@@NqdZ;$8hK#; zn;#Nl$3e=jEo_g#YOU`3?*TMG(?ZMX(9_b;zix2RTEz{;C##b+X zOx}Th)X@7$!hY4{R^BPcVKBVND!(xSrjnpxoY5#6GaQ1WjtDO3;Su)$19Y69jT|9Z zOg1dok#tr%y@x>26u+>;pQcEEb6J9C`$;;VIHbnwj0Zr(<8lD>GRj};`MtfiT-is2 zVDFJGolw$v8I@*l7;nmyy#3v4j3D63%mrlrK?$lAZ_?0y2Fl(b$n!~XkKQpfUOYAh zuBGq)G8~Wyc5{|w)cuvC+a(Iyf5DS+vx5Z%>zML_=tYr#U00$0)bWbp#gZyE{fG2@ zHvSF&XzZ--1e#9oGGB(;i8f$z1fb3z@98txon|sWB<7wv09_~Y&r`v~-)^c>zq2|@ zHhB>c2Q3&s>a3uQoMEAG_bY5~kK`=hyrlBMj0biBOm>mKt~z_XXZ?!bwhd8zKz*`6s>%XBpVWAx<4_vaUE@rU^0K3En1btw)H<~Q}oJ;E6%6_Pp8aa=e$Y*{PZ=59BO5k+h`_)Vqx zjt~;hC1Z9|PQd*b{+w!dw;uF_KkTkj3~o;5S~fX)!!Fks1_zeG8Goc0h@%N}ZF$PE zzs1zRqq}C5Wu|3czfV4RfTJ_itfeGLB{E6Z4UB6pbeYiL88j43hT;6$%xH+f1A-xR zSX^!K*(yK{^!k-Y%tZ*7`VbGK7-1{Zs&9YEvZu##sQuay5rkHSufe@Bf*z@pF81lZF5g@?^uky!hIjqzrn|1P%Nyl)-R za7>t~Ods{qlw0y@XTrBF`dVoCl>%^W@$OV_oa#K9shkLG9jicM{L29Ys3)Hp&v$k#C3(90*bJM_&_BGl zhkjM+YAHt$8Mv!6C#?Hi{RvSaIRXyjA7}aJh&*up_s@UMvu1-ZAcKfxTt8$EVTHs) z2{W{;C8KA_Yfzhnf@BC+tbv0w0?;&r=6)))#^TQ^{TU{Z#zD1}c_{%}1MySqY|~Q7 zVq9Jn0<{bbFGh5y1C}(5mIU_%a6!&nqjj0FuLUVp9GE5x5&mf!TyToS8Mb z`wYsa7)gu5x8Dyi8>uLq>2#=9i%|S$3SBIx{zD#Iaayv-X8*?|KtMq{QLAvTzBHuk zHJMT7A!Bu1^p%934>d*v>9=N$gk?$C>BPnyKR6BXEwdngRT_Ux5Z)+UsNg^PSZ6H; zoI`_*6`mi#9;~}sUx1(HgCb#=h!nXCR@!_q4FS5S4iyiX90=Rv(%It6$EHgxotUn9 z%sK{$#$D)kD)N8|%xDhWU(cL5h|EQUw2IV<&EEvr+Q_4TI8He|rx&6%cQz%v5-L>h zdA8v{tbvJc{*=nC_V^#Oe6yzw0%_`r+_;A@c4ku{fx|Lul=?lW|8_}-#6=7JTRlXc zvU8lTu#(7RzKYt@tow_{AI#ntX`tMI<^KAXIIc6nNGq#Gd%2c9Ib9lrl|!*7Y4$%k z5pvw8UqG^5b`~%UnSFlN8By3W)#%@953&WwcAB^zu`sp2`OPphNUz}QM%2QuQmoV{ zhT)2xdeav~&5qNRoxlfC^5)g!<>&(Id9d_8GR^!J0U&Pr0{0EX5e=3a89KKZj)?>F zvbnY3ePyDo-==(Yj@^!Y&NINtjOE@Xf#C&{dK-!4|+vJa}bm>RYY-85&D1LFAyjXf~|D2 zwN%W^E13S3mUEsL>}M7{8izxDPVrQwU@E5zaps*Y!hMd8saIISn1A4WI)-y$r=#9! zNwYyI11nv1z1CG1U=~^gUaZ1?8|EEyBr@=xS#+jtenN^eU{GBJNeraMi&^~XQP;|0 z!BlZrg|ggtyj5MLXzsaNs3%7qmkRHzTwL4l)Ln^+$gkW5{A$tZbL0|OQ@yt1d)l6& zR);Hl;Bk%BSc|M2doowm9tQwj^Edb%Jnr7=F7;ooXs07$3b+P_6r=2@X9niz=j#mu_%U#+?aqkne#b0k zCbFN@&;RFs5Y2;ZHgkcHQ6fP}<|1@`*bh&8@4^qRr1id*#8a0YWJJr;}QIVs%FwYb} zCk&_(dmWVTxns|yWmJ5;9Vv&QjkL%|c(8nT0cW`C_?9T23?ahHOngCX&G}hmsA8u< z!*IpTGv+@(YIS{{xH)-lUx4GAw#6QvEHiwgp zM3Cu2K_n}3sX}2V<%UK@b3xd@MbVcf7~mVL30)GjL=)EI zsIjU-nwZzL6VV7E-m+5e=}EetUy`LJ4YXCf8R8~dNOcVMxq$`@%ZX^ZBiK~&^Rf^4=(xNZ>ws0dNPrtw(z2G3$PxcK#} z9jnkLNT8p%!mqqRuWxS*NJ-~z*>|__r}VEO+U&HBJ_xcP$bQso!v^L||44zZOl(Mc z>(Wf8nYC>?5Id+Za#v_N#4!_tdwqKq-JYy$x`?XJd6+t=569l5SeOG0C65*boc$S)zZn+^jeWA^y;KF7C~X`5KeUysK5mCEJW zzh5l^8Ckwpz^c6)z@x=$`2K$0`8S?i!Xo|cTgxL`uv{EqVhGP<`@E?u_5OjPy`f89jeKPYg(GeL5PAzyN2=rlhU*P*gOyDuO z>?1Pe-xi0a{2wFO2IfT5uTr>>m=K<~0~QI&F(w)Qh#XnQQ&(DZ|JVD^CUnwGkn6!@ zMZ&2}nm4B9#xGg(&>6+I@F_yJoGw4^YXp6KYLP?W=EVj@FTRz_pC?YI8v`^yVx$v~ za8nVa@ZD+PfFxbu4*r4QRF56l6KP;Q*q%@iVql$A0z*(HlFHQ8VAi{M+L7r;qNrMyzW55vOgm-;+f?=nl<7yCIn8!JFD8;bp=wr)CT+-|tu)3P$oe#A?DU3Tn#kl)u`o(_O>;$gkh?=)IsI#um z{fW1+8APMc>s|>j7}^u_VhcAfX>ocNCyZwm^(bVq6X)Yw@$4Y#+6#0Ksr)F`l1^?& zYjjRWkne%$qPanx17WkHfsLLAIHYxeD*~X%3R@OyV@WFG(8e5nJJ9`K=9$>J%9>grt(dG;ja{$`{eOVoM2f?6S{iLshj47_utdY&J|B zC(V5Xysknn+8Cso|NINQrDJ9CGmrN;i^KW7LCPkQ8n&_dlCKUeSU0GjomxueT7d{r z7S0#>8a8u3mOdkC>}SP=<(Djn@;0*Nkp4#2T(Td?+RK(h`5Res$bTbiRuns$F{$0N z1OGpBX8b^|xHQG~Fdq3}P-u9Vpz*<9u8oOy$j||#KGoysm+h$T(b5Pl3N>E?aQ5E; zxc8I}U6hD;K~Mk%hS%r-1^SwG2`m#3{>FC?GFc$jBKT-Q+Wej1G)ln@dSSMMbvX7T z^|x<;=K6dE7j4rJ;)Asf>g@MUGcCM~zNBzEo2vPB!~e!*50h2f&~fS%o#*UQfNOabSq11{kVJ!Ty6d7ZxAkV0*l0ZeHQ@FJ`@H z+u}QO7e1x|x16d`@=kv&1>96nQBF%KlOz`eZa9^^k|vn~`sP^m@|`WDsQgc7HV(`d z=H}ibQtbPW?4ye42!70gYYioXx`caJJ)w|=TT zjK>RCFe3|yL|p*YrTZ`6dXHB0MclIp5qu{+uha$Wk_jV8Qy1{N`&gN&ddM#KBvcbj z#JjkRi0%1xz+&lC?-3&aKufX*W!e^xlP6uE8i!hEXG|`Z?x%BXrL(a2!mBbz3$Y;fTKQfVo?1S=)xj&6p z&x*D5x_SM3ApQP};L+88fp5nmMq@H{4WL}AW!HnHQy-RYgW?N;m^Gb4fMiuaC9Eyx zXIk(HmOuU@F7_u;_>I?NRK7FW&hh0sdEQ3C|BT_)PV{4;Xh@_}Tmf90 zCwBa%n)O4HqTc`llq#9fdYY2caCNjUVIX}%JUl^e4RTFctPiTui~u}i1t_6l;fH_# z3lzweWv`t;92fJjWlg;fw_17>8HSQJDujSJYSn=U$TdIZqWU%ULLOTCI!2Avn^MJk zR04lAC|OJ}E_Hn1^-^!38ueInLJBmuVM)BAl=I&rpjTwd-Y*7HkAPl;*SI**b_mAq z%?KfE2Z~eyNr7uf5HQMuz5)yGD@_|)_y7!#6BHgeEA&FR%S{vi-a>#(Z$|^>xXNQi z$jdSvZQFy9T00u7116TGckif>sjcAn|=LF5{3}WT40j@;T@9vz0 zJ5`{--lxbCY4?>0i~ASt)tCuxR@)zrj;(RJbK@8o*trNxqP+Q~<>69t+G0FMW_Ni) zMXm(x!vR>`*aH#gyu!cx?7uxQfB+K5S_%?d;fRP>*BUIlOK4$lA?+k=goSyDNQS{q zGIc;Ekscs!Bgrg4UL>BIN+JGbMY|$6wP1^DnL~^e75si$=~mebm?UfPe@;UYDmr}y zH<<7m2{5!b@F>z(!H3Q@+55arX#T?Zv22+LMLxQU`_vSN%zvqQ*`_ZKVB73pmaNW32#W!kO{VV!z zc`N|Uh3@;76-G)!bzO3LAQ!nX-xHICy$Ccmf3lCBirxDeN4J#eNZacJg@Rj-^ZP5X z)xunZMund~7foX;8y!GV{PEM#W#R$XW>J%c;5FtutN&B#R7gtIl*=1=;cK$nFzoQM1PY26$_5RZVINYuxZfUs+5< zU7w!2GTeH0-mVE9Uhj@5ziYb*E$Wov%MDdc#2q{wB2vLk*26AokAM6RTLja8c;kzxi?cEy;wMfNe37&7d|KaWl_^V;dgOK{66cj&hCZFuPE zzm)@0aY9b0`Ogc<{Cvj0_$OkDg+AJppGGqk&9GBr!sUdLZ_7}a^KnhY?@7(N^ia;X z+a9&i9Zyba9I`Kb#3~dd8VBkPcmkkV9B}Q+Y3}_MU8b>(5VMaIGB>O=p^f+%Ef&W< zLH0F6yWzI22iH_keJI%>F#rkxY~^S|)+ZO&IJfseHt&f!p8;z5atgEnOPhdY04z@G zbMXozDqKTxOu3TyAJQ2#T?WZV_BZX@WB!KW&y~gY3$N>{x`4>m;0<%n`cSwy8aePZg%Z>oKKVC8EMEcoKs{=E&K1!e9z9Do1-Lt8=(9|{h=m+If}LnP+8T978$p;Zf{wp9ZS5N%&ej?`fp zkgxic5^Zeqgg;30* z2t8kQ#^KSj9!jZHX&7+@DYiSQV^oekJBz%w(1DFH5ea zJi6*)2EJpB<&D(}Bnl}Pd6<#%`Dum~GfmJ1vCF$2nJB7aYchb;%F@0A z+kD25Hl3l?#__{%;U1>umAu6(>PB#6+o#gU%~E|e95E5 z$jVN5hRYR=2*y^S_8+k-sy=g?bt(5;K-aoVA=%e8?^&vOuuNA2sKMp=l3QnwsGRG| zX{I~qQ%N4lft|^unpKaaFnwkU$H{k~zRX`3mrVTEbdJYqMO86`{pjhyzPPzs1SuOB z#C^vsyeowDE#ZKyO2q*lBj~8dNSzSyzPsTQ%}rH^;=7@ADTwxf0bFVC087cLR7mN59+iR3Fh1jF@yVH^6=5? zICx?phf}&QN&I61cOv}6aX3z-EqX@y62vRk#jG7p=)EWLj0xO}^AW`1I*_&LBVoN1 zE)-Y6qVz~wRSRV!K+o=RK(9JXv9h&HlQ&47MmHw|jNb`Pkki5n%DvxlhG9LxWsXPY zc}#;YBoHHgOG4tBxdBZ>)7fk!F4Ls8nQrC*I5eti!LSh=480~1)$ypx0IT{ISh6>4 zoF}+qm>;5+GzqI`JxHiS(!9Hw*MwkpXap{7-+^b_7s@WXWIv51};3O;(JG3p7p?$kLu- zG56F`T*_DqdYmlzmQAA`3xqr>IMg5Hf)TZMpr^ozNk-4$S$;aWqP7RIX1VA$tuQaP zX;)mAc{H7Jj5|(^d_^S=S$^ORQ#-%&a3=77fN8r__p)J}G~TD6&Khr-VsBGzi)D}r z%@u#Brzv=omMl%)pWh?ugT}9uL?`jL zt_)!2+}c**nK=s!Xzfg)L~3F3jh=5jR|a~d8CpPf+ueKny|#3&2I*$^?zJ$G;}DI* z_YO2!S68g7Ka+B(000000000000000005iNa-Y+1KrR}XoO0X-d!wLTAQ)-Vh~mq0 zNK%#7joLv{zc;qcqw^YNh2D_FP&T?x*TRqQStAPwF)|<(hA2mXKf3|Wc)t|le+CO# zO^FWHNzbHO@7w?YA6x(c000000H7V0fW~?pe%r03=D>EIq>MT=>~`^9pA~*w27{qY za-hXOnjW$7*&+TixS1_pS?LJ=kA^dgE0-l+F?eeN+=Nz3*2{OFHo5gqC~M>huPUJv z)lCJCgz}bRp9;)vB1LhbF1whVpw$KBTiG5|vGV|xlj~Tz4F;Az#o$U_Fv5DJh z6D0T+8TC2u8$NhX#aQq^UwtB%m!~)F1{MZILmwukrPj!c7>M$BUBQkVvZh{YZOyB> zwL`U>z}Kxf;F$este0rfLr=gXa<8BO00000000000000LMgRZ+000007;>y};eJ>I zGY~T+%nGD{Oeh)W) z(fxkim2;Hq7WQ@5`i*Xv&hqADq63}!4OZVbVgG_iAiarUB&GAVAg!sF-(X7ITdq#^Df#`PRcR@@5!1}*ACW{V0c(TCG(z@Q_FG0#kZq+|xTxjAMMTVsqm z#O!tnGOK5Mc9m`YQCYRJ)64RoFd`2SeNfLVnqoG4vOGhRsnN4gyfzk~2L~@noY^5) zdNQ&UWo5+YrOw02qU_Y-#)Y_|NHVg0)#fC-CcM(^O`V*`hl7SNmSuNlb7uv;Wq$Nr zR7Iy?Fc&Mt(mm`{gF&i*D=Gu{!wvYoo?1Z7U040^Hq#a{Y!^!OugUFY5No2D7EmSGNRC6_XTL^^NA|2YN3+Mi z&wh;-kL_4)k7tj4p8XmvAKI{Z;scG}CC>Ix3-`-()`ID(>_@4bZs)(=^R%bA;LO6E z|LaWxv>Wy*N|cngT|ZZk4|~K)0A&ed^|BWf^!DS@F2_Pm=sy9_h7_B4AGTG#cTY`0 zrKl;BU!*dc^+r{Yh(?$Lm7{NPgUN?93< z{lffklwgL7m-YGg*v7oJ1BaUt?5xe&FyC8(xpgjrNo(9eV zl9reP_zRz6I%=tLjvQymXZ{1k2jn^*aLDa!u%47cOS5s6Kbw{^3o( zDcR^djeHxRBSksVO+=L{$05I@(fts#kWem56WbN z3POS_8+`q42mk;y&72pShC$g-Ze`hQL#lLXdnOp zjy#qdjoZf>?tWmPao=IZ20mR&tKcGTW(t+I(VEHFZjgl-ycy5nhJq6eqTIXaPhJR* zt+OADF_AY3=Ge~dUoQyz{FgmAXzgtF`;qOIL3gCb{ttg@l8xWQdJH06*Gg?pgk4G} z%oV4YM_T_-WH+1=;+`z50ASsaupG3^C5XFSJ~lj-)uD3~?Q|1$v!Zfc%w>cwlF16+ z3Z#EXmNZh{J+aRJk;#dekyHyycII`%ZLd$SJb)` z$2%ifShAtiEEtI0mOnvXOjrGr@2IekiAk@&3lMpBXDD&CtD-TL<7aOB6K^=l+UM1R zIiS)d%(F}2Q;;S`ZY!=us)8k#6P=~DIDm>L8mqj! z_O*;#EBO6xG?$chs8$0bKnWFNxnDVSErc2qy}y}k+1H=`U{P_d&pzobS+v?O>&LE; zt?2~x_`Er#8}dEwb7Q~&0000247+s1ta?5UZra^^g>Gs)oI_XWx%F&O29`S}nX<|t z!TSm*LglhkT9K`7Qn z%^wZJ3P1V(fB*mh0Zbb11_RuT?;+y83xNSFC$1>P+yKgKdcGFs+;w;xS_4+S4QdFq zNi4YJp<`X46_$76IQgm+Nlb*d{INhGDPxX zFw;fD00ihFquKPLL{{zq{6!DIR5W=TBB<)6k3c-VRj{#CaY}}(C8tE1^R-!M^7Y7o z3zwbyD9iBM@5o6@x;mZTExgdcOyi*z8A6aJ{KbM9y|qmKPwbm`1ZCXl{H>xVlJRrP zNT8GGidP#B;sQEhkHf~-vvyRovfLD&VzPlBHb?CW?M&JVv|3>4a8K>17@zo!6e_!zt8LDWiY#lAz+jn z7+A$Cxg8juS|T^A^ovqPor|+o?IO-vih{Wuxc${~FLo!5Jm`!iK;yiQW+c4bzOQKu zVYJ0h%cZt)@q+iGE7N{m8RiRmC zc1x4zuVby2ec;VfrF+}Hl$}}T*o;si7t#O#024lB1Jv4WAZ+qDr9g=*33^=nUVp!3 z#iPr&ej~v6{P#_ZO&;zoSVy-Pp$-lCvLCrXi3mhdw%}`bFJhe#;E+4Y$dP_xg!gEg zeNKJ29IA&5{EMN_Meh|tiwBa^!J*)yL)X}6MhjF`df_90d(lN;R`)?ytspH|W7cA0 zDLm$oZVR55e?lL^0ZN=2s#7Ox>imrjID`{6tcxD|v=p@?tG40{GeB&w+EV;Yam;7Z zv4dUn_ox!4F>s`xVRxE)LYr^Sex12<&A(VqkPv~$PyrhQP{jw(soEa#u2%p9CiSuwm+r*dQD0e# zYO4-Q?y!SICq=QkE1FrD&y(pP?R-lF)&J-=fWC!Qi~+dJ0mS`b;GG!G4EkPbDOn)_1f#y5WBYGlqQ>ovX>MSwee;gdn%wjanwU#?VZ{AL=yjg zhKvc`t5I^*D`*92$7tt7DmF>C66yBNP9726_NW==7;{$N8T!cJwvPW}?iCtTZ>8Xp zcYv#Hv5>grd8glgTILz3`5f~p;g#>LKx`a5BMh=UIV@kGFQHXp&sc!;?fgvv8SrI0 zeF;Lp-@#^phRwfE6gZ;Rv#iFTaT8n?aY%>ANlbjer|P00V${h>D8h*L3M~mO%W&rb z6}WRZW^-gauf4;Q8Y#VQZ@uZJ!H+c8N7g{M4fGNA?WPGYjb$}16;?-dYbVr0OQ^vY z9iox9u<)%JtF4%C0<(-W{r7dAEDr__-K%peq}@g>j*9Ch9L3 z%6EtKDE-9j5kX+1X06CL#xvR6Em+0tGIv@fNGYUO1@asfy&6j@dk(Gv9JK|Hd)0c0 z-q5?#gE!*27x@AtS|S)S)eES9bbCH@u*qU`k_!|v%h^DOrIVBdHjMxP05xi4c|vPi z!Z_VpLaLFbH|LYwv?9%d9>Ww*9roqx${|I=JYon)?m-Q&>q{)dr*M$OFI=x^jWLl~mAmNV4+7=j1++0F>Vu2inOtOs!fUJ}@5{{dE zT{HvR)(I28HD=76*mfvEcOS0606@jx>W0oveDBdi>?-}rT=2YjJfly}aTnEkS=l~UwzsM8!inMGF~ zc>)N}04#`S^L{W>-9_)cuPtb~w_@NVy#PJcOKT^4F;1cTB0dRG?L+u2JWpQ0wDC%l zj7-O576MwPBzfyBAmYcKNWt_)m$InE%ub-=plmS_#rC6GxLH-RQinMnDh{hYu<(BxrL+1Xel z{@<)hSC;eR4jr7Np*LRR^VI{pvJrQFTaEZjK9QKSm)T}oUOg6&%m_S4cGzMBGZlt^ z`FGPyKez+IxPXI*#6KjDtBBYe$)RTr4w-vT zE&cF|V!Iv8?wOy3?5TNg`zglnF(MX?MToO2PR#AKSRDk_>*>g&CWn^jYy3;n^19XF zN+{p-3_4WsD*b?vq)^ptrQuRdTkb#lpageXi$KYA?>UqffU*EDoa2v)X;AJLK`v5W zp;v}4nxT!Ck=j3CDa>Wt6LAqUyb1BbE`8nzzN6JsU)R13aqqwBe5kDLWM?({Os(z& zm2R8+mI1&nKZ%{sUn@tL^@#DFD#8^&=Y+NXy$Ujx%6+<~h06%4sXKAhoSqV)+ypL3 zD!A17>#oR6A_G-Q`YVZR)}X!y^2hZ2v}sRuv^Vw>YJ$4z7*qIw(t-k&(Kp_gYZh1e zWs-05NuRA|6cJ!Me2C-;h_O=+n~c`ij<3n{npmYZ1QPWUqJ^^Rp)23zeHx5%nPJo1 zF%=w^zenhRLGXB&&ed_Dy)V{0OsWsxb-dfZ{EchctZi$+Z_^m~P*Y4vY;Oei7VUn- z1s^zG_3$HrY^R@mx^w;=e1|xTJ`kV8z$w47-15BQ|I>VjPntKNi@uqw+lw0Q1Io9K z2Jx{;O;^`J9JbvxX2_Bzh5$aeFqk5O00c~2nscfu2=$D7LN&^>TJG#+aE>0~za8?0 z%=lXwTX}1@Zh=TZMORWF@@j?URFWPb-F_zB9K%#}+uTa)_#{+Y8h(sEm82G^_}UR| z4BU_ei)66?WmqBUMW`nTxU`|RA0QmJFzl}zPtNke zty(qwWg~YR>VxMNA1iOoq=8RoJ5RP(u?|0$@oTk)^5DPvMF(=Gq2hH}NuF|&BXHh4 zazr)F1FOEO@c-_XOz z(>zCWSecDdIyM|ba2Jv$woE*oARxJ@F_IJo;mniQB^e|E?8+63%cdC(aZTG7h>M9Q9JV?&92`-_cI7>q&jj zL;eR&`RWe}N@&?b&rz|op0Ne%k@mc7jcWm|nk&^=Wtu!`miy=Y8%Cv!Q1M^3>yk0? z1`Bl%5e@>{x9Wnyqv`Hp($Z*qaeoj&wB&AXDVQX^4tDqRq=it4tog*OmSHp`^F{?9 z617b!Igo&E46(uH2q&-7-sn$=uh5F=r{~NJY<=Jx{s;}n1?(EmWg>W$|X)Tyzhi)^J+gxL;S4}%z_ESpI?Zjn~BNHQev{4gBtC3{EU7p zH}%lC`^!gV0+oU)rKrge?3m4C?g=-+bu}|z+PpYdg^o$@d^ZL(`d&e>$%y*G%K8wB zdD9{j&kH!zS+C?==K;*XPLW(!Bn0<~>B|_UibKC2t-eo`KXysvhoq|FQXw$;+jm`V zq)-M=7-jYkN0f2f!MH~-GNi>)8TFsIp;XT)ny$eH}fTf~-ET|sB&{uXYL zHi%5IgwBC5{}Y5XIhYi%IZDyTKkG1NO6EAvaOXm`z?UOkYa~4%R4D1sdCAS#9kPhw zV6ZtxO#DL-jqS7EI!SKnrthJTX=aFK<(H+M2Ec5m2on{TdRt+pMk10+puo^=b7kDx zFl;}D%f(Nz1f+?}SVCeaH43{yUsF?nga3jx+Yt+LsWWavBoBO-aD_Gz`*-jUK+i}sB+DvV;$!+lQF$ck-Zocvp@T7lj> zUjI#RAJTr`3#S1KX-ONO21By(&C|oDZ1h$R<0TvJdHefP)M#>;k4HDQ3U8_+8Me_Ac0_B%UYO!oDj@8>(3>? zY{7vEEbZu9{ykGc>uZ+lGsUEg0D^Nm+(5C{4G#SEkH0-+$l<6XuJ5NgMy}Qj_BJ;w zc6y7m6+%ZG&t-Hl--2i(nqr`a+7*T%n`_cdN+!WQsk_D0-Hw}UaM8(f2^(twBxO5k ziXMX_v8d=_ug&tirCp0)i0j7~uX{vB`aJU%aXP&(8EQ7x$O%2#*D%@~o3Df(?og$^ zncc630IW2kwJ6yvyXB&bq@-BZ{P_DbXND_AhGa4@R+i0^rp>rHv$7Ke6@RN&T=|``cIsnHq<;xhE=&`MQ zp$qkvJ&9r$Pd*Hk2pmZ}rr@0R*He3#NH*YSMSaFmM2(7uQk<^bIQr_ zg_C6ePAx5UK995!Wujy8j);0v%7Lxwvev1u6hA~^mKww@{dL|XIXIeDfD+?Va5h=% zRusuN8L|B76Gi#FElJ`Wxr`0$h1-rM5^9ESjL(z_={`_!Npa@Wq7U)<_g{zIE>mZG z>qT#Xs*y6SL~Ce8&YTl2l7^!XWzbZ3d3AXcrp`!C5Yq zz1_8~DXAttc{3c3Hd|vEdi2d7sPMhpMbNv00g9`nV{>GR(YH^JMYa?#{r)QB21ff# z5}^NnVrJ&w)iLZS7a+=|e2brkw{n}6wk+e4W49lBA>5_9OmdRr*fttd(sI@`^(piQ zgVk{l?zX@74H1pmuCzE>uLh;u54nD)l%VIy!qCsa*SV-A!e&|x5adyNwvP`bFq+K7 zbgs(yC>{cja3qujo>@6t>x1)xJR$CMMHwaaC~_6 zBj^st2ymxwE;PkHCVA|$Y+XIo17m-OUX7c2k(0A58q zs_PIxYO((yZvuU5h(Gn%4!i~XYKdpGufYX}C8rrqM^+4peQ`2#P{3@(tbeiG%`ZPH zF9?OTAoQ{Ezh1KY-GWRjmm@JBh|%aHM4>Xyv`jvao07M2uu9V@L6h1~2J@7@!$@OcJbsAQZU|PzS=#RA=Og51wdq$xLTn$c379o!pBkJMG`Mu3gbtT6T}lV# zOZU1*@N!=uHMUDjWQts}6sO{mXvLVmWnIfJ=H3Lf(9|hHAZxA)wNjOe4{6PO;#aEh zkBifSkfUjR{2^DVe@6ox8@BZjvx#5!s+K@?M2em^3kArpvT|39qiCwD6e%vP{kplozAI zE7WGz1TlzPeM#%+n4=lQK92SUJc4@hi_k@WCZSh3b8i3+6F_Y{U%v21-MyG_WYMvy z2o_7$9keTZl;qwR0UwD)DUm)iy(MoE7(K04^W_JA{+P`FsGo;0kMisgbw|3e_|W?$A7fV+KZa z`&7ew+XXvwl75#m(vl~;6JnD^?$@Zv!uMMOtrJ8Twv9{ryia>yakZNguFeE4SC`~D zqSgi0U&B)>SF#|mfRmVpeH(=F(i&OQww~~ALD7~MmRG+|7bQgZRrBjK{eizj&Yrx0 z-Q8U%#(mx@(n+o%Na!|2r{|c{vmAMxt{tFtr{<`pF^s_AF%aNExDal%)Yu>#f9NJj zeb9)9k>LY9TW2F-RJ*AxlddCI9rEKIS=XejOyC4}jSPmwac?ZdN{PgT3ledfKrzZX z0ay!UxsU5f%s{6yGlcb59lTCJxVQ+W7J$|$z;F_Y`}h z_9+%H8OAP>{P|XGa}M5`4))YAu!4k^;>Ra39ECU#qkUHj%F^_u-GCpLt?moOOJ{NP zvLpib_Y5;I5Ar2HoZR6fXi&um% z#Q6fi@DtK@_4-hMtMxJbSK7S1*)#VaI)_%a9L#5-`WbMnRf?`a_C{?#8H@XdMFQ(- zRui~18Q53j*ofZ@96ixMHjm3Fb7tf-EA^5G^T2K};Ns<6_$(_Jw6+ytZ@lb+HZ#Du z-n3SxMh5ubf6F#DSg}~8r|)QG+k=|9Wyw)5CQDqz_z8lE@xa#nY^N+hCD56+kQx#k zN;o0X;H*9CKQDnjx^X0r9|o9amfl7Pg|zlpi1B|&ZdgB(8?fElZ_+?y&sbHzLS!Wn z;CFfuN@}1PUUEx$R^0}`vaH76&*|+bCtIx#6^?|`M+3SdjJ3CV8`h<*_nli)otqPh zmA^8LG)SY}5*~bu*7TW}nW9mDp0Q$v2ym#Q;7zR1s**CdB>y-*Iwu$-btiZ~lTIBI zGICOOI9C(A_Kb^?mhqC@tpcVil<)LE}OL5tX=(C^@{stKFwI~D7!Dx=;MI0f*wQ)B_yvt+0sW+L`eP#nM@bh zzWdcPVHS+xg&L!QV9MSi-ROFh?}2@!aymz_P;25W=A>>FCb#@hN4WiLz&8E<(=awA zr={cn{9D_1JO=}oeBk#+eS_U^< z?*GrV&NCo7>L!TRa)Cpi zons4)Cy7ejG-?d`lcY}ZGe%~aset|r@T=TDKPBR5>pU#900-9q0cD{Q5COFzMqnX+ ztYyJATzbc*b@`TwY#ia=N`?0RliCSWistWAqzWzgYF<@vR8~k%O)<9tSH!F}9j$5Z z?@Z%3hF(yTTY(72Jdd!)gUDn?@FGG`W~NW-0&`GU~NF zimQq6z5_ItxH+&y<)xvqUUBYB&0^7oXx7;5=~)}8ZH12HLOFXsPfs5m)}F7~NxVqF zE-p5a6T!VDCta1eg<+w9gg~Zd4h-Iyvc689*7ETLz+@`rujXQ@B$x;$vvA@@I`fr^ zcv{&&qgX2LCgoWSHmT&Arb+DVpYgd6 zWqaqq01_#&Du+VR8Emf;VZ&(M zSyfSajy!>SYzzH0f9tFWxBhi>cy)jwOyi7b~J^_rd~a=DklNWnh?;ea8H_c=f8PBG|45@=r>_pyNq) z!ErMq`~AcS80+!D?7gwI2LdbG000IzfO72+gqrT^^crkFqJ%G1c*z{Q6^_KK&(f>W z%Qzw;Vj2EB>}(x)Pu#5m?a6Tmm*b{m8uJ=7j?1cXYrT49Qpy8HU;4oS%Kugnt)|^m z86g#4g(W?Fz!#b?PZ=u;rfoGMu}kDC{I=PF^ta7ZWv?XnNM*;V#H<4bxrt}q554p4 ztUB!>d|c?r&FY^C;ZnO)hf|IOxzbj<9{~jCyOHqVeSSb{8vQExqEY+Tuk{t!zAa&K zItJdp5P-E~f>(hoZs{%|MNKv5lMLouP2L9!@ZQiGk>}*6Xifap6S-?1rhvmbb|SZ8 zqcPH$hO-ks$ae!S3fCB>kCXuV6cmBP8SQGcg&d@$2req0_Tem-b}gKr7c*OaCge5z zmzyz{sJIf8NOfglKWe+>i;LaZu4RY(h%ARPG z0kox^9E$;RyT^<)c1&I>s2nkHBCq$!o*Z@|VyW!P87c_D^mOdotWOn=JVx$>Fj)_0 zVjFSU$nML9Qi^_cCI3rp+trca3=e~gze zWxVT%U=|Nx15zhjo&M*(}Sl@^pVZUQ8`Ro)6qU;}YvXDSrX2dX)cMTl$- zY{1-n!VwqXH4|xz=+C%DkftK_dYD;|P)YwZm_DrO*?&T=dY3}Z5SRik+=}e!RJ9cVfh5tx zM-I3ie5_(@{PMim!|OhcJ{R(dVC*sJZF{2lSXf?5hi1=XNARbV9M`ZgX|9?qgOcSwfDVozF8NaIaSC1%i?qtu2Gqd` zvTA+SpbfJB%;j+oE07@yPl*V)p&Iu+GTMVBfo;>eKo*tVFFACoEsIEk)Jh|SLi;IMW z|DGGpRQu%`t0V+QYE)%z3DX~qt0$-=mbHp+0XJ|OnEc=+Lvp=v-7cXCoTFvz>8=7x zYTSO!+mhW&CvPeK)>{qU1fkN&mc&D4Z~1SN#6St|Wq$xOJ&MOUd}0U-yAqM^i1V%4mu9Hx#w1*gs5p^pL-f+J*R4Lb_F{1?(Bx#-kB7e%Hm5;<_sCy-%40PR-jlT8@C zJ{7(TocZD0tdoal;{2#UpVM5dwtV?1u{y6v*Yo4ds{Ol?B;dwEwpBi9iLHrgXLy+f)2$vly~!tD80;}z#wsf{%s-_PIgTdhNcsw0$gTdhIa)gE-SL^lq{eHh+ql9;Q z%K(zf2|MCzAuFzj=n^QG# z`qQWk(9C@bMp8mISc39JNK@5%6{_L2@N$$Dj1km+~ zaRQ$Y(O8)s>=vaGpeP|(R=fA%rz*{nDh~%W%jiYMa2|+1!ML;o?f?KXPOxDO#EUAg z$sqNc;))onSoiq64rm8rcA`&y6RL=l1^TH%55#<2VB4q!v|N`#z&K3k90kGS!>%>s z;kX6VLG8a$f{PX|jP%Bh3#OSn1;x(%9iK&E&Fh4oSYT5kCVQEcAF02|F(Jw2+407y zpxl|z?qy#w)`J*dn;0AOLoOS^rUKOT%XF>duO(x?po8jZma2vCvU^U~!|UhC|Dec4 zA%P#G04~}9xiL@WaE_mg@zWspPO(^qN;S12f#v$ot&pCG@O^J3R^^nd0|(_AnE@Mn z77UwYUoOkR%|7nKqSTz>w7zRWXz!9>if1m@{=oEr0is+Q&@(oEEu+TwDkCqd;of8n zf8vPvQBqRs__s zYc-lbYs!V;fU8LNQV(RnIK-L1YI80;PBV&vZjQ}+bguMf3`?xgCFR{ITl52)JAz}X z6mIHqn=?2nf)+S-+EyJJBR4F`j|~_aTIn>Gnb5cM1$1LN7m>OhH9!^Y+k=zgm-LMG zg}oGaHhDrfdOtJJnF#Ix41&gfKen|1g5>!?fF3)kZ9n)<`}UB1gS+;mtIZ z5pjE1Nwh^M9AI{1)^BSN0dtB_G%bg1qAe4TPzNKlT`sLY_G;g*%E!ox5A#VbVaxGl z<@SbHfjL5m;g_&*vN?nfcnQqC>;;-v_x_IEY}$MKp8mb4CMZT##@#!9-IR;?Kx)MALcQ>0CKFosNuGuR zlpJTO=5>=&D$9qf#sv@FxY9$__sl!hLqDzzA~EixOv>$CQ2*CNe(Vd?@YH~==L1`+ zNcJg_dJt{J{=*&oAwMRVanm0YHb?+YK(W6BTesmj-t-PeBr2YA^@%TNJ`7kveV`!c zW@>N0>DkqsTg5vx0K#C82x-Vtv05ewh9h{B0z-JbjCs>Y>uI*vZ~x*g+1cJBIY)KO zJ=q(8*WpDq{Yk>L%0g1Y+4+a?iN_LJlIt3cDCjXww@#X%bo#ciMvG{+u0)ozkd^&3 z(}tmXpT2COjm0X=kjkoTC2~Xg5w#UEZW>h`E%j*Wug5RMTZxKDP}FhoQ(~ZOUXX*5 zr#n0WV*R&pf#Xvn&mnloOk6Ox+Ik!$cr=3mpcuLV8S6ntc%_Zg|7pbMlb15)T#(q| z8-MOBCyMA&eAk%V(Bw5cBORJUjL(T~lNmLsyJpi=c@&tDYN_cQk-k!Ua(f&_f3Jd# zaie@Awq9YFI}ql(PaeX% zqF0`Dh9j2&_)KVpJDw&^R5BP&JR+P2G!wu{{bvJ)(J>WsU_kb?zb%4HF!gwn%g5^W zJcZe%R>(fDmL`7ar2wdPQgBRdM)TIDa3Q&QIBPsYKhnCp0OhCeZXDiWNdz8EWuHjt z|JE?>aAZVuOK19Bl4zBVh@MWF_lg>u5lUT%RSZsAYdTWA_aMCv>RIoA>nRllA_$(x zqB~Ieu_9E2f%li-YBVo}xC8x9>P1IzXblfr>2!9IN)b|U)Y)*Lj|*PsDk*p58*bC) z=SaR16n#X%pB7v#TQ{N9L{W^eXs{@GqwQ|&IM;Rwm3Qsp-c3xaIuS)&$T#zvbOuc{ z<;!M8_$p@0u*13%r1(7stdf~Y;AwH>8fEXPL| zbpIxg4P?veth&0v-B(^O-FY_b1_0A`hEBqBZo6wIC!vD?R#mIe7-kKmq!;~yMF1@m zWQRu#a%xyI>?R}tnZT90lB)}sG0JE&Eokp|hyeb|c4?v+Z#S#sqmHG&=ry9Po`|T7 zJ~rLfoN{K2A?0(Dufh=@QD*A=nb&NU<3JdoS$mE3^r7?7&^B0p?7jgw1*RaswF^6; z>wWI8qA#kDq#iA3ze>zbPfeqQLbUb(CRL#w0hS+~oK?K6kz*5WXp3xE$F$CXV2Ugf z=z|O0z3cq2@&KF;qPX%Yv@?UWp4GCD_|jRFvrimyJr28uerRc*VeXQX&{*J76@(Jfl+N*FCO9=@KJGTUzUI3FDPynLP+kchmIF{=-ZAJ?+kBb>6^_vXR zMfc|@YGJ{RfolUQK+IZF!1e3b1MyfBu(dd0H zNJnVcAY?iu^(nv6&=ixvpl&d@hncfhYOHyeROEP;9}*=e;i$lD&2D#s3&*5#k;om& z|7qV!DEL7b{g_)3`S+rYa!aV;Rt2 z1}r`)Nl5+F_hOcz#F_g31k-g8P%+6#BpM}%j(;U$KQP%UIW$irm3V;TOftV}7}wlO zs@+^R8^8I#hS$ojus;UK48VGlGA`iP^jVa3#HhB3VKQ%6*G?^MKFbkTfhN~!J;W?6 z5Ngruw+!m zbQv6qcE023CcShPK(zns36xx@%w{MoXRZGrz&584CB`n}O>*?0W9hQuo= z#8}|Yt!FiV-qL{H9ZlZ(td0o@l5lB@YP7(iC)kB!;0Oo;W{F->1+Y~ZrcvF1xKD=@ zjpk8ceJw%=g^dBAeKMFH1x$Vz`9^1r|848pEwC~eW-NHgF}ke+MDm*30YMKRF?Z=V zmz6KFTjSh7T$dRn5~g5yPAEF=osjkCMDq4JaysA%XG7DN`5*DV{X~nc`}*p zSF!uYk#8NC)dOvZ1dL|^9r(V*_114{2WpWD69stkw9KxB*94#zDE*ViP>Wp12M#F)*kz!_%Q zhj;#)y{6CrKl6iBMuGvSGCiMy*nL1<*C8#l4+J4Ax@d2KDi`D^XXY*3ln}zsbwCl% zD-kI%&3V@+C@x$u+0jh?iVw?gm?G8lj|yLW4MJhYCAAdcb}JE-R3IbrDL0b5zND#hOCv5OFj^@}J|(;` z_p{P63#$|D0_Vj{NM2I6{6!sqaRbc*!pnq)dQQv{tWRNzZ1ZVLC;eM}!*rM^$zsnr z1G-+_VD3ixp=hNj_uEAF7^Z4Hw-9~%-&y+_vm<3#x$$VQaG|oOnI6yB)tIQCVbd8& zw9s|PR0#ozO``8Mf$ei*+yD(Aqs#&zi}^QE>}kDxTw^$AxQ(=MjblH@--He5SVb2G7~hGl-Smigcy(H@Js=Zv{!J;;;(u`9 zViJM)fW9{`SmI%$M)>of3EKfJPo{FoSM?EVe9umC>eCgc)uK)WkL>&2j`b= z|6}Y>+57?AsJM)*e?lkCLkEp7*5OpOCsMaY{nG1fkNrqB#6H~puYLQtY5uf0o5!)g zul+V@PXRU^rn-X>QOSsP`m?-Fg@uacnKZ1UTIS3y84AyD4|*-=W_zbRhNV7|2&sx_ zCr2J)D?r5gP9$;8g2$9U51u5X_%t)1&$!dq0Vd&kwhANj9cOEPI`l3#Y~2e^LDUyp z&GG5-_J-H$S2iFsRp?$>!@iBjIPR@FgYfm(zV&8@+dNs8^K>`UeLtz|z! zOTg^GqQq7gOfrt+hq<(+{=qP22;NPKB)<81Ea=W*dmpe)_I(IP$%LiD2>Qn!{Fe*jMTPu=tYYQfj zhb+5SL$5A&*56*d|7x|z$7ZanLF7WvV!z3`C##=HUZX11e+F%aO1qSNa``+-+x>kFTt6 z%I!Dy&USjy9?y>H>VDNQ^SBp*(BqR6yB>em!pNoffp?Ep^cU^(@#4Aqp!f|ZG)V!z zKmA%NdDSEw#(o_)2uF{jJRfl&*Zm|4Rc6Q#FD&<8HwC60&P)<*o@??~+zv=#-L7ln znjrk*#V~lI`;HukI)UC&UX9VA15g-`Q00rPPc+`ye551G`#Soi*5MffuJdy*d$O&f z8kn7Y7|8+?TXR*1#W0)|D`&7CtLAlKpafE;26{ni?IHoI_@8$HokIX7(Ep%LAv0_V z|IJ3REB=e}yUE{3M&PV|TE(OZP_=s|D&{|Og13XI1P2_*|5>0jNDNBGlhNV-b$fKQ zxVg7xLP-!~S*z>mSUo79K3^#e4qJ>%K??lU0(79A^aRt88?1m4OMNupYlqsLeB|~Y z`FjDGDm*nDg7dAZ*8Uc z_T+~kNyzgD)y#95BHz50n^wh9rkLdV$PCx7dvG8CDy7mKhGe-jMWh-Rv4D_?@VP!` zl|dX*6oi^%CHJsx$k$+w_Oz5DI9%C>*}wUVk3@5VOcQqdcDY>%uJ>pIF4$`B&OdS{ zEuc1b7ut|zIG%_-oixxcem|9bT_ahW0Jc5Y+AfPU0&wN`9A^-=%x@C7@y(#3Z&3`C zXMe*A_5KUgqf1x_tzjshK0+u$S9ZqhHKo*eC1IQ;_k0X|(0e+t1`^ z13siqV|ohJ?2uoELM7?Q7piI@CCDJX42>9CzHcN1A@+R;U z9V3-+2C>!0KS6&fk_U2y000jv?^xnKqR*+>fLqB${1Fc&1?iwEsWRNBCw|#*@fD<96u?$D1dSlp zOzAXkN*fFoCz9F!F8xt63}MqI#?)0$4U${ zT-pac^#@1?LM$3qbo%3M%O^hbZ>^InR3OvM{vXm6fh+yRqH`NNPNh8KPmu$g<;ds&!P!ZBa2Q z2QwBYBmfR!I^4WJ^_k4rM3I3vJCdyRm|{|<4j$B@xF8#o3|%wb{Eue*0001T{l}&O zS`Gkf*Zb8CQq;p@UoUpP)b;JEZ$Z9+2Odvw6f(nwPcB=fw%7kCryi=+Lf1h3!H2?D z9;{*UcrS6BCOdI&5WpC}vheU|VBpA$(!q5M`ouEN;UIXSlFFj+ymND^MlcO&!*1X8 z{+xc@K_R%sGfFJ17Fef|DO2 zUahn}&1kKp9>qB)g7X8A zo%M)`Sa0n)UkLL(h)r z+v9%gF&_|KsN|8o*jDzhXaj^e+dPUJNd2{+J1gV=ZM5ZJhlLxuyol?z^Z3!s<-bYK zMSmchzFq>d=cRt8ZJfl$bD^zv@&KHyUGXTtYNsma_u) z?)vd-(4!Rv{-BtDn{Y^fgbB`&ga<%Jx)@H(?$j|VTbCwxShwi;^)h52uxM+`i`p5D z-M6kM8|tZrEX|#Fi;ITB2&lqywuKdHOm7F9gF${N6#S6v8^$YOLqPDX(n z_cRJyakuq`u|Ub=jz36I2x_Cg2~JK=M9UL`+LI5*ZGu=xQ(ST<>DHhIuXXjLsmI4Z zr7WZGO4f(zx@6JEIIfxWt-zmcueMI5mydqRNby9HdY3yme~!P6sc7XGPbtjMvj5s9P`XsyN3U zieL71BH5MVH+cZO!z$kv$;NOK=(7rEAz`azn;%z)hnM2W)T|Mdp@_#s34$gi5(LOy z&oJ+9h1U}r!E~NYV)XH-OOp&pDs70;>WX8X3n>0jV6ik~tM|k?muC6QW#&5{L5+XY zq!H>O-!SLg=-Mh+o!Wsq=H^VT{gYX$@uOJ-UU`kc^hWU?SMW3+r^$sOrjZX-@LY|W zc7Sd?0Nb-_^*?74Md)VH%wMHf@!_J?qHGsJJb zT2MW|X!{s2Tl-utS>O3Yoq{h00)~t81eq4X?CcGhO0xzJpzwHCv`4242&K~wCCJm^ za^O&(ma8xRaysvxX67+(G{f5dZ!p=Ja5-5E$B~!)?U+F7l@K~4Gl!8~W7C=Z7F)>u zeH$byybDt?bQS9#!wpImBWYl;P~!Qk@xpfO4V@^nZFCVLB>H$VPlgW8;5{@`e8Jmr zb%^e#&E$WMb^TuW+exjQNrwOrj{rS>A6n?3nFS1VnQ;|{^pK(K674=9pJ-(8vacFR zs-Ou&&Fw4)s~Ko3pg;6HJ(nGG=a#Slm_MOfx#KYl&m45uJ85XC@0D}+euJau0T?Y1 z3d{WPp|mGJ7h{3d(#!G+7iH!!_a!)SCP4IhWSr!8lW$TqX15<$ee5IFh4xg-TS40X zh&BkO2?CbM){o1s=lN`x$lL+r)Btn6zPkvC58o$Jn5E`bi#EqlkF)FPI;>^i{E=C) zE)5e_k%Y33k-}Bw2Lm#>Qp$_Hhc~sNO1U^g$YfPT{v>m&E&+*}HIZ3ii2najLg*_8 zAA@h_SCPOlPj63*>B9r{UC-X!c*@}I=^oJaMHz0;DuE{*S|!&+S?Q6b|1LTzF0A^& z+^3c?qW8~+445L-wQ!MMf6yUzEnkP8ZU(WN+sZR#Xx;V9U*#PhPh*I$_3y7NJJ?W2 z2Aa!Cc>n=*dy%z_ACoq9kByemf?L@hxm8p3-AXgrcMjbF+Li}cp9(6?+LeD<`p5zvx5U9m@LD-AvokSW2fb%l)xP(!K*oS}*>$8BxW!D*LtR(YI&tTH|A(C)^B-JqGHt;p~%q;oXq zkSYO`;P1-%6}^$Dg#sYPer9lrEsPWG)!R51tUa#55u(UoCGSgwso5E#=Zs^&`)yY< zrcg+*hkd;|6Vl@1)WD5c9W@9TV)d~fgUxR%@+2@N0xFL+9%hG=JPpzkR<=q^+b7Ho z`&=xoASx0qteC@zEg}lo_2%5e=_APT+z2tD9V8L9KRw|!0^Ee9F=R3J&0W5EG(_Od z(!u9cFyWE8In8Z*k0`LaSO#D=$?-nb{nug;{LLU1)!Po0n3v;_Tz~)o00002snQl< zF^_b>QKH5zpO93G`TDs1hjJuv0|s~k{jz|}VJ_b2i)S|-s8Zem?5yKesHfY=qCL<6 z0000004^V)mxa8kZKOjHCB_xza@0W4&<(_&9I5M%-a&TO+hS7ssf~dV#PWyx8NOOR zZ4{@o^&v}+7W5%q$;4=mtuD#z?lUKJn4O&lp=U^y$f4$KZbbj~o{ay?6Qw81gpEes zqh&S+xT80TV2ha3q(Gm^!a-h8 z)4tySW-P&SZJ{}dN+C9YZU@ALTB+%BRI|Cc%&Gajt2((4m8T~bo@1-6Ynl;MPdC;@ z|K!8BI$waV$!=4yVFPc7kt?nj#Jy<+hU3J?g{ft}EvoOMTk=b5Pt&OqOx!LsJQxay zn$XAo12#|q2dSM$9CPnp3zSwea=LqHvqc@3E{4;^8;GgpC~QirVt3_dD{%DuvD<>Z zRG|26F2jJiD-@Bkjx8qYWL<^%Z1H`Wg`{ws>LlXBWpl=4y`(=S7%Bh&0HjkGx%Jyd zW(x`B=-VpEW3pDjQGr+C2;UA}To?pw@%=ok)*7FACdd8@bMflmOD7=4=K|i3a|fQB_g_~3 zu+&%lzL~mD5I0|m@(|#2cuf;2pn&(Fw%&+NPHdGfIz5p4bZTdKCMsfriZQkv*X)m0 zi^QH(5~G^*Prut(D9JG(fqKAmEk9Gt7OtQ%&%`jfu%%@krrn?%g_`_X&m5%yHC^)j zq`yky8|5|kM+Gd`qia+YXZJ@A5?)4p#d|{smPK4a>W>+&E#GRG0=r(3nne|#en}5w`eTsIyo z*1a(nS zKOwIB51Cw};2!!6{+t}uxmDhR{t_dmJ8e#Eok6x|tv^11at?>v=OdAg1@!M&@Xwyt z#3umLH@UfMPAX{3DSLYZmE=#>>Ot`u4R3j)nNAyLMR`IB7zN z+5H4Es`itaIgA0SFunnEelRm`V6ynps=!HjO1)97Rb%V_nGjC3izz|#NnmXnc|tI* zl9J^%{I+g_$uOu$TE&!)j7&>Dk#mrw{xUx->DhUyRlT491b9FI00OYY>rRA^0o+fx z9aU00%(lsx;%aIlIEoT(%|9+@N*xis)Qksv{OL5CLro}3Wh&w3>^mg91l!(-4XUFn z4-)GKfeTY#oibaQwG<-m%4{1@;MH{HsT*iUR`EcO4b|0u0s-`d>--5@q?J74182df zQYe`A+d(3lYu2ym9ulEAKpy{=e7Yl^c>$0?UXYy}8P=GRlCw1wf6Fu9FV$DlQH^>B(;XzQS%*|$ZLD^PGJfjp1 zQzj*IVKj&}WFvPblP#N3f&7Ziq?tK-pa2HI6i^(C{<5CvURBZj;Ed$>ttJzj%jSM) zUTd_yfBM6JsylZzRFrkbSO@0u*p)V8QVRCUb{!cj+KxMx`x9$`RU4yIrN23WB0x~G z7MM@;yUUtkUbz1&o-~owM7T$r)#3e|+5q|6Og_xHa|OVbvuDJbD7ChG-i)Qjg~=`D zxnERToci9k(+rps`tWmGIpuVITUYy=C?Jp!K`YI-++bOyxTv~1FMxND@XG7F3h+Ip zXywcwPIa2}oOJ%tq~EX4zt0+&Mk%M0{VeX-T=N7Z8A7PW7Dm_;QBOXJZ@;_DU-X$b>=?NK4#uJ2Gz7XLAYm15dUAK<{Y~e9q&=CQ z5wOx#%^>9@|1*jyqSplmxmR1_2&x)QXRy|GgxvP^`>vXfTEVbBpBSjQ$GQfx?)C=7 zN3dnWTvSN_00C_AI{z}Lpnm@8V``X!L2&hV2?A7386g(Dw>-b5tez4j>5f}&j&#`L z1;D_op!k~n8OGaD;ye{F2aJJY%CT^!zj^kRCDQRIvN<)BJ|VONvI92YIc8vCdfh}D z(1$c-9kl*{S zdCF?1STO0BZ+5r!a?!%C>|@qWuJ=UAj>jVr>pPj56oE-b7 z6gqyg4pSc|MNI)1jS3bu4tL$)UA29iyCr3kO>*qlzx2Qc2vo5r7@e57&;^*lr(lhz z!rCD`B*f~A`o)0bShk5Gm)@MmFBRC=QIUoU8YhA-dvsEH-8WzV4|F#|Pd>dKW~+;w zipIkLA|QbMf1#IVG=Z)xKjHpU1@oTidc^Do`(_7KXV@+ls(&UPWnc+FseZ=p3K z6v#uGx;FnClptXt5h&MDNlM8$k)Mt{-}ssriA6$^XjA;#L8xe-J*q=ZRsBLSnYQ4x zB|Bs z8@}J@aVnTb`@Avayk5;AC&p0ZNvX^I9Kgt#1_B!fF5>tEREC|4rk@n9V_a5D!-f%A z8!=^Q*2#5f%!XX!;OWvk=}QYSV5t~Qh!{#~jl<=t%bG;6rv|C!1AraTkx>xW>(C^{# zUs9-6QGDDq%Yff-=Q2au`n-)F@CpbvEq(OGm|%MM*gyX}5Wc7DFiGZCm}3P!7(QHV z-#G7JOeY8!tA&a>s$icXv8`f`nd?poP_l*1S(NTMDlBZ_$klXJC_m@E6$d>UbY->g zG5`{G*h~evWQ$VL=A*X{ zv|lg^JrDNb|7}tm))XfJ5j05*UyId0X~>f|z61xvL?9?A)?9gJ zQKWCtV4Ftp^SV&dLb6$`W0D3)m(K_jzp#IFw0aY+TZloHse zMY8Kr5(PKz_o8afG%x6Mb5ymO9y4Wco znJvd6@vJFg;_Vi!ss50#(N_6t@MI(E6WoGBlcFW9RrdDroyP$$jS;?j)qZGlw(?T8 z0s^b7?%Wn`9^_rG&ozOLBx;f`h?+qc4#?ld&==uvE7j7Y{W~E!624n=?EQ1}cKu3a zJ{-x3nMnzdZAJ7jTY8SZ_|>y2vSW8vTUS5^oMQZmg<7EVh+~abX)w&Mi0pX$s9SZY zri0qef%DSR#Gte{gdX+UoHzn+%K!;QsQ9ssmza;NpDgIUnD_aL1I)Z0p(+U#< z%s+!Y|F;?RyhEAdyZy1TPz=!<00fR6u8MFJm}tl z(5>5lUox`uuZE9?`sL11$Q~qDvR$5#E|~uW+N)FAl}@!yO+9x@)gpO+mUq|^g{m8; z8qA!4j?n;i2lISMtE>S@`69nMV?-Q8_9;p#7rUs22R2kR|Of)3xEXL8mcO3yKwZ&wd2lUqw4|<4sO< zm%dt0SD$Yeblu6rVPH^~PmJVmSmnnv%7(a-(b(bO6w{C$Qpk-h((IXJEeee%^Zsb^ zym<3tp5t8=!MsEfHyR~pPAuGdS7eY%@bDv#VOZrFQN+O8353rO<* zK$Q&TU@dx7=>fep{f(2$QFu~B*0vu%7&!02TJN=I}Oh$lL@ICj4Oo+0Z<{DWCtnX1rkCpE<1e`4ni;64FBg8bMc?8L7oL$qzBpY22$Zu6If#|L%2mFZ zhCpM>$D2x0riq~|9<%?qCfh=N7Oj-uS;;qVWv9=A`iw3<6rE6zajY8QoLTp~^lYGd zzOXMCz2qWv3PM7o$N|1ko>1C){c{#q8Act#mG4jQR8TRG!kT`DUtq{)GI{L)x^AAc zF|0{3vnwWlR)O}Z*R>LyODe9lZOFz{=+i7BQC68~A{X(s+yaZ8kyr~h;1tsXIDVm> z&NF%5^1C0yo;z9|CpKGr_lE9*y*Jtc(+%VYAAjeL1r1LQ%eW-;rV{}M)aRO?F-i5> zZ=WOCBk(|-E=&1|7aA}eqXyqvNd9qWTm?-omwmC|8q)CQ*Mc~y$5r*qkDj14GLSD_ z{3l}*SSvinL>X>~@piFzLD*6ESuXsv7!?p`){ruo$3Q`(tzT^MHH$t z)jP|FytF(c?|I*)KIr8$G+{W$`bs_Iym4WAD*2IyA9AuM#w!)9)0Jf(vA$(mX|nUE zAl2*LLatGE#~K;oAP>lv_fcpdU!VX68|G1gl=V-;M=a`e=PS>U`iu~u6d7Pq1(&V3N)Qo9JvzDiz=uT;T?b0HxH9rT|mCulz3^11U9oI19_JnuP{ts0}f3`J%f8(VyR;+0z&Y9npRWd<2g{JeKUoCDPWXGw^C_Q+G!G9o(DwjlT21!5KP*4DE4~rTJ zPAk8cdJXao*V&VW_|uJ`03{Q03U!T$tYSyJJn0%+tHLavRkx-){PG>EuFUa>pUekv zEv7rUqO{ZFoG5IDLP=uQVV+>uf6t~;nUjQ=dp+}33KY>#ST45Bi5z-=DMzIeLu9Ld zA92O_(J->&BbZP8a?bkn(`YZN{jx|9 zpQPQqSvLYjM}g-ed?Q#gsZ9f9zbatJ?CT2|bcs*06hHXqne9Ekk0 z%)jn(OEXsqO^GemrWiY3P5}5g=^t4b3$^CR(^2KvjE0RAd8f$ZI;OHY+UV6`=8bi< zme`UcI7}aUluA-=A%8qw`^W1|qk)uPVR0rwvq+a8&_MBc3wLC9KaM2L=&V7#PwPy3 z)N!4S>_k5R{r3!<6wa3N_=(*+|B9uF1;&#)&V)us1+Fgqw~=sKG-yCJ?n_|n?=6t} zQ**R5Sm*Vo(F=?G$|3v4)i|bt45x?rxE=d&E%j|)mfy%oMJaKSK5m~BsdxWuV(3v! zDbh#0v5|-xtJl8%LyKX^*S(T{+$u2QBBWxLuyJ4@`d{p0 zS~MU~89h_LbPvZi?Ul#Jf$5THb-+E9O}6LAr!HFHeUX(@LU~b%VLb)0pF|ZwNs`If z`mz{rKq>-@&Ldb@8oT}l7zrm3&jlWGoxflv;r6A2|Huf5H%oJIH0TY*K!lBff${Av^ z7?C(KcEWyL+5**u-pTa6dL4Wz7n^czJCcykMNjW{td|(PSpy#sL|dlbh?fmoO&d=0 z-KpnP!7_%g*hChAFlcgOd4G&j=G7Zbw}Ogh5v-wflRi6}VBWBZ0E z6+%i{BPHi;gs7FWcp*u}@R`%j&SS0dRC#W-$<+{+nZf3xBONKS@o(80=(VXBC5~E>hQP-$x>n|!o?uP63;*w{vV~%h-Z;v3Ou;UF~Cbsn21F1RXu{S zAAin2sxSf9ZU1;T!PFf4;Q7z~#mq0f?DHXatS8WruNnbimrz6a(4{VS|As5}FSxPA zr&B-|i**TCosG^{&rX<@9e6_rZE9!Im1Mq9qO&e~6JLFiNbtOQo(=yf(CJ})MHx(; z1GzfvK!n-8^NCS(iujVyysO>aaa^sXU#MQQoUnDzxa~!Ng<1VA=YaRF_{3u``kplY zMkVeCAa$N?`Nspl-d@&axHBLJoYzUxIl>`qa9}WWT42D^Iegm!Fepp|>SIZfXzrW@ zyK?Et51_}eFOqZU+X~lmJJ_(Uy5=VEfNuXkt|0-Kz4KWy=eKUIw3o{+3(z$&Jc&k+ zdOl3@*w>=lYB7GZ)4dVq;}p(1N?HDJ%+z2h*+#r+b#CwQ{^_(ad)nBUGi>BCmpckQ zpth0*Rm9UmI4H`%oE=Ex1h-7x4T&wID07(m-7&da_9m8BG1E*W-_EkOIxa`kR;O=b>-cM^+ttEXAKgz$yw5g^ zdU*Mj0?S5veXWE=6}3^PyY3zl{rtO-G(%Uld0or}|31usF{rqX`9~O4X91&Cx^LUV zLv|m@rEp)J0HZ<^kdU9ngg}GDmhZg|@zZ??|C*e;I*oVfv~QV+_v=xh9c*+P=O-Oat>|#83LK^3dOJ8N)(951T}~J?jT7Q^4Hh=7C)IbvDZ>F8$t z0B0=}V=z484IsHN@F!TQ6tVv5Nai|oq4X!|F^~*2(0#7jz4#;?$`fxoz*V;=H!n_k z_&q!Nr!27%M#3|Rtg&4q7@X}V+WVh@6IWQUH`>3fUX{J7N%zLBAFzrBT_D@b3@y-P z?V^6nKGUX-Rb@*id1ar|d`V(>?Sxy58T*{29~QN68bSgq8Y>QxPJ@2R=R!?;B)}iI z$Rqhr{Qm{V2Xjh2DYI)^;jDiV(}^y|!D~Hy73O48Vztq4M{A7^u$^*wRt=c4h`T;> zs)#l2@(O&SbxZ?Y+Y?bRSg?5X-k7FJ-%ct(;!||FzgUrYFzh#$rxXHU(FeB@PZ8>z zlh!x`B8YDZmm%I^N_*d}uB4|jxfV4#V`-DO9lfjkA_w~o zUdxPcyV-lOY?ljIXN9oG`VcJ~IJ)iUd4tq=CN?*1aNiMS+R&e1p`x_dWXhq;rg_o3 z36@zY*?c&;e24;NLmN-%p(}F_lm(z5z}z0_uY2kK#aVoysGRF&;D!W-o+Z)fy+AoR zGahS#sar3^?G!g0pGr{0#l*8dEJ5cA@QL|#S8ZXQ) z=ki&6e~~<=W2jpmMui)ogRy|ox6I%91jy3=yX{M}2QtfgAu&`LAb0GR@D?C-xlQ53 z>pj5i4=FD+MqZoA9))Vqki>tOJbSV7xkI@TA95O@f6vT0qAG<8MZreGk2pqMB*z63 z_wP8#QmHpSE#XA5YG6%b+cHH{Ac&_BE*+uyDD8r}3Y>BfId`$TgGP?+NOmE~l>gy&geM?#{tVjwMus)S;{aXOBPjvl$x8RX+1*)S>e!Fu^MezJ^7}(HBCKlZS z@!;0)171@Y5AH?A5V^9n3d+6*qG@-|dJcO=a@C7KMz0cD)+5?rqHovfZ$EEoeF^`PqUmluZ@5o{fxz8*o5B z<_wY@i^=%k(a4M}KHKRomSo6u(M1&Nm8l&#jas8n#An8=r;SsVZ5^OSLl3a>6w4KR z`l7sGAPuI+L{ubyk;s?Jn*z5dsdKPT!saWAPLnkeW~&T9pQVUSdaQN&xQ6fkUx}Gp zD8TSGT6n~rDJ}B=TwjulxPdhnms1{IpXlPh&S;c+lKD2W55f#i1?Pa9QjciSv8GU> zt9Fy*pLi15M%CT{HskFRve5)Kunj!L};%9+}RCJ{_sqpeK$sz zY5ex##_3ofBR+u~mzs`jjww}&Y3tE$>*mgS101$UFp0O*q}Sr-(3KwZ9DOyDXbGq( zGi8We_TE;vzfyBOq3)^`Y>|p~&v~G6Wz7U2dZu}tHdc5Cg-E-^eSG#`^X57L+60*; zzGWr07`8s*KBfHI6A=;wnW|?4$^X5lZ*R=c8-}Hn6AL;-bj{V3Xqd;94TEJ5#|6O2 z#D2Y@pjv~bSe%PS(jg`Un8;viDJJ}=B4uvEBz2v5X-DGq@&7K@{WP9KY2s!{ZIfgL z{QvO>77#ugNPUEs6A1>t>7EE8(u$Dsn440t;wI1O0VeW=Qo;HO7*5=2Nj0EMj#O$k zR^)m!(kA~q{YU0P&g=Lp`PE^~-53f2&f2=ueNAcy^Nf4)Q@k#84RWsLzeStlEM5te zY7g0=XO7Ef^F<}$V`+JYGioJ3FK&)#Jdj_ksU~#ZB|P{)X{(A`_7ys$y_2%i$PWqZ zyxCd4apW!T{YIl?BtMM#a_G0P5zLY^!r^A46udNnph0{;^E;{(P4QwgXLf_Hr1FWh z+uDE*Shvwch8nZYZKZ7A-Wb3}Elg!`>Z2(l9LhR_M>U`Xg7hq;EYOzv;dFYyEk4Gp z;QTcfACUkg!6os zzFQ<1S5X~IH1-3K<-CLK#140{6H-Xi6i9>NDFCV8mq)3jP#NmL{~>*RNWl@cb~f%} zfCO=(B>+W0y1ygK6*F?Q0|{wmPToAObsJ>MOQt$95F$d92VQ!sPICV zUw8Djf9a@RUPe~EsE(e?L#inYG9xvWfYd~?+RG~b{0E5ri_m+8lR11!0m9ok4{2N$ z4*kQK!Yt_YMzb@7xVSRons+qA7iXCS^3Z~rVOjB({qQoV*6(%Gi`+=$59RsxL~F4L z_VEG?3Mkz3DeEHCDlKQB`41D_ zS{IHu7x$Sna7a=*0yvri5g1+JH#2Z2J%5&{XYV!ql?)%R&2ia33{ zS2k$1X5+;v&ppF_Y_>*2%UaZ3zM0Ems;NugbILH(7i$V0^=yaE<~1~PIKmm{R&H}O zPUg#bBg`8}Vs~5QW)cmqS6yl_p8S6mXy3_B2xnWAt7u43I?i zyRa=uI;PQ6!6FA&=eYHya`9d0wl)b)TZCqNQ3w#&Cy}TD#hVv?*;7O-JussA29d_| z2V8YZ12AYPz8J_0Gf1c#wQIYt9gZp+2xuEE%ry?tbcl5S8B{h&e4dL+wQL_22J+J| zj<-L@dR|saimb`@3W1v}bf4wowTqf%zZY%dHjSlOj{LLcvlN0DXrYfY3r4g3KooB< z@a?`K%OwsGI-wTIv9Ow&b;kX|7v>#qTto+|lSZ|^(e5^*Ptubp#4Am(XP>U!vK&{V zevN;pzLf}0tX0FJr8xs#r1AG4%OPj6=SO~%9O$4=y{LpbFAqq)dBxXkO?;#W2``Nr z4vFW#1sP|pq0G*cT8O{MIv)K?+W<>X^(s@fBzaFRQfk@zJ$HcFG%VimhKYO{pJ)10=S~YXB))Bv@h=ShF-)M0@V0 zFRf+!ykd;F;8t@6!THO>t)LZGS~3j1Rrj_ba@@0zQ#PXN%N zHVMl1e2pVR?4_CPN{BrQyN2{!+l5w^M1*K=b|a+x>IS@=2_I%h0w9wS{grjpR#j!ZGD ztE~DUrAxL@j~C73w}F~H)GGV}mfwWWn*jgB7`g3e_dA&-wVE^N@zk;f)#S29*A#1< zpq@-@p-7@+#gbnSL{IhV>qXp2mFsgG0pE2Fuaav9s!{0OY*#Gte%R`@nGX}gr|g5? zjjmDYNDgz^CC9#rD{Zx0+!CVlJMZ7-C@PsgdEV&0MWEEu`N~Sjmv$hKY(YxS_s_eX zoG?-v>(K{GLrOY5C9?DtLgX}LTBS&UEN?UwT(ztbJX2%tM_8?1n80ms)d3*C5T6rNbtxe6)r^ zR>fp1tfz_@a@3APC-2-6+OKCYL|5?S#3d?Q7}BSNW3+}aDQKO7k?hB&ro$t41h64#q8v6{zXx7gImWCpfiFq3$Ih!^#Rd84EL%>mR5*S?UkYv6X zivn67?$M|=TV-7b=SA53ULPS8i(*f>8@pKsl*!*g7D|i%v28BE`I^4jN+|r@kVlh> z^I+KtKkeIUrCJQQZF55xMFQ-?fBZ0OI-z++%{TgA#KA*Ge*a?{>>ycd6yd~lpUjdR z9+I-e%p(Ul$2Xl<5p@h?)`{u86HFmSh8p**R0Ut0Bmox3DGQt>9IOJ)-iKR}v>>UD ze!KIf_X*3J=E(|qy>#e#3Noboqp(1`#L&zCeSfC!~&J-)p{{e8oRAfY6NiY|rtHlOB$A$$X z-#QVjLz8p=${s->trZ_hAX9`0m(s2jlQ6unte&J)n<*>+c!h& z@@~E3c#mu(Fh@V5vM1Prc#wut^YNBGn!EB^;ZvU! zV^%n;_jCvkP945@eBB#a+dw)X!0d>O@S(%7=p0E7P6iBL4|f(Nl>$dlJ~p%DS{A!h z0L>Nc+%}9}fHm616LA7cY0<65DGA9^Z1uW;WsSDoGJ4BxNAwF^+0F>qFH*var@_y4 zt)Cx@Uf+ebkyFh=zPiWODx1F72Yy`Oaqny@TQ2y7yl3y|7f>!}C!Q`mnBBgTl3&J( z(Exm>_zQU}al|=8#He17>{p9W66|ji%q7|hHDy};y%gj_=Wo8-ck zD+fW~b(tkNr`vE-AQ5Y6<2Czhg@t2tdsD@Cl#KyaI~MlA6{fIa=?4R3(^^|H?L0KT z->2+5*mV>*`bN7sE>yagw@zY6qN52woiz_T2%nR#0#h(5A}PAH?c@3!n$X`|T~0>W zMK%G4$x@GsL}XYi1~s|Un9+e^RAmdMwfjg`@ zAEabs!B!h6@Deb7M;x!FMlS9*i&7G4YtH<4%+P-&(V~l93RZ10o6rBPYHOH8S&3u8 zRwNS$$uOy@>AS=eNk$b)V*La}3dfkx zm&Gc(l5u|}X`v&p7hf*yca9c3$C+fI4V4DPCn^c-&kRJ_(c0p(vQWE^ufnK8foneS zTtss5#ZWn&QT@}ix(0?gRLejg3ZrB($eAY~6sX3s;oy!>qN{ELZjqP+e|^ z-f;Pqp=4fI2NY=L^pCx)b2HQOJm!V(a98@6WR*jpb`5xDh3Y%Y-VwgILo=eq$YtZ6 z_Wrk#_ivT`_I-TvAL7o_+EiHU=y-9$Up8nP-ZhMtXrPaO{Hq1&^VcTPFV)x}2krPV zf1M|S$Do?I*DxB;P(WxU1dBGZz0hEkZcPS*ulM$benz@7qZ*)CT%Y|A6&)f#4S)gwfx5R;Cya^W`j&*3 zys~7d=Dit{i?yNOL=OZFF@OdBG3c9fw`R|PW3nwKImPM4-oRB5aWeN6h%H2HHtjV^ z>Y>t7=ky|33wy#L*$Cp&y=CVFrpWMB`AIY zJFRUyU{+xYA=y!VVVV)h8p|&e8A8T~0ifvGL0iJ8P&DgI=C0raSf1uZIbuN+bEQYJ zuCm}|tzsR>?=!B*0LHW@`0-;s`gq$o3`@yV3#|esVak!7Ha71^dm|@dR|x>2mL@D! zB|`$Q!IPDnnDy$}yb^rdPx^+O5c*RA#W5GLgSA&MqZwPGO!c4trOHS!f8B{ow-zRB2tIA< zYGHoLro=^I0 zW}2i?C5cu2|h8dk}_#qT`HAt!g!`p3QdW;G_-Vy!psO82sJWDm` zrHr0rGtc%Z-@%~YDk_(zkeT+`p_;Z8knwuPNJpw35t&(1p77MAXhUjn=&I;~i0ICF z&eo1${}q(92~+X2){7Rm0v`SiM^iTJ|EMZi83%Xu?=_X4LvfH z;QST`2v#IfB|vvh6UMWTMHhB;1%-|6+DKhfG;oqKDa#*Ew-Y(3ZR4-^;^(JB)E_FCGC(3z~X1`4lJjGwdUee~HD3W07e;`^6YWs%=tt&dCf zik110Ywi-SfM%9qkF#?%BDAw8d^*cG3e))+fA-tuq}x#iQxf6op&MolL3rMj`hThI zJ@kSLPI4!&k~pW0%sO z;%;Wv8pGx`P+0wt`Uy`=c9kLx^Ic3NGvDu0`B{R zS```l=HFm%Qwar_LSl#j@5eiHLg6eFy)C2*YL9HqOc3Sbo(nP@G1jRh!IWsxet?=? zCb}&>1Zba3cQGJO8O8lkJ$OV!ra_is?}Vq7}rJA(0$$ z2hXbE)O5?q6L?ZU0EXy)psfFvcn=tnc&a#*;lUT^~ z&UWQ0d4}ifKS+=%^8(hygw-m=r`ymvT--OOkvgspz9x&=2h1|cjc2UWGHMVXBE$;4 z^r7Dz;Kp?I(A=nsWeU5ugI8-cbMgGo8$3Hv0n8IK}V*DBeKxAVy}cA>ZZzlW5qUUOSzeux`0J` z%xj$8HJqBwvYU4**Y)_-(x!TH!K;B-v(SrXlXLEtoYeIYHf{A@K+65KgM290A|XPI z<#V%&Lr$x!-Pol0+I@Vb+d~}>ZWA`)(?^!U27@extl8g17sS*=7}PoV6dk!23k2dR zkNuvYSsv!rb9H;j>y;kj1T>{T_8cikS=ggyLam#Q$2$a+ay?)o+zq}J1U*ip{nJGF zU1;Z{hh+^NQuenVIb<*r%kKaOo#>FZS%H*c(3775`@N*pVW`La)(ZpiT$y9#S>2OL zU5YiUgi$cG+w^_Iot|09e*uB2d0I$*%Qz9ahL0G)U!@7~DndxsByV>Fyy`+xFVHGPUEU(3=+14Z19-K66liisAPLkk$=w& zrGDMt2Um=mTOPFP&%MekURHG1Ej?81L4dMII4^lB1+QHwjR>CvlJ2^XTJ!U144jki ze3>;qmZ*ihKzAgQp=ta-4I^UIxm!9fAn~3{Ix_S81DtftRzoBAJ8Di;t#B_Hz7u3< z1wwknK=KNDz6R11_2S}y>DY8{_*(%49JVSTM0~4tP{gbTIN0B2H}@3v3(67t2}XRh zK69Z81wMg)xGoF5Dm)6#yQ~UhK!M^?U6%sPVoIhzl}inp>V5p=v_tex#iA zfMG9?=Wddq`E`R#EYMuQ9Ku-9Er}%q);~EzMGhSlCk4rew6HyR1JC}#e5)K~y zz^J0KT^(VYvcdB+J2dM_mkmf#YKByLvmjdvmZvZ`=7N7=XX1(@)uU(9pvkQfF^u~S zU=ZLcR4mX0NwdfC`X=}uQ{k{c#+jUyr12RMQS$xrmL;lxh{enx;~-8RGS<=j54+rp z>m3}1``F#pAEWEzX&j`xc?wGN?*H13BA$~G2ecIT9U?M#<0hN_5?ah8^-z5q;t4`e z|D!OWttyyv5%OMs-2ChBoq-$n+B((#kg=F<|IP{x3npR;$d`2BiHie4jXwB7g zeaE;Lnz>it7u?h**bv;T@A;P&G)UigS~8B^r+(0pf;qz6C~+TpEuzw?P^1~*MNX{} zv1rKK8`TbBw$jP5Nmy?V9K^E9${QdS-AtA&U|sR?cDHig?-*GwZQm%K3xYc>$tz=j z1=Q7$bWW~X*j$mQC=fzWr#hW=#8h|#E)8kRZZ(tgHl^`4Hd23SutsTC z}zjgXm1ocdr9OLevW-1WLI%>A|5;2v)c4Kj${BZwv)8b{VM;Ct>^uy%VH3+Rl2ClcCz!=vMq4jB6x zOrT-v*&o2(%i~4^Cn3bYqax#J5)kaZ6*)ayHmre2KjY6fpeGVG$8>?!#q@pWayVP( zs`!RXed@>i89Br}{%D6Wh*7tJndVZX%}h zKd$Ga!*MFNDH`_%L3ApsV?`xLY9{~^%a{>5U{@Ao>%>MimO^0-IkDJ$%<_5RG~Y}P zSsg!C|89#*=)+!h%}ME@6`U}-KjA2hz6gFm*Q6vJ=c_#aU+8S#Y@4#zJ^lZ^=8e*a=A;abl=a*|3iG7BvT+9zB9&}h!xWg}{#mqXcrdLsrBOTUr=XU4sz%t7V(=<0I6fQGKutj;Q3ixYp1iRNKZvP|lxr2mPPa5qkt%_22*FPnAAM^;kOj*SC>aDL70U z;skAIT|V`)5;9M?ofuBBj8dB7(jYS79srkQN@1CXB)2od_lZu%+UxCXaaUh`#7ZF) z_>up^UGIlMexEI){j6!9TtYjNnk%>_F)kD3fOqL#E$P3c!waa;x0S{zEm&Ui{wCI$ znb35!h`(qt6S&qpZj_A**!hyB9Ef~^6|Tadk9#>B36C20rsCd;bsgP2_;nbk2NSjS z^@ZzN-v9aW3g~XWShnUkornR(#xy?gY24S8ZLxEkHaFjET-EhJwO4@(6H`wYydaI5 zV9n<~7%CX9;ix0YUA*VC>fhrpY&G^5Pxkm@#k6Q5ZVa9sjs0;u{hg~gQ7rh)>gCn| z6vYE6?lt%om~L4Y`-i1W^JN+dq`s^=Q`I?Q6MPFya8>uZ<`1WN^&9D)y4a_L1`~G} zlQ_ZqWhl(+-u*R+QFe)j_0w6i5@gxQ=sqwFV*@-69G!8Z?&v<)$=7zN)xx9fC73tu z1Rkri*1;)LOTTWB4+Br^xMBpbQ6TTQAOX)Kxk}Qo@ASP84V`?oRg{&WQG`IkMgxI7 z)vGFiW&#C3lRQ{b+>?Gogpg*lib>!X82t#h&QlY_$@ftwyT8*#_{)_L+&aJ-0$RD| z{ya!1`dIonpfH%d!DOHb9N#Xvrg0TVWR|cpq*2ZX&`R1jT5;Y+tNfSR;%W9IkT!qt z;T5r~%oZr#i|v0URGHUTTDqk%n0~>|5yzwlq+-*c%CHGlo?V2V+_V*|3kq(is~l+Z zruV9yz4J7-%{|0WZLC^TlA@>Q&<$SkB#4?bl#VDUcop0wx%VWD!ohcZ{G+UME~6szRpVm1m%q9e9Mk&)yh z=?0cI?4ZLr?5hGBdkR6CqK^|xlJ1OTsTk9^FGqZNAnsyEqaFek_uAhRv%R;LyBLDg zqT-7nsoNeE68qzm3QtpJ21dx&Nj3tSj~Uh-Ra8(83kIwxxn*wm6}IjV{%}e*fFj@R z27=+>aX9ywd`o$-Z5-%L9UyJEB%{s+t3qZ*`rX32J5{Q^tlZH8M_^rL>=@ zAS43498W5gsGL3 z=25R@%6c9w3J$5cvVFh;rv>ngCEDhdq6W8iG#1BT9dZJSa2k;u1Meal|);!(1;)YA&M zO}W@}UIhc;Q66ebO&<{WG~fU7p*QLprB&P+7|IHDc6z#WV=Ke^+M%OJB%Y^dyhd2E zT!EB2prIZ>T+QJZfV9O?C=yAEB<#K}lf`C-$B7!@ju} z#fxr2W~-b$E+{~low__lc>gx-R<8=cJ0S7A>hHZ-8hy$|ys}%$hx!1Cs?W_q&6wwQ z9Jmu6MjgV@Jp8;BQUz614u6!W`hlYLL}Vk;m)UITEDn7fNYwr6t zDtI?j&4cD5`hzBG8pDd&Wr+z7;2DkNKt9o5RYB*iVQ1!Frq@kS;#v}JlrJLs*@y~e zEJTJ%c^>{073K4oV}jvOEV7sG1Q3;UgHRwkm`m?+BDrR!nw^Ko|E0PlR`F*bL6ejs zAru@Y;3TbwvJ4cdyC}o6veVW&fuw2p;JK>M9^U==C-9~=;tlC}T8I7KVHI&&$8AoT zRfXb;)9Kpu0NwkOc(%URBu|s;cyRoppf;+>WeQ9iu^m6U3a}~;S^twbRc%cwXeTqA z0Q&fsZdB~i_tlT?BemXSVwWm~HoHelbXw$)zH~rl_*ESoRUtMPDoU-ZRW5S0E1=A8 z0q#ui^ZWYwZvYfb?pVVj z{cdKy(DveCF%MkPU+z5P2Zqgjt|Q*YNGSp@KjEieK{*r_(uDFQWAgIM_e)E0KesFM z2yMXiHfHjvLgSjLe@_i+#yJgp=rA*nel_xZO}4ex8vP19-i8}3u# zzKU^&$y>On_uf{G2K!N}2Q4Dvp4}%rG#jmlFrP|pwV(|M#Gtm6%urs z5o|lY?0B?)xbWM*H}ObHXRd-Hh%SjY)2Y1pAqp6EaJ`fQFmMm_%A4(q)14NqO2C?N z+DdXb-04!isbd)pe8ok^<-_gsz!9j;BEXxcIb%nu$mMB@+F?j%7{_~G8aa?S_*0S)kh}Bln_+3kxujbs&l=q;F4bU z2+ZgptKXntxA|Xv=8?_(E-qb`8J%Hzd{(8cI^fK_a_)zdl^p(Tc9vLz@Gf7c5p6+B zm4Xp&QRMd8h(#)w1YpA@_gI;&bD6$(E8Zs*X1Xc6yt=(R&1g4}T*WWtpANR`v(b>F z?_V#K@=m(s6+$Ky%&{-vu0^P-oPG}WGN5_K9?@c!)6LD*F8e22&QYyF%>;LfA3RNp9Q!d&_^V830YLhfUWlFA*hgD*QyL-1 zRz+9Rf`}B}TC;BUvJ-&9)!q|x2HKxCSx_w&X@{2bavIMvZ_VQB#Kpswal#=xjjXc< zh7*ckQnHvCY_R31eD&gq!#l-P#slnMLV}2wy{xU;d$O^mf}28&s>xZBF0kv#m6b5nHNIRZCKdtigjtOl%?oT;W`^ zeG4egtU3r~Nzn7{nQ?aJx7XPc1!_#dowa+Gz2e8MWLMY^%E1-ppfpkxm#e+8P19d^ zFgj;?-M$=sdT0Qrp&*Q&78;qrHzOOsoFrGaR!L>s7e0Ku??Dy9Q_=rxBtg`jG5$fq z*bS2K8*YG*%CNu!%Bc}qA@#nKjK7+<{kCM0-25)=4iy(*YlfQU9!AY~+=CKSkL< zD70Xynnmjo#$K)XUBMH%7zTs?@J~^4iH7Xd@W0HD8=3QutnB$)m_k z)m&=eJj#GwH)=x7!}>uU4hMhmSnc{?Y)4o9>a(i2$&$S3m8a3_Yt7`aON;31X$Hj6Uo$o-0AP75$etX%CHL)kS~|xpDtk+JsVCGySUm|v6J7-~^+!p%@7}(b zmh7RVN4_vl{gw{lKYf{l{D~s)`zJ{gI4VRiont)4X0Jg)djM!4lDH1u7!jq_@x@MN zrGEwrfE_!ZW*=RRXtotMJmJbD1y*4rt@(YGnu-bniF3(mOosbARN6Am$$k6Pl2S87 zjCdg2L2RGbd$wi~D z-2RJpv=jVSiAJQ?A3p2Gq4ums{U^%1lmYX=xziP+$Qx8z4DB#q|B$!I4BW|F!@L?S z#k^|PDnaXuAkt-2x*}AM#bO?gebAxPmKxx`xO;;>@pim}usBNDyA9JkRLOzBc|)w$4BbqK2WrO; zq8Gvz^Xz;Tf*;^jI0~^p=bykJ%PX6(Aci&ReOFnqE=8l?ssrjJhi zkKPx!{aT*h!ORi0PjL!w4)-7yoILz%b=h-g9s=DsQ2U_I+4y!9+a^5EFLsZ^dr-vL z(8C+|3z-RrZqXmsMc^oqZv>a8h_S<8A`dz*Z(C4a-N}7LWG!k&;X*((PeHKG@ud5WtQz)k&#BkmP=QIMCQ`VsN-FTd#*=ZXURNFyYmrxCo zQZNK%uUzW-i?y}JfR;lhwshT^OY`?@Ndr%hBkEkuaWgl{3}N8cwy<~b_?rM$ONX5sfrG?=pqH-^H2iOvqz(0|>b_PwWS5%r|&LE*Z+^HkEPSb0cQ+cr&kt#k6Y# zTcI_-Zc>bzPz5)!QP0$7eY7L-dqQ%L0nF=B?FfQA5y(QeGK{s|6qD9khr&3i)M>#U zWIeoQ5is5aEEuQHWDkq!M_q#GAN;88)-p&=U!70He9c~+Ad`VvF12)nt{kVM-vr{f z_(GE$ez@U zxJQ_OujqStivK-RYkgJHTY+)G`N9u@Vg`atXO4MDPGSG}r=0+4lJuP56-u4pha_TK z{$dkvCL`JmaPnnmeKbVU@J5AlD6V#~OaKT)M=rl<@Is`X9#kTaA^3CZMG}C72p<LKTC^N?WgRD1bt(mtz(AMpa{Du!@%WCxxNE?)b#Q0484j#Wh`#T1_;PG|;P1uobb z%lKHz9q_}H{-w6-($zATU1ZJ}F+$CAT&LGNPuk^7H)$F1wnyx5sT{pgADrt*kYwIY zU1fvbe3LPK5s@~d?EP7vg#L-b?a(Ud^;}M}&goyvvNd7Ru>=aAGy(91 z9ZV60j2zb7fg@<(j zc0P3W69l$qumwNV69dqs(~8xO4MY%!Z^WDS^1mTxek#+q09c2?rke5{cL}oo+!evs z4Jn8aRD%go@vuO12#9SHc6)MR-!q!4I5tbMt zQT*I-SxUViRnm6{slLoMT}AK7;UBH41G^J4*c}rEXAxzC5yQ1b2(+6kGKVFVae^4( z0`5DQ-VkuRit)tQVRpf@D%(0``^@v;Q7M04FbF8$f&5Z(prq-~dz66>vYQmO+RRFQ zyFWTs=O}Ywy+f6n=SBMaJJjqX#Q_PN3zF_6=jVfn&T1>j^pgps?lla(d6%s}v2%>V zGt?y^0bu987#4xhDN&V&R9~gtvI+;49P*G*+d|;|RgJR7ta3~dHI#O-#2WhtY36_h zyiIz>65h9;=Jg&UMqtF_&>&>9soEExgIZr#5>BO&_6vsS?V)Iq$@N#}%WzD?RLk{x z-%_x*m6CZw6=puJQRyFhbykq=im`_cI`GFi-Y4x`R%|!s8%;@>MP!G!@RLrrunTQ? z>O|u1hp;0_E-n7<(;r5~uLk`W1`-6~ZCy|k-*Ambu~_-aMz!|N+Uj(3VTDEA7T~29 zM&VB@tRe4pJbHCg^@+{+6)Z)Z9y;m7jyc^S0i&yL_s{3ziQ+L0`>bEiLQHbE&(<+$ z`t@~~IA!NR+yNuS3Ay1l`!I|-BxkQ}_ujVJnjpX8;fhxVYrf3VERc6qz0*M5Go~`1 z+N7JDxw2Ey1M=P()8t!nh2i4?p;R@@^vDjVok#^+8Gs}_k?)B)QQOQ%c?!Dl>nxi+ zw>iL;ecX!H2;%PJj9PPj9GItNl8UfpT;^D-E z;9?gjMW_SXzfzPAC(g{HMZAXO{8RSEEC;!n2=@7)QPge0#R~4qR+pM%7E=#ZV(C@R zWf_`V@cdPID6yQ`DgN(rRi`Z*wxmBw5%Q@GV94H;qqd0RWs8b0s~cMf5)S?P_H_rM z+Hz*i#h`}?|KW*i+uJHxO06Z~<6haBrim+{Dxbo_6Oy9iGZ}sNAa%G>MJ`2W{l*eN z4iqpsVR#w-Ptt`6R3GG~?j4v^uc2H|tZi0dNm{a@*Au_*{|9GP8Ajw-0z5 zHlsIaPn@M8)->&%hQPr|jqKcE(*9NGTOZ)JGiH)VirrZ&ihrP{v(aUXvS9{>7iMn^ zp4S4mM};{MN&$=@ieF5Nl3Bn)Z4Dok?;!(c))$**y4n6MO3|@$PdhxOX-1ZBgm2oF zXG&bB{+R-{r*ZEU!m}m%F|8m$L+Z*@qh7YW`OS3Z$xtZJvw1y(p0^(&p2j<+%J2kc zx()Cb^|^=n)}>q*2|)2fzRj_vfK}UfIqA(HnnJ32DJx@=A^q6>Ar5GnpJB6mD=X2& zxs`c-X~%lv&~$=<+ll41!mR&95GC!ihyi=HGVy7aR1QFsd_lwX6N5(RZr7m&&mFGY z@*?NA@>66tidPjUT~S_+ax<^}8Z)VQvbiCT$!n0RA?dI)y`KrPTyx~!ep&{@ z$m>gf);=sLXAO6>O3r3L9*gm$ejW7f?SH=Vp-2=!$ra{u(MANWpPDs}yh6ca?Paa0 z1(y=Z#J@@DgrQY@;~{NNGl%=7$i48U>x=V6Cj|%QPC!gEPdzy5-3dX=!z&)*PK#dW zPo6rNcb4}8_`eb&p3$_;j_|_0-WmfAjMPlLFV45HM!o&)YonyYd^vB*&^+;3s1xC6 zh1e}C)7ak@T;cBYj|o~zdFsx`Z$nv5zZHx2nuqj1&ad3j6~5D+qC=NgjJ9f^o)XgS z^7&w{AKfF(?U5nT%e(#XTdU@VO9`7-ij)gc)o- zu{+{4YYm!0)X|t~hLz8$(!JR6-z1N0dDx>W-RDK@_-a7*E3M;HH?v_;(AvRmxHzcmx>Gn!EqyOw%_Z_~{MdP4_aR^m}48n;?l}qNgaY_Q0a$)y?@K>pDKk<9=DIqZ!zYi&T5m^7Xf`v(b z_VW>B=4}E!tjb)%xJWJJjiv2l6W{mxW94SLmupLa6wkpe zr^xl7F?1OA1zm*fkiP$<4(ZIF=uU-Yq0W!D$@e_7lY6X$+3%2c>S>;;l$U8~1k~QL zfFz-Ek`34Zn0KsZ{Q1#s)?4=)B5>s09v&0w>ItJh^{OuYxGc}Ge0~D8_I=-nuyIb4 zd=zbAf0u8@gi5Q^jw?hmn!03R8!3v}K~`~n?C?g%I#L8Ao)>ZuKi%q5SXj>++R|7K z>4fffbe{xvXSnW9|HxyZ^jra6|LvVvW5d<)KMEUCBQTFY`BazLWC zs-fbWv5#&VfX}CDi`FR*bbd<@{3eC4N^QqBs(yN=6MVE(o;T!2SScJ6b1fX4WXMjZT)8Hw2@M~R*TjQ6MtkLFUGB(=|6wSBUqeF-oi1zAW%-{SURSU@ak|_7 zOrOz#QSx(L#1h3&$Wme#sBBjc>&iN3<2E}2$BRGOc9MS(WLE?WtP6i1nra>p0qP(D zD*TfpTv(F2vYkwxie7!L4NB*Y5yWQN+x101#P;hY_cHxo3F_{N?Hyb^&W9B#HQMoj zO2s;9TQ76F-6o=y!+Cts`X#pvQTyWfSjbvNz4Dy2Z84+t_TK61cs>#agv-~;zdttL zfCiWTdgR*Kap585Z9)}*v0E$TO&`VjL6-Qq42inSE4H5O@|1|x=$4Q%bzu-^=Btp$ zF+|nfk|Cd;;n;GOt}lR5g2Ft^w7+qc*KaXxg~_YjYQ)s4pH#ucv>;u7f8J)X)@~K7 z&@O$tI1S>ig!psl4l8kqPV6eshm~i_SG|f%8Wk6Y9c}Y|Tt$hmt<;Gr%#2?u^QxQn zywcfdZpF+yodX12Wn?7u`VEb{IK5`S!}hur&?+I&o&<3(X? zh}|zjs?@s>?wQJhZHJQw|Iw|MPxodNAa8q1|Y0-Q~0S)4@Vn z&gj@oguqS*){fl##19zqHsI5er@9J??Y@)G8?^K-s^Q$)`wayvK+)pRdmZkqAAp_J zRV^E$vMDY$MgUTc7u?&0nFFN&H+FpWZt~^MGHUYlz7YzFv6_a*lp}5dcUM2svP>AG z|K!$u*pWaQF9PjjeKDb}tv`|bcmhefh&1_o8^zLCuQnmgR2hh^HD2GJ_h^Az-b*ix z8*4~{qiZtEQ$*HOl-DG&8j*b2`-7Y0m`p0X^T%F#%lPSfTzTS)d@tiu^ew=#NjahE zlGhpSLNtFb{;gEh*JmJl-YU|w24Q`>^#z=Yv}{872-%IvvM3Ztq(GBKwjOV{T!~bmBWg7@R1VV2KgaDx=lmMak-UEadS^|XLJE6BvYeVwhJMZ^D@6#Sx zBk9haJGaf9J6dU4hi>JAp{o_#;~jlo9^xYIPq)F1@2&4R5qVi}G{}u;MDynO{r8Tn zsz=p*-)ZyHZeLt3*Xzm*^WLJ)b6Ph1W^rrhvMR%t|G4z$E_*vZLf4##^&Sw|Jg5mi z*X(NG2^5W8%=bTFSD3yHQZJa+tdC`tsQ5VTj74Vs8YSgd`=&mP^Mz50A`*o3)tESE+Yx{R*4a`=ERzdnFYS@g)+=|zUZ zmy7QiJV!c|U;Br9{7LFuePfF+8;!h}xm|SVG~FrHbktepY2}IU3&zOOeH#Dp=GDwo zqc*dIU(H9~e=lgbeSEdWR~2(#K*>JKn)j<&sjy1-Z@xRTxzgJ~y?QkoIg8jip!<>7 zHs4@ksC#gy=*(>QFBH1^O|(BJp=*MptZwYV>%JWK*{|o$#42Ah-0OOt^XH?ZQxvBXL2ksf2Jh34;df$e2y%tyBqC1$u?>v6G?mpFimiX+#vX2%? zA3hy5wq?DuKXhRJcVu+?jnI`UZ;{)_0P1w*Q=i@GHJ0K=mK&_cT&Henj=q@`E-C*N z_sW>7W7brzaAd`}qg$39H8(qbyBV+J%pYo%@3FIL)LZ`T!eG%F|Lcke`*YHFs8LPE z!nIZ7r@z}bbaZfAo_HeCZE@y`KuRi=YNEgs7Jj3OjU3_U%kG`H3 zm&QA9*L<4EY##f^>nk&()yD6rtB>15{{5$~6RzuW5XJ)@1-08rvq20`pzp8ZY`g57#^S2!7 zUT?=wOMmRTXxNsa@>St^o5vR|8MS{K7K`tmY%%rF714L`Ar&7sI&XAhW$&w= ztgJiESbq+EXKKYp(_VfiYu34Jt%s4sdCBLjcRxDH@89}cjRQS{1A4YN$zQjd6m_f$ z+$#>$-qzen;rrd$)^N zf`Yc3*QY9;pJqEWtnH9GyVo7~?EIqQx9`zAJr8`|vewL^TGyUOKYQO4XwyJ$fX`Kn zcMe_sw(d`1APP?$IPpk&Ll>hYd*h0+c$@+_29K? z-|t4{Pb*u?8!i6DwW}?j+c=Z`rua}JZ3FP(qwN=sS5A!uaqfT?Dti(?`oIz67pYd9Q|NQ zv{u&#lQmzWFM6yUKJ^&(J|)fETyLC{UNxx>dHAi)m=v&y3}RK z80b-}%Eun{y1f6M<>@aM?#2GTFrl{JhiZ!QS2((`;Kf35{q#~YV)TlxG6<4tEW9m_3;L*dGbANmJm5n{}!o-!% zGV_{r{GRv`fNHC*pE!8D!n4#)*4}=D>u2o{w)i^!{A_&ONzaMzZV&6*W*a?oQ?Ka{ zPZg|~TNH`*o;LMSliHk-`;NYCs;hizRFh>p7gq>z3hh&iXZ|wQeVJQl<3zUTY?tVg znFOCYvYo^?wH*4zx)p!bF}x_VEIiSac-iLh`1=mQ<7IeP9l)yl<+pnN8={|LhQ9pE zp6_N~vwLoMf}ySpHq{YsYQ5R5BJ2D{+Jl$khWg zsV=R)ySkP&>GF-YE&87rU!zXz-M=k*`Wx8s)LBcJgO!i(8S$oj{P4t=;YmwgZIx+0r^+UIMY%Ds<`6b+YVV%3HIyNvJZaD77z?s{Z^BYEM zJ_y~Ih0U3MXL4+NWXD=Jwfl#Mf4mtuu}1a2i(hrW=!-^4I75 zqV~_u6i=x)F?r>6;gUhSGmGMq!`aGHRu7w&Au`E75Hp?u-W z=(Yxp%s+feUU=8DXKK^8&Q9FjjaprAwSK@C@H^LzZSa8TE&Th8I$Y1U?)YDk8&5ya zj{mvyCtnSFHFeuh-PRi4^<2z+-Vu%Wx^Uq4C4r9o$~)GS@7(np;Xz~LMo*hj;;y-s!zovTsx$`;L`9^!f1rd62y`I>E#p}hOYel%>m)bVQL#$!;)*CUS} z?kkXtp1$+Qv$4!aOx;)RS0U;gZnpgD=Dp?PpQno{7LyFT_kuO;kFl%e( zQdTo!$CAen&kl0-y5lQ3_s8~^=k|^mb%;H?|LwY_@jsh~vv+NLQ>K>qZi+MZ%&eE2 z2bJ&f*XG!BLDAGTvk$eJy!vssedCk8w5uxAwM-xX{hBG|Z>O;nb0t zQ@6!VbI$iVRrzb%Z1iPM{r4?xi#@&rjRLQ*E~Il!_e8ef#nZMs_W!(M(I-DVxs@2w zId*D(!&-mt4y7uu#ugQ~Z5{7#6+b)P;rX1gllOF-E&cYWt@&B8yVaRHJF&fA`md$! z@1xDiwwdG5H}2CfT5-t6=4;L!f3xJ}iM|D2CkK2ABnx}|vRyxAFZ@SO_qQZeZK1*rJouomV^zg+#}4`jgL{d0%jIP?nps>8r?EefU=Am05Aw(LAH$>LwD z?R)-snC|NL6`l`D>)-A9{gZ>$emr?)r{|lQOFFFVJYnLUW*dr6&HZb~AGT||i?%&~ z{pWlgyTyXD3HbnVr{Ch;-{^nq$$N45V28SojyDpIt<&i8Axnk+1K&5XFRD54z^ex9 zTeWI>s8*;MY$w}(A6?qGKl{OA-yikv_MYEnS2uSxV(TR-mhq42{KPSDUTWpsy3G{} zr{8MWbSCSEUk?^vTwnKQmDW&;k+XND2C&}`z4h|#cYOqhE`qur4F^tXYn&Q?zDN0C ziw1i-eYflQZe^S2o1tS|c-oz=?(mkbkKd2}X{vXGZ}XV7Wc|Xd+IdSzaA zLB&ct~!{)QA+b#Y1_~43*sl&YaG=vbYzLyqHFdHuqF7B{E zP-fQ9m76+Jz=1OMMGbpViAfJJ@7#J7Zr$9S{tGVQw>%+Iw=E}Kw&3OVmur99uqV=O z#V+7$#$p>z#d-mnDsTIGP zf1+XW0cigF9kXpmewkdUT=LwtcLsUkn;mlpwoojt__gxX;kK-An$EaSeM+C(97uh6 z?d}TRGSNHKe&AKM=$7X3f?Uf$2Ub4#q{>lR%8QZq#*17Lx@`s|qH;YOJUiqw5zmQWf0dLe*j=ypB zYJF*gbw-7Y)3M#yFDF;u{x(=);gOMFZfp8!x19-*W!<=0l|NZ`!gsQ1^Cv#Rn{$0u zKDJ4&cYl^_*r0d3*Rux4u6%OTiJWFKFgroYe`a5T;-@DkUP8k-r|=Q-&I?d%2VKW^BXOU*?xVFW2nvyK$+N9xCkI_}Ds@ zegDbnlbAepV|f+~{_x9ZZHwILvmG%K00bF#g8&0l3#*D8vR*9$fn z6{XpgjyL|?(Z50^<=0ybi!I!ucWY#}IyIlx5uV$- z>usIZveD&_t*JYFUAZ+|euDz9KN}w2KeF)V>ES)9%B94iN>`(scb4mKKZo91^>EMq zDejg=_V9VHZ}qj@?$jXg^ltevN1IU9AI)yRYuz$!)u~H#&bwFY!h@gh?Is>J?;z`H zr1AVrrD@3;OLyuwjAD-z13%UL`iMNLsgSxpw!`*4D(lI$^qWGM(^S%WFn8`Dd(lrX zIrcg$=lvd9ZgR1oe7@{A*XUIXYA!Ua9W!NQ&-c~aEUt2;`?MdQby%=@1oUwA)rM>9 zFMQwakCI39<9kOghX)5&ujVaic(PAm`{}Org#I7dqg#DDCHi7juX6)iZ)$S2?|>!^ zu$}dzqgq~{m9UB$cG(VT-%XZZr#rQiydB!Ng1>NR`+;VDm2G3Xx2}Cc{%qPOc4^5M z@2gN39+=zp8M$=GgmmGNC1YE>yLY&9uiqBT=B9UsoR7`+0$X~VtnL*EI5hCQfb?f%7$O{i5^ zIRyoZzI)o9(~P*X@{c>N-k0yG<-9v>yf%u9Jck9 zPAm?EZ$EBQzyHsyiArq3v3J!EE$dio)q>N@-e$k~^Z3zTd#Vq-B#Hq(V!+k52fB0@ zE$n(Yk-GlLS@N^4u1ZfE)P3;sHMlxnwD^hZh_IshMTOoG&RYH9AW-!b938jO`M%$1 z-QeFV^w9kNPCtCF?uTQIdgJG7FL-rsZ{-)$rd9aWT5d|SayPr~7TQLYa}hPyX`e1R z(eG_RpAHQX?DJsp@@^d0!0JPRs}HLL1baNJzBdgEqr3ik@k65tO{y+k)ptCH`)v1| z`ZF_vi{+ZvEUKPYN&E9P9spcbnXSaODnOkG=wPd@4h8Iun^jsHNFlgV=+Dl44xwU-Jp-~D99Sn7NgXfBVh(+ut5HvSzNC)P379s)0c8#k(16dJuoOCa*$vp6~l;COE&~ zp!(>*KD);JIxD;6V$)Y$XWwgoWMYwTYo+0H>ptImWmkontfBbfIdxuiKN9bBv(@5i zC05_-pKgr){z!7ma$BNCv#LF~(G>=&D>k0N<(gO*<7yC1ebW^#yq)!*GX(AnOv@!PNLuf99p zsKm9V_GgakC^#40;@^Jx)g!gg#xrfEc6&6mQQ@%RqG)H=Z-&n+G@|Qt?$bWD@a8SY+is)h zPDIt>ep9csjjkU*S$MPVEOPkp0r!vHTD)aRd_bpxYub;MU@@Yd`$*#rHOE$zjJvPv zd-c!rwT8@UQ5H~nV?$(1zN$5G>KXaf7yNg~y)w;u+tB4}j`E=)-MM$nyQ^9!Pce(r-LhHVlb#1(Nn@?KtqU?a`hpDcE>LWL8?%SQ~4Quu5gWgZP>lDD2E8<_yopc_! zx2yWFX(i~7FP=_rfEk}QvDH4cY^m>u!J~O+Zg6b%M8sb;EAV@40Y&`wC-$v7tsU4@|9H!YfznN%&U1H+>KNHm7i~+)?k;#y5%w zEpE&`@cQUD+3F*W%f8p!OukjyCUG~8_-@4e%B||V(A!&1{($dE*LZYb?P|l(YDhZ~ z^Y8Hco^+fu*VVKPxMWJD<@J~DxZS~fc%!S^)2&zOt?h^P*!S$(H}ZSha-U7**SY`0 z!hK8cjjQn28*En9A>A&{{$16z$=D7H@XL?F#c9#_(D(nu79CP3nm%3uRQx&k^a}GGq*Ju{`qMF2oJIHGO?$V#DN}9Di1CNL z%f67c<3n_Z*K_NXd;hi{x~I$ht>+FbK5~aM;brxLU)b-X`>0`$w{x01cQ;w- zOs2^hIMopX;+`oVAm{Aqk2l^3B{)kFz4BUBw?DNXMaNwp5GscdyeY^ht z>n0bU$;vEUVWy6q?E%|df7Y8JR@Aq>uefc%g;dE)&iF^%j;G|N*Ckgcm+^JjO8)ul zAZMG^qs|TW{r>E=@ z-qCd(ri@P2Dr&dr%R#M^s(I=kCyd)&&}o%e5hjn;8*1#!e8Fz$FZbtc9Wyc}t+NkKcAe|K1@=ux=PCc0j9`3Q^;LBm-G@;IAueY6kMAzZmpXl>JpQpdo zbmTyD3wAbmdr&p*NX?6c7##6T$yqe>YPtR8SgSv4*t=EgL}hhq!@?WCoZE43g3Q!ZTH;xe$v=llec_32KeUB5@?5h=zPxNmm9}vM{oxGxnkkKpTGLu zfSnq``Q_oFL(_+j-rn==rB+q<42)9ROz%BE9oXXU^}5mn>B-$+Fpp{7y1jAOsT%!C zybt#(o6MQeY}twn80(nn!ltc<`n@Syyl!4+-kIp^Cc5z*O_|f2r**D&>mRtd!R5Vh zZPv8kq4_J8J+{kM{`hm%TA^8c>$SP}$FNU6ZJB(ix}W4CuMFLHFqFT*SWSx$nCzkUqS5Pq=}u4gLSK;*GKFCH+1Sa zu(zU)^S&Y10ikP`%v5!F!JnuWZq-Rxi4N_t|;x^~efmi9VvDIny6aPI70q zY*(vWL1FEq<;H9j?=9BW7}>I*0Jf{-P$6?n`#c2&2vX3gprD{qL8Zdl1!b91A@f&I z*tnp4u3S(ME^P95IaS!<156nPhFie=78Ei2=Y_mnxh_+lJM^1exiM2-JJMHBjyW|pbSi3lC=p zF_+hp$1Uawiuuxlor?hG&D>ba&Mo7K2p;HymE!4wM?wV!6(=w|(?&TCkI$P>?jv1= zohBCMuNRgnTQKJ%441>1Q}|E6=al(KZ^glL%6=@D?aJW$hYo&A4kt&4q`;0C3eMUK z3cB@3Qy3m_CtAAPq%U03`oX4cty}ttlGZjM!a*pR+v`(iV{UU+XTh@pTuQX=(YrLc>j3#T0w0J-CM z%ts}Bk#NgWjF>BuOq8@{TF#RcqW(r3j{n1Gb{eDD+%B9_5REAUT zaN_Uk1paqjDj5srI7{GcchDVTz~T%exPQ~h0Q>_S^Tk8)1QsTkm^fD}jD_5BMpxW% zHdT5v8OKO>%tv||ER5d&gT)(3MB?7a|G|CgjERZvN5hO#a`A5kn25FSh@ z7Izkl_(W&SBM@}P#9U5i5nqC10+B$i2=K3%ODQD& zX9@|t*b|9`Fh+}gAx3A|e#%X@Zuz$&l!}3Ao8i}g_l*Ar8cBu|u}n#8Q!*a+Vd0i? zH{oXdFGKjhf|+w>RMMKy6-WgF5uew(1tGa#v{|k?=B#EfoE8EyvrE)@-StEdT(cIp9>ANB{%&5%3IHIs*a*Z? zg*Qbgq*+WMrBx7;L;yfw3`iV z>9RP?wU~m;3A33|E0jZ^QtHemPQKCHosT~Tmn}yTbI!?dRj7g47x0=`mAOr;M%K%n##K%EM1(pg%{@c^Wm^FR(k?qa+YvrI*Vt-;(mrxb}yL4<-q zqO>T@wH$CMtqH8O$j&#KL*TMmy*L8|0=bq0gu?2_7#I*@ei(7#L6g~7dLJsiPa;n4 zKj$BqNFt8^0Ai2`ay($Fx%i+EX9iGAX(TlYgi=AUP;Vhgv%<>HQ=qVN2pQMN$T{Y) zs{<-nmvY%buPe-SUSSI2JVy6EY;ZUyaL7yh~MKbPc;#C#vcn5zY4QAYQ3MUxA$$%EK`(M{%dUd=x? zl;?qn0fqcr<{}jMfH@BU`a+Dz5(1Q|=g0mHsmxp|M?(&QmnkLU(ty*h^XDrvzaV4U zC_-XN69{0cFQ)@J(0_RFKUVm=fe(d$5d7zz|2Y5KR`S=ArD9Y; zj&u%z-s0eCEvh7924Sbo=_PDwj!K>rq2I+zrwF@>#QC7t!9z){8O)*qLiQ2=zf$lw z*8e{Ua|r$u>qqSWcKi>G)XLRCXULk#A#mD+ey16^4d?3Z_ z|ED(o(*;@D%=ABqhpdd4P|{jz#=KzCnPH5HCk$YA6XoIwNX#bq+YU-iAcx?;*ulS1 zpFaV4r4)X+VWA8HKrWXg^Av!Pf$`Olm~jdqh-d*ohlg{zBF}l}4+s#M)d4U%2>^23 zBjj;|4m8=Is$}gSpd)kfEX#u09qVK0T7ty0pb8MG-L#HD8Qp3640Z7h-Mt1!2z&nW*9T# zAY_CsfGGefX*dX&;~+x90>C23BQP1|2w+u#Hlv&bY&hs5LI~h6^O0+%2_OQXz^KfRjeti5vPcyKkhDw=s0hI4kZEa^00_V`Gp$B}AV=n; z)oCCck$C})9-v5Bgw(`=m_wH90&Uc4fdncO0NNOkLK$byh%)VfR7NBLWJ$kI%l z0y#-t2H*=IAE~ziA{7)P^`blvBsj#O4J6ZHh-EY?7+ru>fH46=ao7x)te^scaifU^ zs%SVwnj@gr0jB|r1=PcE4uJ%)us|b6t_08+Xo<+pMyms~;c|kuDL|(~PLVb)h@*0@ z(H;an4h2X$Y@iQU=xC=ZhX4!)6d36ggArO0Hex9-&Ir@!@`EW<3DUR|%*2%jBVhn} zu#y1W3Q!nO#sD`Pl&X|D1VW=nAOqqmB~4~zutQ}dy-Ar$r3#Y1s7xzRaY%nyW{9g{ zAP|&UB5DgA49M&OwVw{<2UnRZqR!Icpv=R3kkZJ+ync?x3Q$p51lELrXi^qOHGCkJ zmCGhIq}U841qPW>5`;Lg!A?p= zkkDa>(`*C8qKz^W5Q1UEm_y(&fh<@ZHzrMx9X6mQIL-W_5|his$PbH{GA3mNCULXc ztCqtdquC2;{BRsG3(`nF$bvJ7#h7NsS^;9AK!Y4+p(yAzhUIVo#k^*%T*E=LUJETZ zsjNECYLnX;hn2QT1qkz%6kUS8uxj@VykEv{2uS+CPbL>VF5tIu8_L$dWma}n( zBJBkf3e@5A`ceuV>R@>TWDbD>MV(kWWKy_rC*Kr-6}||Dnkc@45@2alEUri!UHWvw zuMk9Bac>G!0xDeNO`Da9#D_IdzZU*z_tWwZqNCv?w1K?H415I_|0MYx&}84YD(1`Vx> z>3j)FBjm(5W?HU=0&$lwIy7mxav;8cCCWMcyAE9#ux;SNjnvp5a1>4&gYmo~&MguwjlSYYOVr z0uB#CEjpCL^=9odorlBIWt|yaRKUwgL1e)LIv&6eLms;xGzw72hv*Sf2wH+Ny5EvAwgo3hugIOhYSa@E88(^s*A!&#LECD3( z8h9`};%EDeG9BQAWg(-U05zyQYQ)lcF^U5!+89>L5~!AM%u;xiVjjZttB;~=Cu$6u^I@J0DDVKn)LbY8W zCsQ^h*OB1{Fq#6+r5IZ~kC$kW>$tVF=bOzfQcV9;Q*nVoJT zWUygoXVM!s*xVK;D;P2Gd^SvM%FCo&MsLEQ z;0aw`brR!=VOLt1ax2&-T$Z4{VTi;nR61x^(m4cpAd#WM2#9mJ*`(6Q*fq$>D$xkx zuyO?qT?Ccjv6O_35bFf6-J5hHX`v<@QoG$mUX@MZXwXfY#14K+=8?%Iq&@5PpmJ%% zE&x3tHH!{Q{2qacEp&iNQsV<<5qXTnc``($G3O{CQ)b92(2Kmx&_k*$elO~lM+FYO zH>^=`1-RHNWT`-vm+~PZH6jQ(eIAjBAm3}>C>?V!jL~M zGUy^KcL2bQE()>*EG$!0tuh6|QL{j((*?vBs#2RZc`+KyR=d!yW^~XN7UG&9-)0x6 zy+}ywbE?%LW5|=iFm=ot;%HnkA&rMsAzUit2Ev3UKd>0pqGUMD^Z0~pQA80Uc^bJg z;=+9DsKyqdF+UMCgeW-{NQ+Pbg@rK6+%sfd}y7E47b zwjIC`C{Eivpg^Mb(1{deLkvvnK?!H$R&5&hD@<`0l;$Y&@j*&LN;BH5S|1MyGk(7& zA&K)cVi%&rGu*7ju1nwoUN+`3=p-xw2MQaL30T5$iOmM7Ht$e49E&BLFsZpn%4$j2 zP28Z}#?uivmjyYTI)8*`%Q__l#p9(?E>cI!`KlNJ>v%T4H=CD2Bq5>rA~vZ@%A^9+ z;?3&S7C|EAcd!gGq0$gw>us=*c4GRnlqK1Hg zg?mI+b}Y=|dn6z`<6xV^(qu}gWhb(1mxs+}vjnBH(}0Vo4gw0xzl8f9whK@(b6?WP z1ji^0lBgUc0}9aSP|IkN3GJBYE`W$eMn^PEY7v0|x^&M+C=ehpz<@*#frv#88ZB^| zb}7XBO?j;OJ@@_ zYytq42{0liz{7@t{5)O>l=jZ}ulWO`Jcj_FRSG8UrIY~yrjHCjY2^N&z(zpEbeZ7` zu96ueS_VqSG>{LnN~b2w4Td)iBF42bc@`6sNRX>csiB@oqQ9DIXJhZ@5Hkv^;Q5~x#YfjCK(N*zJeGB4@$ zsu})8%^Yh&XU!qdfU%5#ZGgFY3&&|MO*7cIX~G_q+MGNu;{XzICQX-e@J(DIObFa| zO6*Z`SsuO~_BwJ1Fr`lw()a`!M(4figcz)C!C46l(ZaCngnK@ zOm0>C@D!ZQQJ{uQ5b4FtXvXGqFwO#u+ZlW zQAs@P6nO$seSpHSa8whDMn#!KEXqo;F-F@MT>#dAPFE!SacGJO%&23eCjMm zA>;Ve8i_4SWI$5DvolWB712{PZqYI`1)5ZPr7T^J2SJ^QU`N;qDNiqA1^F7DG?k=X zG2F)Jw<#;|5dLI}BItx);PLpK7SzTzk_L+e@~7o_k0jIju{2AqFet1NmCl25#R%?! zO#+K4p5?hDh{30ls4q#>9hC8hwPNhn=u zb;?XhECEOrjQjn7RYAaVn+^fwai0-Z8}K-r1~g(VUy?&$2?iV}$C9yUP>oX;B`k6~ znznKgD;`DC5fByd0y2&j!T@F*VAC>75T~SB0+RD1k-Q?gG&~^>)MRXgDxFI5ou+_S z!9yrgrLb9r7OOwOQ@XSypi~4^L8nDVGc8z64x7R5aKbqXGAKlBC>hQ(F)h;ShLCQm0c1MZ~(4QIqnSf?z&}qL*MwT_(+QV z)C#91#uWsE%$GJ;DUkLWU@M(;ngmJ_qLeA@J~^(BhumLwi1WhF*fay%$4$T62q$75w=IH)nfLA#k_ zux6z|MjhfY@`{Sm5i$~?0y3A&1cL!r~k z!!bP;;2P}egf{AkSw8IMNH*?qI29qMnt2~J^XfBZfr4UmCby?Z+-3K=qy|213wvF# zBb_kF_##ZeOO+nPVgfLG0=g`TM9nH&TAAhM^U6+FR!O++F*swPF-K6U#xbwkM!UEw z*p>~O^;Q{R$-uFwoU(h|A)X^d1=FU8-ox`~kQ@c-K-lGkY=&UaCXr%6VIt+mb!idD zEJE#mw?EA%31gLq&W)gO20p?lIUWh1Uu;Q`qNs0D-}(% zLuo-!qKn)3Oq}JAU>?XW<5`ksT&W1tbjqed2zg!#CV?*lGnp|OVODEmwxG#P@g+zA zATt1JPulbuSvcuVnqfs;s27?XK|dOzGAO-3NDrMfuzL} z^~>#e$|dvhlBTqcZ#DRRv1mA*wj%j1pm9Y)CnGTgr^Fe5Chm?Hp#W?br|pQsX7L5w z0)0B{l^9?>%WjZbl&TmbMXueIR=U(V3iL`$&K0p(aVB_VoS9fEi*sC7k=cYO?c$8a z!3p~;KA8ySz&OVlQ)(gvBt|rTB(Adta|lqWH3c|0DVQ&!BnW0n>sWCE1k$mz&gb+1 z0*S`0PH|DGM6MQ)d{NY`*J;xrKbmIg^Ikc3IpFb=kr;D5W@5}qW)+YeCr1o~Avz+U z6FQpj032?)Qb8qDV#4ADAUF_oS@PpT`X4XIlxz%Miefkv4Qb>o9vBkKAuD1ng#j@s zvr52_ig?*hJ>-M5-dq>7js#!oB?V-d)DSGYRF)9O!UmkJRQN%wQ9-IH#?!MD4xUGE ziYpzM)|3v5D0M2v3g^3k+u}~Yf+M%kN=ZV`XG~u051MeX&0*2#`6g1I!t8`QD0RjS ziC9W2H^Om)+~Yw4a!x!)K_qULOLcZZR43r_3<9T~CucODs=X@l2~X|TLYW7NtKWju9~`i~Mnoo1Hq(*SJ?g_W|H6+yKDpEGNOgkgZm z+$eZ-9)X^g<1ULil1QlBNq#oNEVUX*TN;8PQz+^I91@d-_EJu*JBZqYVz$-=Xf+li z?qs~?hgg}GX!JVFsfJiwDNjMB1-^vN!qwp#g_C8o`}Bdd&mcA|@qF$33OFMN+E@{Wi9+4VXdI?j2tC9HQ>g zb>zc5Js%AS)jo4b8DwP{uMkvb~0y z&(G$k|Fk4*CL&frC=0k!W?_Vjky4w==T(U~(sVFJ#x-0|h8;^tg(OEW;3#YvPS&bZ zY8k!Bhd7*=O&IVJSrrH6THSgplr_)+%HqL;@kjuIB}O7l#U;+PMDOCdGGfNEVkQd{ zMlumlK2EgxeR{VB1=16oe z5SC*(MatVIN9^-JjEJM&T!{DYZjSaVBZ$iuq`f$cpQUlLTb|+w($c&&Mp<+^PDM0f zg~>%wT)%*h8mWN65@6PWl|Dj3I%P~kC8P>_5mHA+5SxR`*p45yFX zXd+QYnh;;F)~R8a4&ifBW`WM&)kNH=pCdIJr6M;VhIGzAMqzT3G=}G=JZYmag|M@F zjyGmuqO)L3q?4pHDX&@q#2|50sf{BsxjxQiX}w{OJBrD1gO1`j0w9$3=O`dCu@zut zJjtwdk1BND-ziDZxm86!NN!5Js?Ulq?jDq%e~W%^_eD8Mc6% zRdRu(HY9?%0jU^4xZ;%35v8Fl!e*W-plFOV<3R(%cvn}wZH_$fUdNx>YoH0!ehsLsG;+w#LZBGV|0 znY2dZRfAdPNds0y$q|@T5q$<1G0|%XvI^63IF2G_W-&)#mAO?CFr$@GVwW=?Bbn8x zD;}eQeoYun+dl3gsRT_%JxXS*vpAyS0B$8Rh6J;0!}Z$}Y7UmcSX`Sp*9BiZW|9FM zP8PIAEtXWkX$FLGfibA!fSAsP#(_*gZq|@~9Cl!iQb)u*6~!!nMBNmf*MpG4E5fuq z38j)bROw8BO4w;;=+fXk#+mSS8A44;%r>@4%g#b!8;_I8#v?`*EunBtK1$<)3WJO9 zL0n+cY}bm7DnDS7S!{7Z$ZKNxVJMKom54yWw`vlIhLd7mO-Dw_pbrBeJik03kl7r} zWQgO*NL7IV!6lTwOaKWOMO=kIt7L{{x82~T8Iw%FMw7;#ae|D8RI5F@L@Yn$F;EW7 zBzG7y8iAHsBCEpEBmtd3$QzaA zLmZ_n72&zUQZXLaWx#~V#LwW41mi-aUP>Bs@G?AUJS@`@88_vSX42xY)~Im;Dql#h z%V+l~W;ExdZJ~demwb$ROOIv~3ukjA1RU~nWI8or51TO=LmZHnP=Bw$tZ0EJB_XBKWeS}wCtsl_Q9!S*@5TssY9!HhFwNo0sr-k3P3TSno0 zx7DR*C1NtWND__VDV<8ANx?YmB2yftKcmcMQd)yg>!J{oSLq33;;fd+C*!zDI=~F& za8T@uWQ|%j0HJcXk|T}dxJaY3Q|SPc+>991_7vf;plE=3!oi2h*}eqJkPmf9F&Xu^ zm=!yp-2|hUR_oxKO@Ihdpi&uml-OG!y0E8yrQ9$iF~po}`mO#^I+6J^Kzv@&nbCIQDF zq*<87?KJ2O!6+T4R572`#igtowpAr#Qkp6@rsAOCMYF&H$dwCPZOXk*y~ zA&1w-_y7?GOQNzQ9Wz>41d`#K6afK8!Yn88gLF9y?V`E4i z3~Ee51(SBd^aiC8PYaSP<{?BgE5Mc{;!3uPYY+*ov{@WhGg($fN?r-yL%Gb!R6c)@ zf!F~oEXCb2W^U#euo-^1yis0|FvQ|=H(Sh97f3*HP=P7khLAv%qKu$AC9jE5 z4>v8a@YPHnf&q|ZB@#@?tkrQ9{J6?#B=p{-9Ay@@{5lL_OXN(3S(A2%q+%-Jj2d#9 z$mB9v%u+lojrg>3g$QJx0f|rwTERs!QLRscyCsZr$yF&KmnCs

E5@^D{X=G@}7N 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 I_%DC?FGODjTL1t6 literal 0 HcmV?d00001 diff --git a/site/docs/benchmarks.md b/site/docs/benchmarks.md new file mode 100644 index 000000000000..636ad36b1646 --- /dev/null +++ b/site/docs/benchmarks.md @@ -0,0 +1,132 @@ +--- +title: "Benchmarks" +--- + + +## Available Benchmarks and how to run them + +Benchmarks are located under `/jmh`. It is generally favorable to only run the tests of interest rather than running all available benchmarks. +Also note that JMH benchmarks run within the same JVM as the system-under-test, so results might vary between runs. + +## Running Benchmarks on GitHub + +It is possible to run one or more Benchmarks via the **JMH Benchmarks** GH action on your own fork of the Iceberg repo. This GH action takes the following inputs: +* The repository name where those benchmarks should be run against, such as `apache/iceberg` or `/iceberg` +* The branch name to run benchmarks against, such as `master` or `my-cool-feature-branch` +* A list of comma-separated double-quoted Benchmark names, such as `"IcebergSourceFlatParquetDataReadBenchmark", "IcebergSourceFlatParquetDataFilterBenchmark", "IcebergSourceNestedListParquetDataWriteBenchmark"` + +Benchmark results will be uploaded once **all** benchmarks are done. + +It is worth noting that the GH runners have limited resources so the benchmark results should rather be seen as an indicator to guide developers in understanding code changes. +It is likely that there is variability in results across different runs, therefore the benchmark results shouldn't be used to form assumptions around production choices. + + +## Running Benchmarks locally + +Below are the existing benchmarks shown with the actual commands on how to run them locally. + + +### IcebergSourceNestedListParquetDataWriteBenchmark +A benchmark that evaluates the performance of writing nested Parquet data using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceNestedListParquetDataWriteBenchmark -PjmhOutputPath=benchmark/iceberg-source-nested-list-parquet-data-write-benchmark-result.txt` + +### SparkParquetReadersNestedDataBenchmark +A benchmark that evaluates the performance of reading nested Parquet data using Iceberg and Spark Parquet readers. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=SparkParquetReadersNestedDataBenchmark -PjmhOutputPath=benchmark/spark-parquet-readers-nested-data-benchmark-result.txt` + +### SparkParquetWritersFlatDataBenchmark +A benchmark that evaluates the performance of writing Parquet data with a flat schema using Iceberg and Spark Parquet writers. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=SparkParquetWritersFlatDataBenchmark -PjmhOutputPath=benchmark/spark-parquet-writers-flat-data-benchmark-result.txt` + +### IcebergSourceFlatORCDataReadBenchmark +A benchmark that evaluates the performance of reading ORC data with a flat schema using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceFlatORCDataReadBenchmark -PjmhOutputPath=benchmark/iceberg-source-flat-orc-data-read-benchmark-result.txt` + +### SparkParquetReadersFlatDataBenchmark +A benchmark that evaluates the performance of reading Parquet data with a flat schema using Iceberg and Spark Parquet readers. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=SparkParquetReadersFlatDataBenchmark -PjmhOutputPath=benchmark/spark-parquet-readers-flat-data-benchmark-result.txt` + +### VectorizedReadDictionaryEncodedFlatParquetDataBenchmark +A benchmark to compare performance of reading Parquet dictionary encoded data with a flat schema using vectorized Iceberg read path and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=VectorizedReadDictionaryEncodedFlatParquetDataBenchmark -PjmhOutputPath=benchmark/vectorized-read-dict-encoded-flat-parquet-data-result.txt` + +### IcebergSourceNestedListORCDataWriteBenchmark +A benchmark that evaluates the performance of writing nested Parquet data using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceNestedListORCDataWriteBenchmark -PjmhOutputPath=benchmark/iceberg-source-nested-list-orc-data-write-benchmark-result.txt` + +### VectorizedReadFlatParquetDataBenchmark +A benchmark to compare performance of reading Parquet data with a flat schema using vectorized Iceberg read path and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=VectorizedReadFlatParquetDataBenchmark -PjmhOutputPath=benchmark/vectorized-read-flat-parquet-data-result.txt` + +### IcebergSourceFlatParquetDataWriteBenchmark +A benchmark that evaluates the performance of writing Parquet data with a flat schema using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceFlatParquetDataWriteBenchmark -PjmhOutputPath=benchmark/iceberg-source-flat-parquet-data-write-benchmark-result.txt` + +### IcebergSourceNestedAvroDataReadBenchmark +A benchmark that evaluates the performance of reading Avro data with a flat schema using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceNestedAvroDataReadBenchmark -PjmhOutputPath=benchmark/iceberg-source-nested-avro-data-read-benchmark-result.txt` + +### IcebergSourceFlatAvroDataReadBenchmark +A benchmark that evaluates the performance of reading Avro data with a flat schema using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceFlatAvroDataReadBenchmark -PjmhOutputPath=benchmark/iceberg-source-flat-avro-data-read-benchmark-result.txt` + +### IcebergSourceNestedParquetDataWriteBenchmark +A benchmark that evaluates the performance of writing nested Parquet data using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceNestedParquetDataWriteBenchmark -PjmhOutputPath=benchmark/iceberg-source-nested-parquet-data-write-benchmark-result.txt` + +### IcebergSourceNestedParquetDataReadBenchmark +* A benchmark that evaluates the performance of reading nested Parquet data using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +` ./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceNestedParquetDataReadBenchmark -PjmhOutputPath=benchmark/iceberg-source-nested-parquet-data-read-benchmark-result.txt` + +### IcebergSourceNestedORCDataReadBenchmark +A benchmark that evaluates the performance of reading ORC data with a flat schema using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceNestedORCDataReadBenchmark -PjmhOutputPath=benchmark/iceberg-source-nested-orc-data-read-benchmark-result.txt` + +### IcebergSourceFlatParquetDataReadBenchmark +A benchmark that evaluates the performance of reading Parquet data with a flat schema using Iceberg and the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceFlatParquetDataReadBenchmark -PjmhOutputPath=benchmark/iceberg-source-flat-parquet-data-read-benchmark-result.txt` + +### IcebergSourceFlatParquetDataFilterBenchmark +A benchmark that evaluates the file skipping capabilities in the Spark data source for Iceberg. This class uses a dataset with a flat schema, where the records are clustered according to the +column used in the filter predicate. The performance is compared to the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: + +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceFlatParquetDataFilterBenchmark -PjmhOutputPath=benchmark/iceberg-source-flat-parquet-data-filter-benchmark-result.txt` + +### IcebergSourceNestedParquetDataFilterBenchmark +A benchmark that evaluates the file skipping capabilities in the Spark data source for Iceberg. This class uses a dataset with nested data, where the records are clustered according to the +column used in the filter predicate. The performance is compared to the built-in file source in Spark. To run this benchmark for either spark-2 or spark-3: +`./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=IcebergSourceNestedParquetDataFilterBenchmark -PjmhOutputPath=benchmark/iceberg-source-nested-parquet-data-filter-benchmark-result.txt` + +### SparkParquetWritersNestedDataBenchmark +* A benchmark that evaluates the performance of writing nested Parquet data using Iceberg and Spark Parquet writers. To run this benchmark for either spark-2 or spark-3: + `./gradlew :iceberg-spark:iceberg-spark[2|3]:jmh -PjmhIncludeRegex=SparkParquetWritersNestedDataBenchmark -PjmhOutputPath=benchmark/spark-parquet-writers-nested-data-benchmark-result.txt` diff --git a/site/docs/blogs.md b/site/docs/blogs.md new file mode 100644 index 000000000000..a5ef127235c5 --- /dev/null +++ b/site/docs/blogs.md @@ -0,0 +1,389 @@ +--- +title: "Blogs" +--- + + +## Iceberg Blogs + +Here is a list of company blogs that talk about Iceberg. The blogs are ordered from most recent to oldest. + + +### [From Hive Tables to Iceberg Tables: Hassle-Free](https://blog.cloudera.com/from-hive-tables-to-iceberg-tables-hassle-free/) +**Date**: July 14th, 2023, **Company**: Cloudera + +**Authors**: [Srinivas Rishindra Pothireddi](https://www.linkedin.com/in/srinivas-rishindra/) + +### [12 Times Faster Query Planning With Iceberg Manifest Caching in Impala](https://blog.cloudera.com/12-times-faster-query-planning-with-iceberg-manifest-caching-in-impala/) +**Date**: July 13th, 2023, **Company**: Cloudera + +**Authors**: [Riza Suminto](https://www.linkedin.com/in/rizasuminto/) + +### [How Bilibili Builds OLAP Data Lakehouse with Apache Iceberg](https://medium.com/@lirui.fudan/how-bilibili-builds-olap-data-lakehouse-with-apache-iceberg-9f3408e53f9) +**Date**: June 14th, 2023, **Company**: Bilibili + +**Authors**: [Rui Li](https://www.linkedin.com/in/rui-li-19282979/) + +### [Introducing the Apache Iceberg Catalog Migration Tool](https://www.dremio.com/blog/introducing-the-apache-iceberg-catalog-migration-tool/) +**Date**: May 12th, 2022, **Company**: Dremio + +**Authors**: [Dipankar Mazumdar](https://www.linkedin.com/in/dipankar-mazumdar/) & [Ajantha Bhat](https://www.linkedin.com/in/ajanthabhat/) + +### [3 Ways to Use Python with Apache Iceberg](https://www.dremio.com/blog/3-ways-to-use-python-with-apache-iceberg/) +**Date**: April 12th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [3 Ways to Convert a Delta Lake Table Into an Apache Iceberg Table](https://www.dremio.com/blog/3-ways-to-convert-a-delta-lake-table-into-an-apache-iceberg-table/) +**Date**: April 3rd, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [How to Convert CSV Files into an Apache Iceberg table with Dremio](https://www.dremio.com/blog/how-to-convert-csv-files-into-an-apache-iceberg-table-with-dremio/) +**Date**: April 3rd, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Open Data Lakehouse powered by Iceberg for all your Data Warehouse needs](https://blog.cloudera.com/open-data-lakehouse-powered-by-iceberg-for-all-your-data-warehouse-needs/) +**Date**: April 3rd, 2023, **Company**: Cloudera + +**Authors**: [Zoltan Borok-Nagy](https://www.linkedin.com/in/zoltán-borók-nagy-7370a65b/), [Ayush Saxena](https://www.linkedin.com/in/ayush-saxena151/), [Tamas Mate](https://www.linkedin.com/in/tmater/), [Simhadri Govindappa](https://www.linkedin.com/in/simhadri-govindappa-1a7788148/) + +### [Exploring Branch & Tags in Apache Iceberg using Spark](https://www.dremio.com/blog/exploring-branch-tags-in-apache-iceberg-using-spark/) +**Date**: March 29th, 2022, **Company**: Dremio + +**Author**: [Dipankar Mazumdar](https://www.linkedin.com/in/dipankar-mazumdar/) + +### [Iceberg Tables: Catalog Support Now Available](https://www.snowflake.com/blog/iceberg-tables-catalog-support-available-now/) +**Date**: March 29th, 2023, **Company**: Snowflake + +**Authors**: [Ron Ortloff](https://www.linkedin.com/in/ron-ortloff/), [Dennis Huo](https://www.linkedin.com/in/dennis-huo-2aaba92a/) + +### [Dealing with Data Incidents Using the Rollback Feature in Apache Iceberg](https://www.dremio.com/blog/dealing-with-data-incidents-using-the-rollback-feature-in-apache-iceberg/) +**Date**: February 24th, 2022, **Company**: Dremio + +**Author**: [Dipankar Mazumdar](https://www.linkedin.com/in/dipankar-mazumdar/) + +### [Partition and File Pruning for Dremio’s Apache Iceberg-backed Reflections](https://www.dremio.com/blog/partition-and-file-pruning-for-dremios-apache-iceberg-backed-reflections/) +**Date**: February 8th, 2022, **Company**: Dremio + +**Author**: [Benny Chow](https://www.linkedin.com/in/bechow/) + +### [Understanding Iceberg Table Metadata](https://medium.com/snowflake/understanding-iceberg-table-metadata-b1209fbcc7c3) +**Date**: January 30st, 2023, **Company**: Snowflake + +**Author**: [Phani Raj](https://www.linkedin.com/in/phani-raj-9830a31b/) + +### [Creating and managing Apache Iceberg tables using serverless features and without coding](https://medium.com/snowflake/creating-and-managing-apache-iceberg-tables-using-serverless-features-and-without-coding-14d2198cf5b5) +**Date**: January 27th, 2023, **Company**: Snowflake + +**Author**: [Parag Jain](https://www.linkedin.com/in/paragjainsa/) + +### [Getting started with Apache Iceberg](https://medium.com/snowflake/getting-started-with-apache-iceberg-80f338921a31) +**Date**: January 27th, 2023, **Company**: Snowflake + +**Author**: [Jedidiah Rajbhushan](https://www.linkedin.com/in/jrajbhushan/) + +### [How Apache Iceberg enables ACID compliance for data lakes](https://medium.com/snowflake/how-apache-iceberg-enables-acid-compliance-for-data-lakes-9069ae783b60/) +**Date**: January 13th, 2023, **Company**: Snowflake + +**Authors**: [Sumeet Tandure](https://www.linkedin.com/in/sumeettandure/) + +### [Multi-Cloud Open Lakehouse with Apache Iceberg in Cloudera Data Platform](https://blog.cloudera.com/implement-a-multi-cloud-open-lakehouse-with-apache-iceberg-in-cloudera-data-platform/) +**Date**: December 15th, 2022, **Company**: Cloudera + +**Authors**: [Bill Zhang](https://www.linkedin.com/in/billzhang01/), [Shaun Ahmadian](https://www.linkedin.com/in/ssahmadian/), [Zoltan Borok-Nagy](https://www.linkedin.com/in/zoltán-borók-nagy-7370a65b/), [Vincent Kulandaisamy](https://www.linkedin.com/in/vincentkulandaisamy/) + +### [Connecting Tableau to Apache Iceberg Tables with Dremio](https://www.dremio.com/blog/connecting-tableau-to-apache-iceberg-tables-with-dremio/) +**Date**: December 15th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Getting Started with Project Nessie, Apache Iceberg, and Apache Spark Using Docker](https://www.dremio.com/blog/getting-started-with-project-nessie-apache-iceberg-and-apache-spark-using-docker/) +**Date**: December 15th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Apache Iceberg FAQ](https://www.dremio.com/blog/apache-iceberg-faq/) +**Date**: December 14th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [A Notebook for getting started with Project Nessie, Apache Iceberg, and Apache Spark](https://www.dremio.com/blog/a-notebook-for-getting-started-with-project-nessie-apache-iceberg-and-apache-spark/) +**Date**: December 5th, 2022, **Company**: Dremio + +**Author**: [Dipankar Mazumdar](https://www.linkedin.com/in/dipankar-mazumdar/) + +### [Time Travel with Dremio and Apache Iceberg](https://www.dremio.com/blog/time-travel-with-dremio-and-apache-iceberg/) +**Date**: November 29th, 2022, **Company**: Dremio + +**Author**: [Michael Flower](https://www.linkedin.com/in/michael-flower-b0a3474/) + +### [Compaction in Apache Iceberg: Fine-Tuning Your Iceberg Table's Data Files](https://www.dremio.com/subsurface/compaction-in-apache-iceberg-fine-tuning-your-iceberg-tables-data-files/) +**Date**: November 9th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [The Life of a Read Query for Apache Iceberg Tables](https://www.dremio.com/subsurface/the-life-of-a-read-query-for-apache-iceberg-tables/) +**Date**: October 31st, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Puffins and Icebergs: Additional Stats for Apache Iceberg Tables](https://www.dremio.com/subsurface/puffins-and-icebergs-additional-stats-for-apache-iceberg-tables/) +**Date**: October 17th, 2022, **Company**: Dremio + +**Author**: [Dipankar Mazumdar](https://www.linkedin.com/in/dipankar-mazumdar/) + +### [Apache Iceberg and the Right to be Forgotten](https://www.dremio.com/subsurface/apache-iceberg-and-the-right-to-be-forgotten/) +**Date**: September 30th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Streaming Data into Apache Iceberg tables using AWS Kinesis and AWS Glue](https://www.dremio.com/subsurface/streaming-data-into-apache-iceberg-tables-using-aws-kinesis-and-aws-glue/) +**Date**: September 26th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Iceberg Flink Sink: Stream Directly into your Data Warehouse Tables](https://tabular.io/blog/flink-sink/) +**Date**: October 12, 2022, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/) + +### [Partitioning for Correctness (and Performance)](https://tabular.io/blog/partitioning/) +**Date**: September 28, 2022, **Company**: Tabular + +**Author**: [Jason Reid](https://www.linkedin.com/in/jasonreid/) + +### [Ensuring High Performance at Any Scale with Apache Iceberg’s Object Store File Layout](https://www.dremio.com/subsurface/ensuring-high-performance-at-any-scale-with-apache-icebergs-object-store-file-layout/) +**Date**: September 20, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Introduction to Apache Iceberg Using Spark](https://www.dremio.com/subsurface/introduction-to-apache-iceberg-using-spark/) +**Date**: September 15, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [How Z-Ordering in Apache Iceberg Helps Improve Performance](https://www.dremio.com/subsurface/how-z-ordering-in-apache-iceberg-helps-improve-performance/) +**Date**: September 13th, 2022, **Company**: Dremio + +**Author**: [Dipankar Mazumdar](https://www.linkedin.com/in/dipankar-mazumdar/) + +### [Apache Iceberg 101 – Your Guide to Learning Apache Iceberg Concepts and Practices](https://www.dremio.com/subsurface/apache-iceberg-101-your-guide-to-learning-apache-iceberg-concepts-and-practices/) +**Date**: September 12th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [A Hands-On Look at the Structure of an Apache Iceberg Table](https://www.dremio.com/subsurface/a-hands-on-look-at-the-structure-of-an-apache-iceberg-table/) +**Date**: August 24, 2022, **Company**: Dremio + +**Author**: [Dipankar Mazumdar](https://www.linkedin.com/in/dipankar-mazumdar/) + +### [Future-Proof Partitioning and Fewer Table Rewrites with Apache Iceberg](https://www.dremio.com/subsurface/future-proof-partitioning-and-fewer-table-rewrites-with-apache-iceberg/) +**Date**: August 18, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [How to use Apache Iceberg in CDP's Open Lakehouse](https://blog.cloudera.com/how-to-use-apache-iceberg-in-cdps-open-lakehouse/) +**Date**: August 8th, 2022, **Company**: Cloudera + +**Authors**: [Bill Zhang](https://www.linkedin.com/in/billzhang01/), [Peter Ableda](https://www.linkedin.com/in/peterableda), [Shaun Ahmadian](https://www.linkedin.com/in/ssahmadian/), [Manish Maheshwari](https://www.linkedin.com/in/mmaheshwari/) + +### [Near Real-Time Ingestion For Trino](https://www.starburst.io/blog/near-real-time-ingestion-for-trino/) +**Date**: August 4th, 2022, **Company**: Starburst + +**Authors**: [Eric Hwang](https://www.linkedin.com/in/ericwhwang), [Monica Miller](https://www.linkedin.com/in/monica-d-miller), [Brian Zhan](https://www.linkedin.com/in/bzhan) + +### [How to implement Apache Iceberg in AWS Athena](https://big-data-demystified.ninja/2022/07/28/how-to-implement-apache-iceberg-in-aws-athena/) +**Date**: July 28th, 2022 + +**Author**: [Shneior Dicastro] + +### [Supercharge your Data Lakehouse with Apache Iceberg in Cloudera Data Platform](https://blog.cloudera.com/supercharge-your-data-lakehouse-with-apache-iceberg-in-cloudera-data-platform/) +**Date**: June 30th, 2022, **Company**: Cloudera + +**Authors**: [Bill Zhang](https://www.linkedin.com/in/billzhang01/), [Shaun Ahmadian](https://www.linkedin.com/in/ssahmadian/) + +### [Migrating a Hive Table to an Iceberg Table Hands-on Tutorial](https://www.dremio.com/subsurface/migrating-a-hive-table-to-an-iceberg-table-hands-on-tutorial/) +**Date**: June 6th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Fewer Accidental Full Table Scans Brought to You by Apache Iceberg’s Hidden Partitioning](https://www.dremio.com/subsurface/fewer-accidental-full-table-scans-brought-to-you-by-apache-icebergs-hidden-partitioning/) +**Date**: May 21st, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [An Introduction To The Iceberg Java API Part 2 - Table Scans](https://tabular.io/blog/java-api-part-2/) +**Date**: May 11th, 2022, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/) + +### [Iceberg's Guiding Light: The Iceberg Open Table Format Specification](https://tabular.io/blog/iceberg-format-version/) +**Date**: April 26th, 2022, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/) + +### [How to Migrate a Hive Table to an Iceberg Table](https://www.dremio.com/subsurface/how-to-migrate-a-hive-table-to-an-iceberg-table/) +**Date**: April 15th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [Using Iceberg's S3FileIO Implementation To Store Your Data In MinIO](https://tabular.io/blog/minio/) +**Date**: April 14th, 2022, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/) + +### [Maintaining Iceberg Tables – Compaction, Expiring Snapshots, and More](https://www.dremio.com/subsurface/maintaining-iceberg-tables-compaction-expiring-snapshots-and-more/) +**Date**: April 7th, 2022, **Company**: Dremio + +**Author**: [Alex Merced](https://www.linkedin.com/in/alexmerced/) + +### [An Introduction To The Iceberg Java API - Part 1](https://tabular.io/blog/java-api-part-1/) +**Date**: April 1st, 2022, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/) + +### [Integrated Audits: Streamlined Data Observability With Apache Iceberg](https://tabular.io/blog/integrated-audits/) +**Date**: March 2nd, 2022, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/) + +### [Introducing Apache Iceberg in Cloudera Data Platform](https://blog.cloudera.com/introducing-apache-iceberg-in-cloudera-data-platform/) +**Date**: February 23rd, 2022, **Company**: Cloudera + +**Authors**: [Bill Zhang](https://www.linkedin.com/in/billzhang01/), [Peter Vary](https://www.linkedin.com/in/peter-vary/), [Marton Bod](https://www.linkedin.com/in/martonbod/), [Wing Yew Poon](https://github.com/wypoon) + +### [What's new in Iceberg 0.13](https://tabular.io/blog/whats-new-in-iceberg-0.13/) +**Date**: February 22nd, 2022, **Company**: Tabular + +**Author**: [Ryan Blue](https://www.linkedin.com/in/rdblue/) + +### [Apache Iceberg Becomes Industry Open Standard with Ecosystem Adoption](https://www.dremio.com/apache-iceberg-becomes-industry-open-standard-with-ecosystem-adoption/) +**Date**: February 3rd, 2022, **Company**: Dremio + +**Author**: [Mark Lyons](https://www.linkedin.com/in/markclyons/) + +### [Docker, Spark, and Iceberg: The Fastest Way to Try Iceberg!](https://tabular.io/blog/docker-spark-and-iceberg/) +**Date**: February 2nd, 2022, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/), [Kyle Bendickson](https://www.linkedin.com/in/kylebendickson/) + +### [Expanding the Data Cloud with Apache Iceberg](https://www.snowflake.com/blog/expanding-the-data-cloud-with-apache-iceberg/) +**Date**: January 21st, 2022, **Company**: Snowflake + +**Author**: [James Malone](https://www.linkedin.com/in/jamesamalone/) + +### [Iceberg FileIO: Cloud Native Tables](https://tabular.io/blog/iceberg-fileio/) +**Date**: December 16th, 2021, **Company**: Tabular + +**Author**: [Daniel Weeks](https://www.linkedin.com/in/daniel-weeks-a1946860/) + +### [Using Spark in EMR with Apache Iceberg](https://tabular.io/blog/emr-spark-and-iceberg/) +**Date**: December 10th, 2021, **Company**: Tabular + +**Author**: [Sam Redai](https://www.linkedin.com/in/sredai/) + +### [Using Flink CDC to synchronize data from MySQL sharding tables and build real-time data lake](https://ververica.github.io/flink-cdc-connectors/master/content/quickstart/build-real-time-data-lake-tutorial.html) +**Date**: November 11th, 2021, **Company**: Ververica, Alibaba Cloud + +**Author**: [Yuxia Luo](https://github.com/luoyuxia), [Jark Wu](https://github.com/wuchong), [Zheng Hu](https://www.linkedin.com/in/zheng-hu-37017683/) + +### [Metadata Indexing in Iceberg](https://tabular.io/blog/iceberg-metadata-indexing/) +**Date**: October 10th, 2021, **Company**: Tabular + +**Author**: [Ryan Blue](https://www.linkedin.com/in/rdblue/) + +### [Using Debezium to Create a Data Lake with Apache Iceberg](https://debezium.io/blog/2021/10/20/using-debezium-create-data-lake-with-apache-iceberg/) +**Date**: October 20th, 2021, **Company**: Memiiso Community + +**Author**: [Ismail Simsek](https://www.linkedin.com/in/ismailsimsek/) + +### [How to Analyze CDC Data in Iceberg Data Lake Using Flink](https://www.alibabacloud.com/blog/how-to-analyze-cdc-data-in-iceberg-data-lake-using-flink_597838) +**Date**: June 15th, 2021, **Company**: Alibaba Cloud Community + +**Author**: [Li Jinsong](https://www.linkedin.com/in/%E5%8A%B2%E6%9D%BE-%E6%9D%8E-48b54b101/), [Hu Zheng](https://www.linkedin.com/in/zheng-hu-37017683/), [Yang Weihai](https://www.linkedin.com/in/weihai-yang-697a16224/), [Peidan Li](https://www.linkedin.com/in/peidian-li-18938820a/) + +### [Apache Iceberg: An Architectural Look Under the Covers](https://www.dremio.com/apache-iceberg-an-architectural-look-under-the-covers/) +**Date**: July 6th, 2021, **Company**: Dremio + +**Author**: [Jason Hughes](https://www.linkedin.com/in/jasonhhughes/) + +### [Migrating to Apache Iceberg at Adobe Experience Platform](https://medium.com/adobetech/migrating-to-apache-iceberg-at-adobe-experience-platform-40fa80f8b8de) +**Date**: Jun 17th, 2021, **Company**: Adobe + +**Author**: [Romin Parekh](https://www.linkedin.com/in/rominparekh/), [Miao Wang](https://www.linkedin.com/in/miao-wang-0406a74/), [Shone Sadler](https://www.linkedin.com/in/shonesadler/) + +### [Flink + Iceberg: How to Construct a Whole-scenario Real-time Data Warehouse](https://www.alibabacloud.com/blog/flink-%2B-iceberg-how-to-construct-a-whole-scenario-real-time-data-warehouse_597824) +**Date**: Jun 8th, 2021, **Company**: Tencent + +**Author** [Shu (Simon Su) Su](https://www.linkedin.com/in/shu-su-62944994/) + +### [Trino on Ice III: Iceberg Concurrency Model, Snapshots, and the Iceberg Spec](https://blog.starburst.io/trino-on-ice-iii-iceberg-concurrency-model-snapshots-and-the-iceberg-spec) +**Date**: May 25th, 2021, **Company**: Starburst + +**Author**: [Brian Olsen](https://www.linkedin.com/in/bitsondatadev) + +### [Trino on Ice II: In-Place Table Evolution and Cloud Compatibility with Iceberg](https://blog.starburst.io/trino-on-ice-ii-in-place-table-evolution-and-cloud-compatibility-with-iceberg) +**Date**: May 11th, 2021, **Company**: Starburst + +**Author**: [Brian Olsen](https://www.linkedin.com/in/bitsondatadev) + +### [Trino On Ice I: A Gentle Introduction To Iceberg](https://blog.starburst.io/trino-on-ice-i-a-gentle-introduction-to-iceberg) +**Date**: Apr 27th, 2021, **Company**: Starburst + +**Author**: [Brian Olsen](https://www.linkedin.com/in/bitsondatadev) + +### [Apache Iceberg: A Different Table Design for Big Data](https://thenewstack.io/apache-iceberg-a-different-table-design-for-big-data/) +**Date**: Feb 1st, 2021, **Company**: thenewstack.io + +**Author**: [Susan Hall](https://thenewstack.io/author/susanhall/) + +### [A Short Introduction to Apache Iceberg](https://medium.com/expedia-group-tech/a-short-introduction-to-apache-iceberg-d34f628b6799) +**Date**: Jan 26th, 2021, **Company**: Expedia + +**Author**: [Christine Mathiesen](https://www.linkedin.com/in/christine-mathiesen-676a98159/) + +### [Taking Query Optimizations to the Next Level with Iceberg](https://medium.com/adobetech/taking-query-optimizations-to-the-next-level-with-iceberg-6c968b83cd6f) +**Date**: Jan 14th, 2021, **Company**: Adobe + +**Author**: [Gautam Kowshik](https://www.linkedin.com/in/gautamk/), [Xabriel J. Collazo Mojica](https://www.linkedin.com/in/xabriel/) + +### [FastIngest: Low-latency Gobblin with Apache Iceberg and ORC format](https://engineering.linkedin.com/blog/2021/fastingest-low-latency-gobblin) +**Date**: Jan 6th, 2021, **Company**: Linkedin + +**Author**: [Zihan Li](https://www.linkedin.com/in/zihan-li-0a8a15149/), [Sudarshan Vasudevan](https://www.linkedin.com/in/suddu/), [Lei Sun](https://www.linkedin.com/in/lei-s-a93138a0/), [Shirshanka Das](https://www.linkedin.com/in/shirshankadas/) + +### [High Throughput Ingestion with Iceberg](https://medium.com/adobetech/high-throughput-ingestion-with-iceberg-ccf7877a413f) +**Date**: Dec 22nd, 2020, **Company**: Adobe + +**Author**: [Andrei Ionescu](http://linkedin.com/in/andreiionescu), [Shone Sadler](https://www.linkedin.com/in/shonesadler/), [Anil Malkani](https://www.linkedin.com/in/anil-malkani-52861a/) + +### [Optimizing data warehouse storage](https://netflixtechblog.com/optimizing-data-warehouse-storage-7b94a48fdcbe) +**Date**: Dec 21st, 2020, **Company**: Netflix + +**Author**: [Anupom Syam](https://www.linkedin.com/in/anupom/) + +### [Iceberg at Adobe](https://medium.com/adobetech/iceberg-at-adobe-88cf1950e866) +**Date**: Dec 3rd, 2020, **Company**: Adobe + +**Author**: [Shone Sadler](https://www.linkedin.com/in/shonesadler/), [Romin Parekh](https://www.linkedin.com/in/rominparekh/), [Anil Malkani](https://www.linkedin.com/in/anil-malkani-52861a/) + +### [Bulldozer: Batch Data Moving from Data Warehouse to Online Key-Value Stores](https://netflixtechblog.com/bulldozer-batch-data-moving-from-data-warehouse-to-online-key-value-stores-41bac13863f8) +**Date**: Oct 27th, 2020, **Company**: Netflix + +**Author**: [Tianlong Chen](https://www.linkedin.com/in/tianlong-chen-39189b7a/), [Ioannis Papapanagiotou](https://www.linkedin.com/in/ipapapa/) diff --git a/site/docs/catalog.md b/site/docs/catalog.md new file mode 100644 index 000000000000..0480f9423322 --- /dev/null +++ b/site/docs/catalog.md @@ -0,0 +1,48 @@ +--- +title: "Iceberg Catalogs" +--- + + +# Iceberg Catalogs + +## Overview + +You may think of Iceberg as a format for managing data in a single table, but the Iceberg library needs a way to keep track of those tables by name. Tasks like creating, dropping, and renaming tables are the responsibility of a catalog. Catalogs manage a collection of tables that are usually grouped into namespaces. The most important responsibility of a catalog is tracking a table's current metadata, which is provided by the catalog when you load a table. + +The first step when using an Iceberg client is almost always initializing and configuring a catalog. The configured catalog is then used by compute engines to execute catalog operations. Multiple types of compute engines using a shared Iceberg catalog allows them to share a common data layer. + +A catalog is almost always configured through the processing engine which passes along a set of properties during initialization. Different processing engines have different ways to configure a catalog. When configuring a catalog, it’s always best to refer to the [Iceberg documentation](docs/latest/configuration.md#catalog-properties) as well as the docs for the specific processing engine being used. Ultimately, these configurations boil down to a common set of catalog properties that will be passed to configure the Iceberg catalog. + +## Catalog Implementations + +Iceberg catalogs are flexible and can be implemented using almost any backend system. They can be plugged into any Iceberg runtime, and allow any processing engine that supports Iceberg to load the tracked Iceberg tables. Iceberg also comes with a number of catalog implementations that are ready to use out of the box. + +This includes: +- REST - a server-side catalog that’s exposed through a REST API +- Hive Metastore - tracks namespaces and tables using a Hive metastore +- JDBC - tracks namespaces and tables in a simple JDBC database +- Nessie - a transactional catalog that tracks namespaces and tables in a database with git-like version control + +There are more catalog types in addition to the ones listed here as well as custom catalogs that are developed to include specialized functionality. + +## Decoupling Using the REST Catalog + +The REST catalog was introduced in the Iceberg 0.14.0 release and provides greater control over how Iceberg catalogs are implemented. Instead of using technology-specific logic contained in the catalog clients, the implementation details of a REST catalog lives on the catalog server. If you’re familiar with Hive, this is somewhat similar to the Hive thrift service that allows access to a hive server over a single port. The server-side logic can be written in any language and use any custom technology, as long as the API follows the [Iceberg REST Open API specification](https://github.com/apache/iceberg/blob/master/open-api/rest-catalog-open-api.yaml). + +A great benefit of the REST catalog is that it allows you to use a single client to talk to any catalog backend. This increased flexibility makes +it easier to make custom catalogs compatible with engines like Athena or Starburst without requiring the inclusion of a Jar into the classpath. diff --git a/site/docs/community.md b/site/docs/community.md new file mode 100644 index 000000000000..bf5d4449b43e --- /dev/null +++ b/site/docs/community.md @@ -0,0 +1,106 @@ +--- +title: "Community" +--- + + +# Welcome! + +Apache Iceberg tracks issues in GitHub and prefers to receive contributions as pull requests. + +Community discussions happen primarily on the dev mailing list, on apache-iceberg Slack workspace, and on specific GitHub issues. + +## Contribute + +See [Contributing](contribute.md) for more details on how to contribute to Iceberg. + +## Issues + +Issues are tracked in GitHub: + +* [View open issues][open-issues] +* [Open a new issue][new-issue] + +[open-issues]: https://github.com/apache/iceberg/issues +[new-issue]: https://github.com/apache/iceberg/issues/new + +## Slack + +We use the [Apache Iceberg workspace](https://apache-iceberg.slack.com/) on Slack. To be invited, follow [this invite link](https://join.slack.com/t/apache-iceberg/shared_invite/zt-1znkcg5zm-7_FE~pcox347XwZE3GNfPg). + +Please note that this link may occasionally break when Slack does an upgrade. If you encounter problems using it, please let us know by sending an email to . + +## Iceberg Community Events + +This calendar contians two calendar feeds: + +* Iceberg Community Events - Events such as conferences and meetups, aimed to educate and inspire Iceberg users. +* Iceberg Dev Events - Events such as the triweekly Iceberg sync, aimed to discuss the project roadmap and how to implement features. + +You can subscribe to either or both of these calendars by clicking the "+ Google Calendar" icon on the bottom right. + + + +## Mailing Lists + +Iceberg has four mailing lists: + +* **Developers**: -- used for community discussions + - [Subscribe](mailto:dev-subscribe@iceberg.apache.org) + - [Unsubscribe](mailto:dev-unsubscribe@iceberg.apache.org) + - [Archive](https://lists.apache.org/list.html?dev@iceberg.apache.org) +* **Commits**: -- distributes commit notifications + - [Subscribe](mailto:commits-subscribe@iceberg.apache.org) + - [Unsubscribe](mailto:commits-unsubscribe@iceberg.apache.org) + - [Archive](https://lists.apache.org/list.html?commits@iceberg.apache.org) +* **Issues**: -- Github issue tracking + - [Subscribe](mailto:issues-subscribe@iceberg.apache.org) + - [Unsubscribe](mailto:issues-unsubscribe@iceberg.apache.org) + - [Archive](https://lists.apache.org/list.html?issues@iceberg.apache.org) +* **Private**: -- private list for the PMC to discuss sensitive issues related to the health of the project + - [Archive](https://lists.apache.org/list.html?private@iceberg.apache.org) + +## Community Guidelines + +### Apache Iceberg Community Guidelines + +The Apache Iceberg community is built on the principles described in the [Apache Way](https://www.apache.org/theapacheway/index.html) +and all who engage with the community are expected to be respectful, open, come with the best interests of the community in mind, +and abide by the Apache Foundation [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html). + +### Participants with Corporate Interests + +A wide range of corporate entities have interests that overlap in both features and frameworks related to Iceberg and while we +encourage engagement and contributions, the community is not a venue for marketing, solicitation, or recruitment. + +Any vendor who wants to participate in the Apache Iceberg community Slack workspace should create a dedicated vendor channel +for their organization prefixed by `vendor-`. + +This space can be used to discuss features and integration with Iceberg related to the vendor offering. This space should not +be used to promote competing vendor products/services or disparage other vendor offerings. Discussion should be focused on +questions asked by the community and not to expand/introduce/redirect users to alternate offerings. + +### Marketing / Solicitation / Recruiting + +The Apache Iceberg community is a space for everyone to operate free of influence. The development lists, slack workspace, +and github should not be used to market products or services. Solicitation or overt promotion should not be performed in common +channels or through direct messages. + +Recruitment of community members should not be conducted through direct messages or community channels, but opportunities +related to contributing to or using Iceberg can be posted to the `#jobs` channel. + +For questions regarding any of the guidelines above, please contact a PMC member diff --git a/site/docs/contribute.md b/site/docs/contribute.md new file mode 100644 index 000000000000..9b47fcbb283f --- /dev/null +++ b/site/docs/contribute.md @@ -0,0 +1,397 @@ +--- +title: "Contribute" +--- + +# Contributing + +In this page, you will find some guidelines on contributing to Apache Iceberg. Please keep in mind that none of +these are hard rules and they're meant as a collection of helpful suggestions to make contributing as seamless of an +experience as possible. + +If you are thinking of contributing but first would like to discuss the change you wish to make, we welcome you to +head over to the [Community](community.md) page on the official Iceberg documentation site +to find a number of ways to connect with the community, including slack and our mailing lists. Of course, always feel +free to just open a [new issue](https://github.com/apache/iceberg/issues/new) in the GitHub repo. You can also check the following for a [good first issue](https://github.com/apache/iceberg/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). + +The Iceberg Project is hosted on GitHub at . + +## Pull Request Process + +The Iceberg community prefers to receive contributions as [Github pull requests][github-pr-docs]. + +[View open pull requests][iceberg-prs] + + +[iceberg-prs]: https://github.com/apache/iceberg/pulls +[github-pr-docs]: https://help.github.com/articles/about-pull-requests/ + +* PRs are automatically labeled based on the content by our github-actions labeling action +* It's helpful to include a prefix in the summary that provides context to PR reviewers, such as `Build:`, `Docs:`, `Spark:`, `Flink:`, `Core:`, `API:` +* If a PR is related to an issue, adding `Closes #1234` in the PR description will automatically close the issue and helps keep the project clean +* If a PR is posted for visibility and isn't necessarily ready for review or merging, be sure to convert the PR to a draft + + +## Building the Project Locally + +Iceberg is built using Gradle with Java 8 or Java 11. + +* To invoke a build and run tests: `./gradlew build` +* To skip tests: `./gradlew build -x test -x integrationTest` +* To fix code style: `./gradlew spotlessApply` +* To build particular Spark/Flink Versions: `./gradlew build -DsparkVersions=3.2,3.3 -DflinkVersions=1.14` + +Iceberg table support is organized in library modules: + +* `iceberg-common` contains utility classes used in other modules +* `iceberg-api` contains the public Iceberg API +* `iceberg-core` contains implementations of the Iceberg API and support for Avro data files, **this is what processing engines should depend on** +* `iceberg-parquet` is an optional module for working with tables backed by Parquet files +* `iceberg-arrow` is an optional module for reading Parquet into Arrow memory +* `iceberg-orc` is an optional module for working with tables backed by ORC files +* `iceberg-hive-metastore` is an implementation of Iceberg tables backed by the Hive metastore Thrift client +* `iceberg-data` is an optional module for working with tables directly from JVM applications + +This project Iceberg also has modules for adding Iceberg support to processing engines: + +* `iceberg-spark` is an implementation of Spark's Datasource V2 API for Iceberg with submodules for each spark versions (use runtime jars for a shaded version) +* `iceberg-flink` contains classes for integrating with Apache Flink (use iceberg-flink-runtime for a shaded version) +* `iceberg-mr` contains an InputFormat and other classes for integrating with Apache Hive +* `iceberg-pig` is an implementation of Pig's LoadFunc API for Iceberg + +## Setting up IDE and Code Style + +### Configuring Code Formatter for Eclipse/IntelliJ + +Follow the instructions for [Eclipse](https://github.com/google/google-java-format#eclipse) or +[IntelliJ](https://github.com/google/google-java-format#intellij-android-studio-and-other-jetbrains-ides) to install the **google-java-format** plugin (note the required manual actions for IntelliJ). + + +## Semantic Versioning + +Apache Iceberg leverages [semantic versioning](https://semver.org/#semantic-versioning-200) to ensure compatibility +for developers and users of the iceberg libraries as APIs and implementations evolve. +The requirements and guarantees provided depend on the subproject as described below: + +### Major Version Deprecations Required + +__Modules__ +`iceberg-api` + +The API subproject is the main interface for developers and users of the Iceberg API and therefore has the strongest +guarantees. +Evolution of the interfaces in this subproject are enforced by [Revapi](https://revapi.org/) and require +explicit acknowledgement of API changes. +All public interfaces and classes require one major version for deprecation cycle. +Any backward incompatible changes should be annotated as `@Deprecated` and removed for the next major release. +Backward compatible changes are allowed within major versions. + +### Minor Version Deprecations Required + +__Modules__ +`iceberg-common` +`iceberg-core` +`iceberg-data` +`iceberg-orc` +`iceberg-parquet` + +Changes to public interfaces and classes in the subprojects listed above require a deprecation cycle of one minor +release. +These projects contain common and internal code used by other projects and can evolve within a major release. +Minor release deprecation will provide other subprojects and external projects notice and opportunity to transition +to new implementations. + +### Minor Version Deprecations Discretionary + +__modules__ (All modules not referenced above) + +Other modules are less likely to be extended directly and modifications should make a good faith effort to follow a +minor version deprecation cycle. +If there are significant structural or design changes that result in deprecations +being difficult to orchestrate, it is up to the committers to decide if deprecation is necessary. + +## Deprecation Notices + +All interfaces, classes, and methods targeted for deprecation must include the following: + +1. `@Deprecated` annotation on the appropriate element +2. `@depreceted` javadoc comment including: the version for removal, the appropriate alternative for usage +3. Replacement of existing code paths that use the deprecated behavior + +Example: + +```java + /** + * Set the sequence number for this manifest entry. + * + * @param sequenceNumber a sequence number + * @deprecated since 1.0.0, will be removed in 1.1.0; use dataSequenceNumber() instead. + */ + @Deprecated + void sequenceNumber(long sequenceNumber); +``` + + +## Iceberg Code Contribution Guidelines + +### Style + +For Python, please use the tox command `tox -e format` to apply autoformatting to the project. + +Java code adheres to the [Google style](https://google.github.io/styleguide/javaguide.html), which will be verified via `./gradlew spotlessCheck` during builds. +In order to automatically fix Java code style issues, please use `./gradlew spotlessApply`. + +**NOTE**: The **google-java-format** plugin will always use the latest version of the **google-java-format**. However, `spotless` itself is configured to use **google-java-format** 1.7 +since that version is compatible with JDK 8. When formatting the code in the IDE, there is a slight chance that it will produce slightly different results. In such a case please run `./gradlew spotlessApply` +as CI will check the style against **google-java-format** 1.7. + +### Copyright + +Each file must include the Apache license information as a header. + +``` +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +``` + +### Configuring Copyright for IntelliJ IDEA + +Every file needs to include the Apache license as a header. This can be automated in IntelliJ by +adding a Copyright profile: + +1. In the **Settings/Preferences** dialog go to **Editor → Copyright → Copyright Profiles**. +2. Add a new profile and name it **Apache**. +3. Add the following text as the license text: + + ``` + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + ``` +4. Go to **Editor → Copyright** and choose the **Apache** profile as the default profile for this + project. +5. Click **Apply**. + +### Java style guidelines + +#### Method naming + +1. Make method names as short as possible, while being clear. Omit needless words. +2. Avoid `get` in method names, unless an object must be a Java bean. + * In most cases, replace `get` with a more specific verb that describes what is happening in the method, like `find` or `fetch`. + * If there isn't a more specific verb or the method is a getter, omit `get` because it isn't helpful to readers and makes method names longer. +3. Where possible, use words and conjugations that form correct sentences in English when read + * For example, `Transform.preservesOrder()` reads correctly in an if statement: `if (transform.preservesOrder()) { ... }` + +#### Boolean arguments + +Avoid boolean arguments to methods that are not `private` to avoid confusing invocations like `sendMessage(false)`. It is better to create two methods with names and behavior, even if both are implemented by one internal method. + +```java + // prefer exposing suppressFailure in method names + public void sendMessageIgnoreFailure() { + sendMessageInternal(true); + } + + public void sendMessage() { + sendMessageInternal(false); + } + + private void sendMessageInternal(boolean suppressFailure) { + ... + } +``` + +When passing boolean arguments to existing or external methods, use inline comments to help the reader understand actions without an IDE. + +```java + // BAD: it is not clear what false controls + dropTable(identifier, false); + + // GOOD: these uses of dropTable are clear to the reader + dropTable(identifier, true /* purge data */); + dropTable(identifier, purge); +``` + +#### Config naming + +1. Use `-` to link words in one concept + * For example, preferred convection `access-key-id` rather than `access.key.id` +2. Use `.` to create a hierarchy of config groups + * For example, `s3` in `s3.access-key-id`, `s3.secret-access-key` + +## Testing + +### AssertJ + +Prefer using [AssertJ](https://github.com/assertj/assertj) assertions as those provide a rich and intuitive set of strongly-typed assertions. +Checks can be expressed in a fluent way and [AssertJ](https://github.com/assertj/assertj) provides rich context when assertions fail. +Additionally, [AssertJ](https://github.com/assertj/assertj) has powerful testing capabilities on collections and exceptions. +Please refer to the [usage guide](https://assertj.github.io/doc/#assertj-core-assertions-guide) for additional examples. + +```java +// bad: will only say true != false when check fails +assertTrue(x instanceof Xyz); + +// better: will show type of x when check fails +assertThat(x).isInstanceOf(Xyz.class); + +// bad: will only say true != false when check fails +assertTrue(catalog.listNamespaces().containsAll(expected)); + +// better: will show content of expected and of catalog.listNamespaces() if check fails +assertThat(catalog.listNamespaces()).containsAll(expected); +``` +```java +// ok +assertNotNull(metadataFileLocations); +assertEquals(metadataFileLocations.size(), 4); + +// better: will show the content of metadataFileLocations if check fails +assertThat(metadataFileLocations).isNotNull().hasSize(4); + +// or +assertThat(metadataFileLocations).isNotNull().hasSameSizeAs(expected).hasSize(4); +``` + +```java +// bad +try { + catalog.createNamespace(deniedNamespace); + Assert.fail("this should fail"); +} catch (Exception e) { + assertEquals(AccessDeniedException.class, e.getClass()); + assertEquals("User 'testUser' has no permission to create namespace", e.getMessage()); +} + +// better +assertThatThrownBy(() -> catalog.createNamespace(deniedNamespace)) + .isInstanceOf(AccessDeniedException.class) + .hasMessage("User 'testUser' has no permission to create namespace"); +``` +Checks on exceptions should always make sure to assert that a particular exception message has occurred. + + +### Awaitility + +Avoid using `Thread.sleep()` in tests as it leads to long test durations and flaky behavior if a condition takes slightly longer than expected. + +```java +deleteTablesAsync(); +Thread.sleep(3000L); +assertThat(tables()).isEmpty(); +``` + +A better alternative is using [Awaitility](https://github.com/awaitility/awaitility) to make sure `tables()` are eventually empty. The below example will run the check +with a default polling interval of **100 millis**: + +```java +deleteTablesAsync(); +Awaitility.await("Tables were not deleted") + .atMost(5, TimeUnit.SECONDS) + .untilAsserted(() -> assertThat(tables()).isEmpty()); +``` + +Please refer to the [usage guide](https://github.com/awaitility/awaitility/wiki/Usage) of [Awaitility](https://github.com/awaitility/awaitility) for more usage examples. + + +### JUnit4 / JUnit5 + +Iceberg currently uses a mix of JUnit4 (`org.junit` imports) and JUnit5 (`org.junit.jupiter.api` imports) tests. To allow an easier migration to JUnit5 in the future, new test classes +that are being added to the codebase should be written purely in JUnit5 where possible. + + +## Running Benchmarks +Some PRs/changesets might require running benchmarks to determine whether they are affecting the baseline performance. Currently there is +no "push a single button to get a performance comparison" solution available, therefore one has to run JMH performance tests on their local machine and +post the results on the PR. + +See [Benchmarks](benchmarks.md) for a summary of available benchmarks and how to run them. + +## Website and Documentation Updates + +Currently, there is an [iceberg-docs](https://github.com/apache/iceberg-docs) repository +which contains the HTML/CSS and other files needed for the [Iceberg website](https://iceberg.apache.org/). +The [docs folder](https://github.com/apache/iceberg/tree/master/docs) in the Iceberg repository contains +the markdown content for the documentation site. All markdown changes should still be made +to this repository. + +### Submitting Pull Requests + +Changes to the markdown contents should be submitted directly to this repository. + +Changes to the website appearance (e.g. HTML, CSS changes) should be submitted to the [iceberg-docs repository](https://github.com/apache/iceberg-docs) against the `main` branch. + +Changes to the documentation of old Iceberg versions should be submitted to the [iceberg-docs repository](https://github.com/apache/iceberg-docs) against the specific version branch. + +### Reporting Issues + +All issues related to the doc website should still be submitted to the [Iceberg repository](https://github.com/apache/iceberg). +The GitHub Issues feature of the [iceberg-docs repository](https://github.com/apache/iceberg-docs) is disabled. + +### Running Locally + +Clone the [iceberg-docs](https://github.com/apache/iceberg-docs) repository to run the website locally: +```shell +git clone git@github.com:apache/iceberg-docs.git +cd iceberg-docs +``` + +To start the landing page site locally, run: +```shell +cd landing-page && hugo serve +``` + +To start the documentation site locally, run: +```shell +cd docs && hugo serve +``` + +If you would like to see how the latest website looks based on the documentation in the Iceberg repository, you can copy docs to the iceberg-docs repository by: +```shell +rm -rf docs/content/docs +rm -rf landing-page/content/common +cp -r /docs/versioned docs/content/docs +cp -r /docs/common landing-page/content/common +``` diff --git a/site/docs/docs/latest/docs b/site/docs/docs/latest/docs new file mode 120000 index 000000000000..4ab810a8709e --- /dev/null +++ b/site/docs/docs/latest/docs @@ -0,0 +1 @@ +../nightly/docs \ No newline at end of file diff --git a/site/docs/docs/latest/mkdocs.yml b/site/docs/docs/latest/mkdocs.yml new file mode 100644 index 000000000000..ab40aa004320 --- /dev/null +++ b/site/docs/docs/latest/mkdocs.yml @@ -0,0 +1,70 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +site_name: docs/latest + +plugins: + - search + +nav: + - index.md + - Tables: + - branching.md + - configuration.md + - evolution.md + - maintenance.md + - partitioning.md + - performance.md + - reliability.md + - schemas.md + - Spark: + - spark-getting-started.md + - spark-configuration.md + - spark-ddl.md + - spark-procedures.md + - spark-queries.md + - spark-structured-streaming.md + - spark-writes.md + - Flink: + - flink.md + - flink-connector.md + - flink-ddl.md + - flink-queries.md + - flink-writes.md + - flink-actions.md + - flink-configuration.md + - hive.md + - Trino: https://trino.io/docs/current/connector/iceberg.html + - Clickhouse: https://clickhouse.com/docs/en/engines/table-engines/integrations/iceberg + - Presto: https://prestodb.io/docs/current/connector/iceberg.html + - Dremio: https://docs.dremio.com/data-formats/apache-iceberg/ + - Starrocks: https://docs.starrocks.io/en-us/latest/data_source/catalog/iceberg_catalog + - Amazon Athena: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg.html + - Amazon EMR: https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-iceberg-use-cluster.html + - Impala: https://impala.apache.org/docs/build/html/topics/impala_iceberg.html + - Doris: https://doris.apache.org/docs/dev/lakehouse/multi-catalog/iceberg + - Integrations: + - aws.md + - dell.md + - jdbc.md + - nessie.md + - API: + - java-api-quickstart.md + - api.md + - custom-catalog.md + - Javadoc: ../../javadoc/latest/ + - PyIceberg: https://py.iceberg.apache.org/ diff --git a/site/docs/docs/nightly/docs/api.md b/site/docs/docs/nightly/docs/api.md new file mode 100644 index 000000000000..286f7bd2254d --- /dev/null +++ b/site/docs/docs/nightly/docs/api.md @@ -0,0 +1,256 @@ +--- +title: "Java API" +--- + + +# Iceberg Java API + +## Tables + +The main purpose of the Iceberg API is to manage table metadata, like schema, partition spec, metadata, and data files that store table data. + +Table metadata and operations are accessed through the `Table` interface. This interface will return table information. + +### Table metadata + +The [`Table` interface](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/Table.html) provides access to the table metadata: + +* `schema` returns the current table [schema](schemas.md) +* `spec` returns the current table partition spec +* `properties` returns a map of key-value [properties](configuration.md) +* `currentSnapshot` returns the current table snapshot +* `snapshots` returns all valid snapshots for the table +* `snapshot(id)` returns a specific snapshot by ID +* `location` returns the table's base location + +Tables also provide `refresh` to update the table to the latest version, and expose helpers: + +* `io` returns the `FileIO` used to read and write table files +* `locationProvider` returns a `LocationProvider` used to create paths for data and metadata files + + +### Scanning + +#### File level + +Iceberg table scans start by creating a `TableScan` object with `newScan`. + +```java +TableScan scan = table.newScan(); +``` + +To configure a scan, call `filter` and `select` on the `TableScan` to get a new `TableScan` with those changes. + +```java +TableScan filteredScan = scan.filter(Expressions.equal("id", 5)) +``` + +Calls to configuration methods create a new `TableScan` so that each `TableScan` is immutable and won't change unexpectedly if shared across threads. + +When a scan is configured, `planFiles`, `planTasks`, and `schema` are used to return files, tasks, and the read projection. + +```java +TableScan scan = table.newScan() + .filter(Expressions.equal("id", 5)) + .select("id", "data"); + +Schema projection = scan.schema(); +Iterable tasks = scan.planTasks(); +``` + +Use `asOfTime` or `useSnapshot` to configure the table snapshot for time travel queries. + +#### Row level + +Iceberg table scans start by creating a `ScanBuilder` object with `IcebergGenerics.read`. + +```java +ScanBuilder scanBuilder = IcebergGenerics.read(table) +``` + +To configure a scan, call `where` and `select` on the `ScanBuilder` to get a new `ScanBuilder` with those changes. + +```java +scanBuilder.where(Expressions.equal("id", 5)) +``` + +When a scan is configured, call method `build` to execute scan. `build` return `CloseableIterable` + +```java +CloseableIterable result = IcebergGenerics.read(table) + .where(Expressions.lessThan("id", 5)) + .build(); +``` +where `Record` is Iceberg record for iceberg-data module `org.apache.iceberg.data.Record`. + +### Update operations + +`Table` also exposes operations that update the table. These operations use a builder pattern, [`PendingUpdate`](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/PendingUpdate.html), that commits when `PendingUpdate#commit` is called. + +For example, updating the table schema is done by calling `updateSchema`, adding updates to the builder, and finally calling `commit` to commit the pending changes to the table: + +```java +table.updateSchema() + .addColumn("count", Types.LongType.get()) + .commit(); +``` + +Available operations to update a table are: + +* `updateSchema` -- update the table schema +* `updateProperties` -- update table properties +* `updateLocation` -- update the table's base location +* `newAppend` -- used to append data files +* `newFastAppend` -- used to append data files, will not compact metadata +* `newOverwrite` -- used to append data files and remove files that are overwritten +* `newDelete` -- used to delete data files +* `newRewrite` -- used to rewrite data files; will replace existing files with new versions +* `newTransaction` -- create a new table-level transaction +* `rewriteManifests` -- rewrite manifest data by clustering files, for faster scan planning +* `rollback` -- rollback the table state to a specific snapshot + +### Transactions + +Transactions are used to commit multiple table changes in a single atomic operation. A transaction is used to create individual operations using factory methods, like `newAppend`, just like working with a `Table`. Operations created by a transaction are committed as a group when `commitTransaction` is called. + +For example, deleting and appending a file in the same transaction: +```java +Transaction t = table.newTransaction(); + +// commit operations to the transaction +t.newDelete().deleteFromRowFilter(filter).commit(); +t.newAppend().appendFile(data).commit(); + +// commit all the changes to the table +t.commitTransaction(); +``` + +## Types + +Iceberg data types are located in the [`org.apache.iceberg.types` package](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/types/package-summary.html). + +### Primitives + +Primitive type instances are available from static methods in each type class. Types without parameters use `get`, and types like `decimal` use factory methods: + +```java +Types.IntegerType.get() // int +Types.DoubleType.get() // double +Types.DecimalType.of(9, 2) // decimal(9, 2) +``` + +### Nested types + +Structs, maps, and lists are created using factory methods in type classes. + +Like struct fields, map keys or values and list elements are tracked as nested fields. Nested fields track [field IDs](evolution.md#correctness) and nullability. + +Struct fields are created using `NestedField.optional` or `NestedField.required`. Map value and list element nullability is set in the map and list factory methods. + +```java +// struct<1 id: int, 2 data: optional string> +StructType struct = Struct.of( + Types.NestedField.required(1, "id", Types.IntegerType.get()), + Types.NestedField.optional(2, "data", Types.StringType.get()) + ) +``` +```java +// map<1 key: int, 2 value: optional string> +MapType map = MapType.ofOptional( + 1, 2, + Types.IntegerType.get(), + Types.StringType.get() + ) +``` +```java +// array<1 element: int> +ListType list = ListType.ofRequired(1, IntegerType.get()); +``` + + +## Expressions + +Iceberg's expressions are used to configure table scans. To create expressions, use the factory methods in [`Expressions`](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/expressions/Expressions.html). + +Supported predicate expressions are: + +* `isNull` +* `notNull` +* `equal` +* `notEqual` +* `lessThan` +* `lessThanOrEqual` +* `greaterThan` +* `greaterThanOrEqual` +* `in` +* `notIn` +* `startsWith` +* `notStartsWith` + +Supported expression operations are: + +* `and` +* `or` +* `not` + +Constant expressions are: + +* `alwaysTrue` +* `alwaysFalse` + +### Expression binding + +When created, expressions are unbound. Before an expression is used, it will be bound to a data type to find the field ID the expression name represents, and to convert predicate literals. + +For example, before using the expression `lessThan("x", 10)`, Iceberg needs to determine which column `"x"` refers to and convert `10` to that column's data type. + +If the expression could be bound to the type `struct<1 x: long, 2 y: long>` or to `struct<11 x: int, 12 y: int>`. + +### Expression example + +```java +table.newScan() + .filter(Expressions.greaterThanOrEqual("x", 5)) + .filter(Expressions.lessThan("x", 10)) +``` + + +## Modules + +Iceberg table support is organized in library modules: + +* `iceberg-common` contains utility classes used in other modules +* `iceberg-api` contains the public Iceberg API, including expressions, types, tables, and operations +* `iceberg-arrow` is an implementation of the Iceberg type system for reading and writing data stored in Iceberg tables using Apache Arrow as the in-memory data format +* `iceberg-aws` contains implementations of the Iceberg API to be used with tables stored on AWS S3 and/or for tables defined using the AWS Glue data catalog +* `iceberg-core` contains implementations of the Iceberg API and support for Avro data files, **this is what processing engines should depend on** +* `iceberg-parquet` is an optional module for working with tables backed by Parquet files +* `iceberg-orc` is an optional module for working with tables backed by ORC files (*experimental*) +* `iceberg-hive-metastore` is an implementation of Iceberg tables backed by the Hive metastore Thrift client + +This project Iceberg also has modules for adding Iceberg support to processing engines and associated tooling: + +* `iceberg-spark` is an implementation of Spark's Datasource V2 API for Iceberg with submodules for each spark versions (use runtime jars for a shaded version) +* `iceberg-flink` is an implementation of Flink's Table and DataStream API for Iceberg (use iceberg-flink-runtime for a shaded version) +* `iceberg-hive3` is an implementation of Hive 3 specific SerDe's for Timestamp, TimestampWithZone, and Date object inspectors (use iceberg-hive-runtime for a shaded version). +* `iceberg-mr` is an implementation of MapReduce and Hive InputFormats and SerDes for Iceberg (use iceberg-hive-runtime for a shaded version for use with Hive) +* `iceberg-nessie` is a module used to integrate Iceberg table metadata history and operations with [Project Nessie](https://projectnessie.org/) +* `iceberg-data` is a client library used to read Iceberg tables from JVM applications +* `iceberg-pig` is an implementation of Pig's LoadFunc API for Iceberg +* `iceberg-runtime` generates a shaded runtime jar for Spark to integrate with iceberg tables + diff --git a/site/docs/docs/nightly/docs/assets/images/audit-branch.png b/site/docs/docs/nightly/docs/assets/images/audit-branch.png new file mode 100644 index 0000000000000000000000000000000000000000..3d6506a513ca1e0af6f290c3cd4763be710da089 GIT binary patch literal 39065 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

6`Q8;eo;*=koCH<02oVlCihezhy$j-1 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 Hx^Mm)WV8tK literal 0 HcmV?d00001 diff --git a/site/docs/docs/nightly/docs/assets/images/historical-snapshot-tag.png b/site/docs/docs/nightly/docs/assets/images/historical-snapshot-tag.png new file mode 100644 index 0000000000000000000000000000000000000000..6a6be3d5352610e100b2310bf33af3e8cf395b0e GIT binary patch literal 17755 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 ZlVz&!R_;bthm+~5-+!oFuJ|PQe*xlvKWP8} literal 0 HcmV?d00001 diff --git a/site/docs/docs/nightly/docs/assets/images/iceberg-migrateaction-step2.png b/site/docs/docs/nightly/docs/assets/images/iceberg-migrateaction-step2.png new file mode 100644 index 0000000000000000000000000000000000000000..13bb2444b67b3d0b7e36e8fb9bbac936d23d966b GIT binary patch literal 29566 zcmce;c|26_`#(OEkgRDaTNEiQPyz(tXVR3sj#2`9`FC3AHnc+QAVPCj3xAhtKLh4!x4N0i# zNneyG8robAoB$WJ^)=KH)Ug}LKm?U@+o}{c46|YZL{d3 zIj#oyAE5?U_*S1Ja`StoY|*EdF3K8?1Y&0U>c}B186U~k=W}hnMwX6N1m3@8Qds-5 z9^1kuMr5Ptz=pi(t)-c-o!A=>row)B~=zx2xLl}BU1Loskei)RDWAafgl&FYuiO39_DJru^T2{&oK*jENWY)zBI^D&~F=&AI%L z24p}*K@ZXsq;Uj=-ceFK17bZ-c?Fd7mR+vW2S0ED>k8 z)lY>}4)++bxANa%MDnKWsMhiqScE+Gj^n}_KWy7C)s-(Ga}mu{6m=Qtdkxc%r*FV_ z9DM_Cd0)WEC$p@&YX-Mq&+V-vIBV}ZQIP478&OuoE@SvsxA0WV3SDK$@luJ~agib+ zHKFb%A+RG$3`w2@9-v~GR~@%Z=NG)iADa?-enPwUCcWAX|%Xf8InEUJ-JtFAsuNM z-vxb3Bt64OXV7JDWIln#y=X@@L&q&s!hHpVa`;<!Benun)Z`LI zsz#l7i}_zDA6M`>7;k>Tdp6H`v7*tvO>aL9rFW-+caq^I71Eh1nVMQ6c9A>Ad3q=owYk;Xk1e=@UAga<*c`XxtooLb^*)~ z6UFoVN~isZXpQgKXnniyyA&6eL031cj?I(T14jL5lH&{L; zF$KF^BTu${gWs}Tz#b1i?J;H{86D@DMDH-yNGLS zjN5pLR8~9%{C?@otoY}Y)#;~YD-a$Y$}VsRZoo9m9KCI#nu%LhDv;UMSs0)^W4zQ2M)@ZwB(?=w4Ism+}Ntj9H^^`q|n6 z{kq_(c!;FvU9^=Y2I*72M0^)foA7{#?Qy!M;^(M??_x7Y-8YcI*HWUgx*hW04$TNl zC*h`@_gHX?$w&vvM({IHbJ|NDA4jz2ZdEj!wUy~k7UJKkI@TqOOU$=9tZbSjNVE)S zE(yOZ6&b&OMX#9>V2?}M}^z~}PdKHA! zNW?I*#U3us7gux@Tygf)ot&NvD`cPU4*(N zw!ERCF+lXJ_msd0_RXQ}{@Aum*6>(jYsmmabltY8yntpBcf~`_D})0|DIIg{&*05T zw1pp=?j4$KcSn6v%@scJC^&|>LM9~`3`p3(3&RM4ovQDEVT6Fr&EvQF)hy|tU&C)0$RBXNB^A4%kOPWppF{wo~ z`E$Yy)|foyLa_UQBb+f@$ePv!jpT`nHZ&>~aulUjHc!`Zra1Llt$W>M=WkyD@w(yYg2;iz5jm`@kSL<`>WV~@~DPoL9%+5#i z`CDU^WYH<63Ms@Rdx}hKFX{s}2ij~IjiT;orz~VK)>tqpdzAB>H<8NQoUyPgjhG)A z&r1bXUWZDoPu1P-CtZsD63_aQWz~>tnBXH8xIVNa?>TihHV`A+89Y`wQ5NW4OtoF% zWB&Tl@g9`Vxsfv*XCdIE&ysTy&om^-uOnvi_I!=j z%6{1a?q}90`9+f@a{W@7+mez+IYBHo(|=8WZSk=AhEG$0(Z^Rw9+qaqpPyQ_s(XCI zS3Q|YhDj^LrmHrK1SR9#&aFzgVzVld0=!L*#M}O!1p}_bhyl7y;|;H>)hA+p!lI&ic zj4UD$G)EvD1DK|vAJ=Azt#w$%i5Q#}#G9c}{mZ)Qq+kwPYm`g7eb&a}%TNe0A@5dp zK|sQ%S0+<36V+B#W9n1a@Xz&|JcDI^RmCBu=}1H3_n>0cE>mDa`UHOirVTs1 z&bj{k2#71+3Cz(urE6%ph5LPn9Nb`*r2#}&35(^+t_`V{Iv zRKDfdLh1gL^z5h#Y@hOItq78K?q2VzYzWnlc>`*2pS;ba1I7iD%0>Xlufqz+3l^C zvU-O$`K`c!rqq;_IMjRm6?}`oIn$~>)Gdm8x&J8Dw*e^;=Hw}S_(WE0Z+6~yx=n6y zXXaomtnTf{CH!-8`S;|gw@le09|x>>41K46CiCZ{Q&S?+1fQ0svEO3eN-E;K_1k#P zMOlD$9BUqE%kTI!*@fw=#{=4O!FIouu=?Wu)@q$;NOO-u88xCDBL_Iq%TUWEH({HQ zVU@4kSo2GRVDf{}R(W{9fi|#4iKT>v!FMfdc_BAh3#8)@s(liIVnbb}cKwgOJbzDV zCh3TBl!(@znuOWvN*>m6G`jUn>-Y@oIRYr=`&0Cw_!N$sItI)EEzCcnx zny}Ru<8hgGMz-E_a@zrqAnwbG+C9UL?i*e21z%S4yiB(g>2iZ!qQe`owZgrZU^z@(3%pdyx8> zBI0~zs>0hfqlVzjL;piUvqbIq=-FA2#d1EkC-si=?@9b_Y z9$3M0%-+WHBpc%LtZ#j@8T@f=k8&r>?G+2Z2Z_kB(L(KJ$7JxDV)REFB0x;}Y_jCx zB*{4TRp9LrUy7;P?BHRmS5%#d{m2<6p2=?IwTBd~ulJ4@wAuntZy9jv6y-4I2P6Eb z%9wrgK9d)ZGM4u&z6ztGFfMu1xQUK%{ClEm;?>yp3BH@MCQzQnlr0qwBSyPT>6n86 zx#8afiFFHDu3~5-|ZMGntHhZc|Iz*xjsqt0Eno z5%MPnGhq`%PieOQ?f&`gNje_rjS7w}@EIPXsDqtq>A?}yJK~LRw*6+ zWprsqyvn0y-Nwzhk;+L;*iG3xbHf=%AZOe9_JP$dD?Ai>DjBFaK z{uDl8)zCLLjFG9NzJS8$ZT*th11PaaUcqzEBB)Jr* z+0+$BI!MkYK5lBX2u|8ahjK6QZ_#5PiZ@kfUlq<5M5Df-JJb?G!E|Bi zuiiv_+%oR8Fq3bX3g+mut;FV69fPHz{;iWjIA~|fBXC+C%fs2!7J7m$Z==~+w}FPM z3E0vJCy(`=pkr{ZQ;_LVbfKLP@1zON=pDx#lI^B^MwcC_6Jr=;#`TaL-z3Go7!j<{ z^T2HfVG%Q0C}B}?myJ->V2aiMh+SJn922M${Gqr%^mIFg4VQ0Q3adTcx)OL${nqqi zdmX!Fr!cyYyCG*%ASd75s#^|zu%vt&ZD7ZvZxSTqF;m3=DatFIeY_%X=mB1CMd zZe6m@a{f-Pt5ExG5#!A4U(GG}-O>1)bzvz(O{F1ztb?&qhkBBo3NI=iUTClfcSlEb^ZXK0sOS zB>9)_4GY9rvhf2fk37_or&I-kmJ4&f7)`e^(-*|WlX!yJI!N-XgGleHhQQvCe$6ln zHWBLU97bH^h0wfU8)m+IuU*npOEe zK&wM|LDr7M$9e<0Mu$4Cy;rEM7zCznB4u<&ujut&8Zlzee5A@L+gJi4Qtvi(obWk5 zJSYr4?yG^Ab$aou;U@~8$})=r z9-;0CpX=_pPR-o@rOI&@5o%i%8!F$`VX*?l@&wL8klfviJ+b@7Xr?M#$Fmu4BZ&)N z-W>^0c2iJZITuYGem~+l$lH#M0v6-KL_vQ^DdR!FA1es!z@zzqcKL3ANjax+2^&Zv z8e`|2DG+*H{ch1t6?3f(!ovWMQ}$=X( zHR23FFZ+9qw7yhSF&Q&E9}!nE}!yC zxd6b{wXm4n{oUbq@Oe+pI{<%ZK0}t#g)KC@y?gY@^XJ8x9X7Y`@=4R%J=l=P66rr0 z@1;^-0f887X^4tw*G@J@I99jq01DsogRi6@z3D)`3wR`{xxqE$m}lm2;Ks=jGWiFC z(!(<#&>NN=`<2Ke4zUyaHmfs=VLeZGzLNgrZydF~Km(S#Q*LXTw3>l#b8go^s{iak zH(m&ExDo&>H8q%Xed^VYGdn0UqFcyu(5)W-L@RoiuO zX8~Pcq!$gImxSXlj#p@fnM#3xk@lvrJQU_+8AQ+~L|4KH4Q}KKEo8_Ch)-8mX{#nf zz~-h~tdnt5^6(Z;jZoq{_0+*+QT=3erfCPo1H%+mTc%K+(q+K-PO;~Q9&d7p8CK=8 zM8Wc*&e#d{lKp+jh^0sOg3?stRSAPQm4_)_g+5yTG*Z;kzGuCufms2ji5kH{vrK0H z7<#;hJGK_Bc-_b8DV&wwD8?Y?uv#VmytPUX5i0txU1vag+=ze=vaju(Da`_Opv9P{ z?KLIFch5T}xd@yU!Hy0K)mmWhh);H6WBsSCD)C=pwfFaVNV^-w@@+$}dbwewAakl( z?9~megWh4pMExzQwi7_-z`b?M@SY#Kp1Xw}PoA)I& zj_%JJOBVmO!@v*5Sow{*DX6yv#HKKc`{Fg_VIolc$Yzg|k7ccjx5$=~5` zn-UdY8b`T#%gRgeR5tKLe8!26R^QsodCZf7MH9+wNAHxzs`|=i6?zfKs}?SD{0hR` zmm4f8X_ljNHIVn+ayccVAKnsjg8Y6_!{2f0kq>y9L+ju6ztqjU6;mcW@XGc@UB;I$ ziJ&)93VYp4)5f&V7bR&IU#k{G)W4pCU7Ri)^u4Zpt+crdURI-uOc-f|+Hj>$7@$5- zl_qqw{svb!SsuAFq0?3`MO5`bv(a^fC%tGlwvX}9Wq`n?7H6z=<$HjX5*2j~`ZL>b zWMtX-@yI+mt>~$25-62EjmyT?r@nKaCDL->cR#k>Y$Y= z5mlqn$hZif{ylRLEwMgOO3Y1nZd_Tq0&9rl&7Z$zs(6=9ou~ibNU(|}9DKhB>it#; zK@z_!oK)J?6vQt7q`Ft*o{=S7dW2Q3iZ1lWW;_IEz7rg@X;d}X@@%TeD?v^AH>dxR zh)@yo4sJ&|d0a{0KmV)#xhL5Ww+~v6+TxtBp~(DB$U#Cda!U}&ifqiqEh~sI;SvVw zJj@#&y57Tdwl=WFTV){pG7t-&e-8W&+aK}^v8u{!&FvWVwAOkkO@(6e z457YOZ^{?l=W`z%pC)`Y{WYbTzg|v6QWlO*kjt|JRJ{)jRSg%YN~w z0q9lFpg`=V|5Twzc7=J!#Y`2s7RU>shLR`TuU2+ibZHOR71k+UAB;untWbUo$dSoY z(%5`S9Xgw{dH%|FI^l|MctDP>F4(AD?qinPfLy*ljgW4;7GCRhmmX-ZSB)8;cI)~% z5zv(W!t|5HAZ{Cs)=j!zVZHp{25I=Y!xwyXjRs0 z_^#U*7K5@62~sa&H^xM(s3)Lo2{UrT)0ODYTNYM4;j3I2AI788V4|YFkb~O&MSc5{ zw^z`=;n1Sp_N-1xEUIdut>I+_RE+&t%wzRn`LeQeq)Yk+`qI73)L>=TS1ml_9$ zZdQv=TP#+&>Tr*-r*NF1S$=m{jhY^Ki1{w1gcR{ZhiD1fPlXLx>momR&oYZ$8!9;Q zN~^=N^917#j(3&vGpmL5v&a- z6p$S3XP0Rht~_h}u%4sEHrU@z5yh9wNj;{a=XPE$Y(*adS5x#m3m9pk={HET?D{*K zB2sJR_%g*5Gv~dp?l-5D?lO8y{y~E}3cOGHWe2&wDV@2H@{`MZCz5GUOcvFG#? zHqPzFZwkQg|BrLvj(bE!zcVKnfR6Ui0H*m*^C|Ll)I`NIU#kSC6o7D`?ml?h6}2Ux z6oDB)QeeP@v)=#tG%j?#yXvS$UWPo5CU>kJZsLmJgaWI|5TO-#ZnX;W$XtVje_zB( z?Bos5Mg=~~YLhKqu$8-?fm|Y1yG)l1Wbc|+pIW?N!f*u?ulsj!hZ^9c7~+TRt`<}q zS*BEMMdJ|-?FI7F>M_0X+<@cq?bEcQNVXvL2E%t3=~2-DmS>u9vRm-n_3S@ON98;& zDJ{0y$e-l9fQ6sjsOdpP`G5h^1|O--D3!9)kFyfsZlW(49)-#s?(RK>23=sbmx7~a zQ*n$ZvYv-IFqi1Z_l{DrJ9H`744rHks%r8}D=c2Yszb_q0_iYB|@# zZFGnwJxV3_Qv8{9j)9rY71Ggcwz^I>V2_U0zxJfS)q*q6b@98nTg|v1>!{4&D*sO2 zN}tl1uPeZ$0;7&HTngL0DlR4JY&-t^e_STa$@PCO6U`~QLq|=ycCR>7E><=Bzm2^K zU4LYC+9fcAS16|9+Ky@O3T!-Q zegNsLyD;FM1$UDIau^BU-cTYm{GCT%YA-?^$K~T)tSAWc55O?g;K!;qCBe8hG|zFs z!uDbg#qaIkIVf$b;{&~K&fiKP_62X21YGf9qihR?6-QFYidQ)eMR)dIkchv`)NPO@ zH}v|7CvEP~ITBEebY?n~Go;bo+A{nqA&5(4!-t{3jl!Nm4*rD)maPD|`QJhMrj(H0 zc0|mj?58w)X$U8m*z?Yo$VN{K=8(H&_a5Wtc{JB4Fjlw{or%KHA-7nc6P_T;I+&X- z)_4n77AGG$YbU%$UX-WvTXf14Y$r;y{EQm(qX~Vmee^BlF_ekd`;*w|+!p~Jr9B@2 zzyvS(H8sGbK_(ELBRQ$@P5aMw)7ExKFR^C+! zFbq66?FJrXrX?35)>UTYQq%QdBazyzi_VqW&=$s~9}~*HyGlE;Zg=_QE9$o__h3B- z!}26n+}}{u)5HZt6`d6Zgg1p;lBpEK>1WgHv zt+Is{KU*NDWO%3p0>2ci`0`B`jba`=<#3T3DKDAkv+}x(8F57RdU4sx%~ww#Q(QO% zC-x48>os@LG|TaSy%?AUAlh@l4sZe#^>n8FCd-;2(>2ZOsva<vQ*A6Ykm+i!z)r zJy4sFdo8Yeq8>(=){A`@TR0};%KZ2M+bs@G4rQN?F+bXq6zwoh8}UU=e+cW2bahMEY!j0pb3%BDAaVw|E2 zNX?TC2akLiS?IiRQ-7b&!OD_F^_`=v%eJD)}XI0=Fx z#Vc!`n6Ck&Sf`WmJzs=j@7D{KL2!S?=ZRl)zo7Yc!UFS%Hi5~3?w5w01b?~t-K{gx zLCF!=A(9@s1-+&R0j zaBfN5E?V10tzB@}-=0F`$ZUqv7{x>t+aZ$29z$um2>t<hk+EQG`RHe>D88Gm@;nRmMaog6VbO_|V{YP!o@CP?S|zFS3bse29l(_2a?!2bmpo^-!wGvwNP`6A%n^aB@oUsuu6>d zGKp*M6Yd*Y_YU7j>oE3GpY8`+XcngCeW7JF)>RmveoD~e{71hId=i?dUa+odXB|-AAk#0&k6_jhQse z-q?Dv9re3tSpxNw8n{6QQ-QjW{%<}2^or0q9AkJ<2*gBySqDe22kd$3@0&@PjqUR0 zLXl z;lf6L0GTIBg>_If{oKc5VNvYJ?Wy#sC6mJ(F$!qa%ucHH-P29PVRrC`?Rlc2B<<~G zp4f&qCuz88zZ%8OygzjcyP@*XzL%qWB`RlY`)W7X&tE&jl8Bxjt?cKYw=jx)UbJDU z_?%Mn8gu4VP|j3X{&v?1E(I+2i{Z!6$@VE4aX9oGH24T=1|@GtDX7RHIM4YMMO zgW~IS9;!k0GZHhx8aPy;k3E9GWe>XEp_ms}T2OXAvYYvo%p?4f`JJB#of18ZknT_Hou8y-Kh2LHZVyV$4o z1?x}~wRCcT%%RISJRz0c%w~v_hbDusB1P48&F*OoG^ct@g(oP-2q%^E&}Rq%XNf_F}eA=5`g=*qaF{zYp6KZcP6>P0@`IjFg*ea=QJBDp%!JOT0&xx{i8_ zWdVESY|CW+i`3u>sj8)GeUdBfJg;z60` zd1~?mrfqjs5;iP3^^S=83Zy-1irJBERH?H^nUQVinVUy1zL12#JNKf^IyyTZe|KX&W9_ehfcr3e3x4q9H`>letgfW zZQZA`*<)|YZ|3|;foY&~G9)pDM_PJ%+~d;Tts1eDHy|1Mt+3`da^q|RGfs+%9S|V4 z7b$}~0;I26Gdp`^V3@%@5@Q*=mj=17vruaaKCsAhlF^oHg^CXiW z%jfgNsO2tIv-|q1T+sN!v`sHmc}-Zlp@2PkwnaFHU(M^N+5kK#UUtn*y_4JS+Bc4% zLhMgbO0&LNdNuF4z{$)uu5CPI4}l8T3(-LG-*$O-z@1X!7g)a!4IrD2yVPd1%h&3B zq&}2b1X6K3$ICm%hYfF~(0yrv2i?_ytx3>=YE?naBd&xdjC)dSEVlF)#S!t#U{kMr zC8Iy+iU5p+YqREFj{lvrm#=X_rqG08fbw4VS;=zSs%0f#=uPP#0=ZWlNP4}ky#sq~ z^IO`KW;T1EBK|8|rwkpJw$uwU>7b*%;K zVBK3I#=V&3ED>Mx3nh&~M*ZD~CDg%o={{yOYyKA|bj&I=;lBxJc^C$Uv0?~VmD{j0 z4CK$jyO^y^$KsLOhos6ANx!L`Kc9)!P+(E&^5-lXY`^7lVDDwzVI~z<4K{%87D>Ym z)S;6SqAN^+yXaPcGXawSz+3mCt!L9CJ1*LYL5HQm1>BCQL)9#^+C(7~$IrslMM2m3 zHcOO%`p`W(^^Ce}H6rJv*RMq;Jm=-+IortX2Vk1a&Sit9mbm7W)*iI9t~Jy2r~&4< z<^&R;p{OEViN*1*CXPw!zT>GgFogOkt=7AEW0Bd z^puAu#FIS>i|PG*Mho}};9@h0nAm>|`nrIw<%!~4K54XCRLurc)hhTi54jyQw#0|@ zIY>c_;OWT*(*hJTlBWp5AQM?Kymv+~`}lzU>OTo$)_H9klTOHlY^KGqbd}`Om!oo{ zy$ydxeh5n2#{TpVkHLB*RJ(FQ>Te43#kjg@k)N!apw%mpHU_5LFMu$`_{7$GkWb@ukl_GkhMG8d#uw zeoAkSW$>iLX)Fe0TG|K}0WefLQfKW-JF{weP^gOOnyM>nFUJCoIw&q{>! z?Vl{4`GE7)Ym;GHL)BW6z-JQ*(=Tovcg|q#U5#uY&^>o#IGpSeZ{E9p z?Z(Wn;VLICT0LDkTzb?SsaS_=;odRW;W#b4Gsgskb+fHW@W5U`y$n#iKN&%Haj zj-BHj8`X_a5nC|N`}2db3p`j4$J~wd?z+B>Io9rL#*EvAXpQcw5mGs|6zL9Bx5=45Z0hgB`94rZ?9IKaoc8k|H#qPeN^S)FIXns8 z_Mom@t@%7{Djv~Q3=5r4i`Ml-G_2nffqXY09uyjNgO--5^ihie_RuFM9Pcep! zJyF+XXk9yVcDn~C5x$TDa8tB%Qgs&mW?qmrXj_oaQf!jb+=r~j-8S`Fi_=GA^D81R zjjc5s2NjYFI=kE$!7V}Ip&`&ziAqRCiqie&SMHOf-#b=Wv`g^&T2=_$+~EnZs1jP_ zTmhHQh*%PLXDpe1r6DM!(Wxm#hxB>B@1^UU9tsY*h9j8#UgOJ|Zv$|?EzPGlXUD5% zGD_sGA09o+c|NpmOZbS}-PwQk9v1)9|yVIDLWVwK5$Sl z_zyz#x=t;^+{)A?KGOPA$X0swvgCD(nz7c+4*2g5$&X86@qBe6R==>8FKSk<-6+*v z&V_uszQsXR>&lIY?%ZnW&%H;xY&xxwNxPWcv@v&x%!posUmptIYh^siZK+u4Q^X_N*EnJF_Vw{G#t zPKzgC{a_C;DT@O65C`}@n%pvAeoMc$C@zV#KRF28x` zV)3*MO&Qodr}fIo9C{#R#cdd4muB|b*h;D2ZUrAn=S>{cUeGL7fh_3>=WH}J9^KfJ z_a~z^fRZh@sUVMo1B}9ckKdX!n>WBj7*YnVE?YFET>PDS+{DN<_KGwjCC*kTUFe21hx}d zn^P|GAJT0LeSuGw9>p;G=K|aDT>dW3^pUJx_WQ0p2bBlc(a5A=0_Vs7u#OY_=o&Bo z`sB863JDo)ocBN(SpZ#nsonR#+>I$lg9jV-qc|>sKZpO%y#lLYjO(oX^_=@+O%wHQ z%$5i^kRhKY4Wx%V(b*0^D&2iys!K)v?>9YemKZB_PZlw|IiLB#u>qj zj&41wwA{v4eqK;Ky7U?9cwi6=E2Wc{;4(P+t$ZZ)wXKs_;_GRy(bG-nZS;e&k5h9c z#DAN!JV7Pg_z;<~Br1^;MbD+B!Fcu*n^A4+JIpSJopv6$FEt5@j|vXT-s}7?UXg$M z_TAgso?$oIWWkr@D_VW3YZh}|JV9qBE~sD=zxL~9teTq1k&{vfa4vUk-ZmO}CPm;oUV+1>M|B!Y8k$R@X7F@Ch-Qi=Nt(&avvv}H z{YuUww9u3XE&sS)SwwKT9gkllued>;F0*T{Qr((Tx|Y+d{~jJmcxW|#L%WUUP(4!t z!LzOU5BL4BX$H%Vpj*>xNX)ghcPW%+L=?Nq?0F@pSZ|OR0J8< zwM2Zv6N1T8*Y8|w(rM@Wg_;cv#8%1Kh;6{hDGJram0NSqXvrJXLD_C14n@L6v!0b3 zwtpmSWa@X;a0toS-RH1V0l7DO)t6OS@nbWc}tI&RCUz_Rp?)gIBH>4hux1|Hp^}+K`(;AA%O@s&@ zwOYmz6>QIrw|OIXvy^OI?)#w4x-2Z8m0vFgptg8qZPthtBw4CcPXTg$`?bD9Ruq+UHz!K_%4e{m4%ME&y%eAIzJ`s&X#6;RJ`ORI@^&M8#JkY>~0i|W9+D)H`hAv zmV8SvHmCMXAvXvc;y=6)D!=jzL;s1u7WqCch z`khIHpP|NcefIAI!ZBJ&-@gaKZpf4f=WT5?Lg_SVh3Lrj(ss33$w}ef8yao7Ca)18 zRiX#`jLd_kiZQ7v7_4y*0%nt$4~oMYm|_FQ{gB=Gm7@hhY59B=!Y^?6hhzil!M_wV zFQxRM43a-4_i;6Ift2glMX`_rq1Y(*#Rsn*YV3-9!^ej;w$-!E+zFIRa>}fqc0QPF z6dRdgBzv5USG1zn4Cbo7dOIS(h`*t8S-z+Bd&ufQC5zm$7K6?rc(`5X34S46ku_yn zjYI#>ee!NSmnU-slIpkU0>NHbp0E$Q06Xs(+r2C`*VKcDvQ(EE4{t8pgLwi!2L2dH zP9KO3dOuJ9lu+Fl=W4)c#yY6H^QLbJHh89021r5Uybf>@GdHn`nX4!JYJ+`advbfo z6O$d|zMc@_-#f@rkL3*VBMS`I^=iLz+|0VQYqcNgLvW?L>0~7N?cR#)hQUrX^5;xp zkRAsZ56ef`Z!(e-D@hgqN@Y0DLG9pk%pj0lN2_2xB#0A? z!ev>;-gO&Zn31?Qb9R!H%Ms4ix|;7PhIu&cDlqMC2q&mx_l_sx^#j(UJ(}jD>l9-3 zW1m3{7R^wE<-j^ZzJ?--+jHizaLoMf+?_*0`WJL8RydoDtKoe}kW5>ZV`iZ=EmwL4 z-AozGi?t#Jgsk@@~qQ>$@*Y{%`m_jNsH*D2dJcO^=VT+bMNckDSb z$IfDJA8yfo(${8lylT3KbtRsVns8Xn~v3zA|YMR#G@hnoV)$r@yr#B86eA={onW#NowA%2wRdS?~@%rk= z^;kWLzZ@DsD8j5!4cavHRC~eg?`@}^$HTZ%MoFiF)~VYA;!zvri^MqTPAzy{#&ODT za*@?>Z%pWJKf5WQyq~CV671@V^!C#7<4TcnuxfGIQ$#NQc(|TnGN!35pM6&oLI2kqz ztPYBVY>e{^kgXzg{$d-`)<`GMB`-RDq%L2?33gZ;-eC&9+ zvVjpscRT;^9+zK9aYa+Ub#~T~SFYD|Q349cdb|L|Z<&E1a1t28D1onspWO6oqiqR7 zd!0NnLBG?vFL=X>%M!?)zJJo2E8I5XcidV2OFsjeBIDmXNi5G){=M^aCDDOm>Yk`N zc0)^1y;e7()0?01p4_?rsl)-)^vRm!(N`|X%A-Lc&wo_1CNYY0=}vUwE1Fg~UF_x? zl`6@x&EQ9{X|gzeHTQ@@9SYej=tTMu+cARBYK6$9WXT9Xmh3neI35 zO$^&FMpg7DRjanMT(s8ef34QmiiOJ`F zOgi;;kQ!?es_CnUV_gMR1rPURB3@~GBGNWT<&TF$g&4NC zLk?FTY{)zYlHC6PiwK($S@_Khc2yrYJtJz8TAC$Z)-Kl!$bN8^p>?Bry-NV(zpMYu zeC+&&fe8)C#M`1Ys;lJ~<11WT$3r*Ks31}~Q(9AGu$B+AX(i=&p8yi>e+mPb2d9?% z6m$caW^b+lYL=jKGgHI-%_CuFe&zg7FnHva9}9SJ)0(#eE09B8?9Di;$}p+$nY#}l zoQQ@k*~Mo(a;U!{=6}e&j%U3kfjl2ZdwcBh3}yee&+(k^uUI_en8{Z)<`?34rt?uw zuT`jO9S-%-^`mj$i`7l>CXdV?s#8EYZK2<$lG@N#E5}X#6#rZQ1-#-*8t(Z!4N6!PLd}kX zGFcSbBLCmWqJWXlaLR@eC=@s$o0AO1+>SK!dl$SW%{k}e=Mhz0Tk@~sRk2Be34}$M zbZw{H2t=LqhBSD`Y?QJ^NPmBi0?2cI=bV;Z{8Q>Nlmd5U5g$|l)>mSX$E?)0;!2{o^Tce?r zGGKUio2hG=X{bqBP3B^|$0^adk9#-#4^Y15kPyx-c8b@=oB(0;GC*~wnlaw{SHxE5 zVfO!TH4hLQ?l)A(OH-g!XE{(bvx6LGVJ2te(I3t+E2~3JzFteg%p9)Ie80gB>Q&=m z7-VZ)Z}MMo2I@DTo$3bwbUHt$elrISaJ&&OrfB2Cx$lqz{C)HCghWYgmuPRT;mZm?-1=@({Olg~lq`~vZ|So;)no(SlU@s><_YPuf)TOD3mbQ|8NS|rTONW-gdbZWSKc*{O@J~npif;p zrrfvgPkRy`j#UL}7R7Ks{#B0(k$R_?GN|%!FZ`%@AZ>0mVe`-5db59^ttj_#efBT< zrb@ToGhaOhv3zoWdhP9+$1}7jfr<)ws@k`1DSGaWVfE##Mep#}(3#@Lt!`OQ`Y%Q; zn6j3{55x{H5Ykf;i~zK`PBB24^vzR<78} zzX3SSJ2Vs`_L4R`_6K=ChifxliGo<7y{8vA>br${%b8p|z2 z#RuV6*9C%q_vdG<95_I+GB->r%BUy*!(nAggd; zf3Gb-Q(V{kdn{@-szPN8fYhYPl`Be$e$Q@GO6MqPTp6#ts0&meKC0HBKOAX{cv0id z4k!>HcZ*beRuJg-1)!ocl5Y5ovi@?U=SJ5K+sPmD%+|)@9TVI%b>+!SCH&9f%)o(d z6#i^CX!s4~g^Lk+f|C^sPHNURhV9F_eKuB2$=!xnmr|7F6=0vGFT6~}F2KEz21Q&? zr4F>Y`exU*lyF~W9b>3_!e|Ls5e**(Jfr<5E)4wm?)lro&p)fA+J8`bb@*_`smno$I_#M#}8DfBW40IP7y3Rwd`4gKY`-3<7p|sVNxzn~v z#4ir848G2rO7#@}BRK^p$7?F;Zc|X!-+f3S_q0&Tc5qE;87JV^e^C|<>dIka*!j8; z>}cOx_0?cKjGCRrpk}1R*Yz<$_6%#+2dY`#$K%%8I|5X{BU zTf*%Zefzch%V(jayFL`(RXPKT&u(*N3;VWEhSQ+Y!Cy8Oy$p!D20PzRTPvaRt^#Vw zU}tYp{ID1d%3blXhwlFTP;_$b!~FKu9BYm`1-O%@?}J6^!&U2O4Ny#uKLwfS7d*Fh z`ewfUKvYK+F|C??GTqdCMW7(vNc1Di>*Zm-nHxN*M6KB*-4_1sv38by#Q&$a?~H4zdG-$m0i}c{po9R5gkq$q`W}OUEU0CyM4sOW$`*jA7^@hyw`Mv_KEhl*ewFX?7LT&^cEH=) z*B2`!@vruwkO|nYIF#!I>8FvJ-*SHXtLalWDqHxIY};xc4VEiFCdsvWM@yWsk4q-X zjN5k}!`c(JScBU3ca$~)jzaC$;uQ9B&AeAD4i=^7w@^e^JpT{(niD9Hy0+sGrOnOk z-m-ZVeh`4puuQ#Yfam_?YlN>p=-N4Iuy$XupVhUu_x!juio%ch|ApFN58;oqDg)3u zSief)DjcfIUg*a-L_F{rAgo7<2MZXQYI`60wGm<-o&tbC_G<6d_j3-EkCKlExV@*p zpl8NC1EeMj4F@yx{M!CoUjR&$D*FJ^1UVNA^W88qJ7U>E&qwA19X>%xF~fTZPYj~= zld!075ra4KP(NocR@e{y-7T`#@F$WjUJZa)u`?p0&}%sYHKd#6YOOD8=+Ayb zr!%QG)h$pE0dYxI$p^0zyJ3u=gehJu_Zu^R2 zdjOk7BCqyDPDv-CTf^sRyN6B0LH%SculG1#Nf7xLEg#AkE!Gj)m zjc#t5DcVnkTpu56U6IK-Gs&twHm@z~hGA@?&<91&t;WuqS;;kA&>>3OzAc*g4cdFY zH2ql2ftrF|pKdAW`IT4(FNXzWiBedE>2!K+i7>1OB-aGpR#5KWV5HEeakb=&eos5_ z!b_EXuuxU*`Ow45ABZ{m=}M6Epncv=1%z_;cDe|RhfVBINST+hrI%&&_miT!gognu z7S1dmVn9*`NVQET-NMmRih)%6Y@yEElvB|3+rc1S`W`xuuc19Lmzld>zT!87fNrXA zXa+ELm4Rt&0yX9wZABdWYop&*&^8kUi0NofS#D4lr3DmjK&uWtW=ji|Y@l>b>GPdj zou&5`pRPJ%!ig59x<=O|vxnqth*jr>*D$P~tPM;l&E0mZyTSGp6T_67-5@Jf4y^N} zMydSC2B+Yf_r})n$J*xsM0ruLnAcW1d%lk`MfNZqP%{juxw%0fG@dsGF@p ze6V1BHl-a+xDYY#5fMOjjiX5>7OSexGEsS5@JogD>cg@7Oi(dN#xA_YXzRoOs?k0| zgTS4st?us+d|4s)aGQS`l&Pg#A!s#vz^rAY?9FpK|EQA8Gk#hqb`m6p?2%A zB~&W(O`%%+M{31TS9x2mv)}IuGIupAfmskkUqLp7loI^fl{a!8nQtCvAm*Z+5t; zn>c5AHz+iGBsx|&@K>)nL%m1n+lFm>xCD3f{U6$*>NJDXZW|AypJHog-Gop2tA!Z?fq|dg%bJ22g++>Z=_%-h4 z?a-nEjcBm`03HkK$4oZXn~lB>6FtRof$o|Rl~PClskqlin(uV9`^IM)zoBGAhMvZW zns}HidL{RK%PJFXBZI_13t=L(b7^ocT5+z>!CHuk;?>QYaXo<>9YULp*Sa_vkSt-> zGlgp}O{ncsHR$KESKrzl8ICM6BO1OFj1-$Ce5{6k6g!puX?FwT7Jk^vm$9jI*tG2q z!f1BAmia7ERM(o^B+uxIz3Pv?{N3-|+TF1YMwY8;{zYeh(mJ!WW!gcIzZ_L{`#``GNc451>m zH**kfS^rco!uxMDXN(kgJE;X+W3pJ0v%PDeOHCI8({WCP4pBA@yz}e6LY5wCaWCNw zI@8Wtz^l{t2QnJ5W>>Nfo_($>Ps48MEy@X<^~t8tXdA7Z-|QL1h5u0aZa!S2?xo3e z|KiuPJCEq^au{iUfkEfWzI(rHu##zc6zfiNzfZEH?i{cBd(eU(6^A(CHIBfKb~#T6 z9=paq#WfJ&9G-yxrIND}XLPY8uV&>xWR&haX4sl>d`^`A63 z-xrw45Vd6Ab^4nTg*L-(&_=gM-tM1GZSpaKVB*WLApe?hCp*UB0i60Cg9*i-r1q{8<_S>!ZoA0wA#w;i6*Da2Slqr}Zm@$q%)4~C|sCn1;u=Vu% zV-rUQBeUPO6#|9}x?Fb-TPo1ryoKxgGs*Sj7ERFrbKYkB-~{SWv0W|om*UWoEa2zs zq$c)u#`o)F;ysfTk9$1ph}7JEM`Ooik!5zXy4ewtV&CO*PxC73IaH#f(XsI?zGU@H z+Ys>9+^$N>sBL01bq8*mmr6o=S>$Af4h1Zkc%li{*XxO~diBS1LT~_UtTOsPtT1NZ z(;}~gW`8|+z3xBn**ax*^yPRnCf~)xH+^v8NeQh?g0-gM6SCsaTvhul>)y41?Eu8i zF|qS_7VqycJR_UU;D|3roUqA*f7X%Qk2p?RS=Qn9U5;xG@^|$5-Ff_5XM#K^>fau) z)79;dk30IQH%qJ-c_`pmgDO8>?yg(D%I&pN;a`uqnK-_S(yZUW9&gF}Yq%rudgyh{ zkrM`mw9l0J`PADUkItepm(X2Z76bW?)kobQr70lBbF`L zIkUKN<=}Oak3P;N(2O-@DUW+<=ztBYFh$NApz{4q*?f?@yJsNF9Uk`d$GLmP-y=$9 zkh_Wp4I2h1hm0z^oW)DRmNgIAzCG!0>cv(s#AI?2Seq?b81!uRo2IV$6dg7^Qj*+V zrG>}!-5f%fgf*Js^?B_DV$uk&8Yxy}x!bU(&RA2VKVmu$UQNYcHhqkZ^m_T+K(w4& zGW?gvfOE$ZG}gimf^-615akJI&@|8p`EHX{_5K=T5BT|-SOhXWvrzqh1icAep+My8 zAA-ppTB7c2wO{a+31Z1aX#rpe4~t5Mhl!0h#9>kFJp)i*nuW%9oOj&SriwU8%F{14 zUnj|B9rXvhMQyfSeorW)iF}N3lJygH_wy4{a=8bgQXsmYoqcbsI&|yC99ScyW&Ru& zxR$`vRKH=pR=G8QRx$p}Oyw8;79UmVLG`xRKBNdk*p)^5h9{!#>kw&n;&!yN*yKu@ zY@QQxN*uCU-StT18qJxxIRA}JLGJyM^lxsqm;JVD9PYon5Y-@EY3le~j9-Ck{GH)m z1I6QJ!MNl>)0bgyI=gmM=F4Gpm0KF>_-Tu?9u1BbuV^(x&Rp8eI8D%0+O{KlUOo<4 zHYfsUnWhQfdDO~(SHX?zQrasJsi`(_YQWp_i{xe-V@c5NbBoQ=@43mRmsG z_1+U|LrPZ8wT!rCLAB0{#@7C0pSSN#5GIN1S&^CRsfmX(F%WBzJWs&&ojd4aj!PJ* zItKzcBGNdw7{vGS@|#4(gGMri)-BO6VyD1_l`~wpz?b*}fD)QOE-?-zi+vfSed<>= zYWp$EDX$`wSZEGNjBT47M7t_2ed?eKKb%>6mO4F*%fmLK z8nOlk;Qj5Z#j&k1?Y2g1VI?R|bwAdgyE(RGZ@ur$Bo}?FNO&(hF?6drEJ*eqdMoPI zFArA5<`Dd|UgBG7@Y=T1Zr@tpBFF0I$1)M>$)N^SD(+j>fgS_~BPk7hx^!7*PI7wolD5VRR z+}9+w+QA=Z?{~MA?VvyFOJ=>2_8-025}V{yH(&eaRZna|tib0*TGZ3@KnE5K+nlLmdSJWTwnj}CsT zA5o^Dbj?odw84+a%@2xRY_%OnszzL#Pxj0|`Q+d_wd|O5C5^v2iAWCmG1Vm~ZQsN0 zCT}>M8S5br$O>=nfpM8PA89bQ<0-dQ;63!=%s*UX?~e23q?784sGq<)+VBZyHKgf% za*@~;)4AN*{MhL`U+~C>$NbkBr$*bttig|4?(R>X&&B{-Bj-L&Kf4)d2y>(Q)5bM{PnFZD`rAW0d_tdKOH{p&i#fvz>b4D& zi@U&n-vmCXd!^XQ?mUsNkIeH5@I7+JB+BN&^$0F)=z+Mxj-7Rxr9tl;HR$)HB76SD z-OLHT657!7vTZrl7IBry-rV_o{ZZCWo2p_ETuLTLM8AO|LlZWR%j9k7C0$^QFT~f+ znfi-;knIe6aRt(F-QMif{Wm<_wN>7Sw^lBj4R>iTba8_QZ#v_XX)PgMpE2lUKXOid zABr(K*jyu)P{!C9+xVHb@D>!KAMoS^z-Blx0!7~Zb(0Iejm_Pw=58YMEF>yIUV4_x4hb~K zMt~uf>)v#It=D~`jd;MqCxnLgjGM$(j@p5SG|-3$ z&5LWy^27`!D=9x*X!-2BPuvB{N4Fif%%3E0vaM>V}d_#C<^Dx=)1kd(k`P93&!`u0pR1cPqOtK zz)r+S$La0Bf-`AO5PHe$56i>{XM)F4?pUc1#tv3|)@WLKMNTI8|4i z&(wC=T7e9cF72-SZM?Q#LoDpE6OR>4j_;eI`1rC+hK7&py}H%n6gKyy6@8_r&$Iom zaA50#m>e%LmTX@6NFCyJ+?0<=JCO&)NhBHh9V;VdZujocbGfxh?gaW^XFK)bi0|dUun{cbM;rwC_rm1?#A?@ zn%SQfoCdZLFQXEe`JbEg;$i{_-1WJUTUeRFMF%tDp~n3=Bkdj(n=Jz@GK=KXvK6L)_| z9aH{Q+lqF<#Eww+YB{@WxlwXsvf92Vv;w*RHG0ZHS54u{0@bMtQR>oDD>)hcHFs5g zH+7#rO=6`@845e&;_QrH30lgYU$(qKZ>G#b4Yyybs`QmJV-ZiBuHU(tDMfz77Kv=R z4Zz#z^Wl9Yw7gDNwlCW?Usp%b_E^xq7)J%hN7S8g=y6LDyC`(|}rJ2tKY=m{9CF7La zRq00+{Kpt?Y1_kQn`HFIws`t~Tb-$!`{DEG9uasV)Ey%h3HZufCpS;TpT zt38fE!a@U0n~zwkDQfPy%NZNjDLmV(`sLh|p8PW;Fnl`FU?h>*kqyDX)ZG)A=(Rv` z<^?6?%IyO_5@W$vmRHn)s&cNAzO2Ajvw)#nyi`W$648veZ|IDk1=1^A@ljnbM{M8~ zH!wpZ*TuY!{O_XGN2%2eAgT}_K`occ9Yr@Xbo%_>hw2v55~Xay9W_W?sDm;)-#esJ zv2-I50t!*h<;)9pr16=UFEz!?D+KzTZz>-T9cN2)6Fn7p$+^4F@QdPZN2VTp zx1&56<~s)&zeq1`QnT5BUGt6WvQn%s<;Gz+m`OeCe=bfU;o^|(&u?V8QJ3C7k(T;Z z+Lx&}OQ5R>f=7#6K#;+IE(M{FWi;B*h87O@xp!?H>xflmzEgu=Y7Z{jm@g(D53@TQ zP8KMcW~ycDjqPrc!#m9(Gq-SRN`EdGZx!V@z!g?iiTmvydu|JlH|rm2g6dNb1)oAMH163MfL$`IhWK! z&OTH3DF@G}et9y_ur+drj8?mj!+15qNYn@pQ5;xV^WcK!=3Oxn)}1*5Vs=rmV%8P1 z3^a5af#Y7WK$}BATxAqLQ&5{*j()_!n1F5mU&_BG4i6?wWm={Ee|JGfk2}__6__l3&Cn&)aIcX&7Qx!GiOPmeo+W_GJr5x|6&G#Naj#QwHQBFg?ZC zxW+5(Om~0gNP*^QY`h6i94D>71?M+|tV`hOUrsl`^52vq$AHW0QsfMvy_cT74KZer zNRe;Jw=$PmIQNMPOwJfT?zL60yHbi=420WJBT^V}80Qmi>M(2#XYV>aAmz@FcOPV5 zx+r(sbf7pGt_@kqn_qh=;CHo{JHO^g;xBipZ1ZLJb8 z&=;4QKz+ zAm|YV^_f(D`CBU#5;!mkbSK{q0ur3$;k|A}9SK+d!{MI^tGTpiMIWX(cZpKX1&wq6 zvwr|~ng$I6vxI?1JP!P;da#yrD0#~ zRR9gZNC77)3?y(EH)TMREM;!-BF!~>l3)PVS|#y)!1DU<2M~>}ouWA3o#F{Evb5`6 z(G?eM@$&=xLxQ3P%mDYWrAU(!PP<%fBSt_Rr73|kzeKw)&e zp;_*;zsS*?PL}X_VgHNL*&&Tj;l>;eoOhAMMn9_l)Gba7#$^8-MBAuKTqQj?w7Y zJ9yaye)(2Q9HwN59#yJ!2E_yDlLcoD%m6?#nYSvX4s4PESg|jYuuk6H5y?<7sLS|p zl|3&{= z`yVZ{dta=Je*uuELU2%bfZ?itwUB4?SeE{f+YEbtN9tKYa)rgV5kK~)y^EwyvrkK- zs7{g1efLo$p+gG$T+@;ILgvf5#}OuGa4B&sYHvEki|RUnCY#Zt*@VCORhESVOEv<| z=S0YovUO4@VL{|2Yktjl{e!lpHk_)lYE~{z+>b_YVPk&u>Ed9&}R{~IG$G1Yx9`ThR8D>dj75w+;v!!E(%Q3=lJ&yZDm zT$lcuLw+Z7Na*i5zmzPiEFb&hI3GJJ+1ttG>qr*QD6lwk_#~!qy=zzaIKp7M+CO&yS+z>} zH@nq$C2i0-3-I@6Xa87*ulzrO1D3%NdTlP0y|ORyv5WUo5i|!MO6*^H%d?7p@|ES0 zhZZj%R@$*(-QHXzNnTsjV2+a?5>5WuJMSSE`ezP~s*|KgmArfZoKY zbbw9V;yr?(7?mg;-FscPHHr(SP(!!Y&P0T8e~;yQchtuF!l=xUS7hCH;D_@wttT1` zKlJ8DN>GoMR=R-*lWfNd2foK;U#LD3=<2Uk3L~6u_8+$AN|LJT%8(K# zHvAVCUq;sdbdM&$|Nq5BTW|m{rvj;jT&{n($N%G+;NRR?bpv3gDgWW3P0ZP1nS%ef zk3@>_Z4Ki+BV) zFT64ZV6U>M)Y3!zS4QTY`RdkN|81pK)69r)}uL3&ES$Y$ural%cmuk zGLXbf0LLPXQ}JJ@+WDew#!62^EvxGk4@4GyrxA~U^|=TR+`zWNlPKxe+g?u`xB+fb ziDiNQmIIu9BW&;w8Bl3VIK?H82$@#rkPtgy5iNvM1LH8y8et4GdwR9YvB-dfGQ-~o zPk}Jv=w~1MV&a(D?rWu(5U99Js|T=h4uXFNoIPY^N(^AY#}jT*XH}6tXMSK|aLJhD zsR)E?pIxz_#bG`)z!<<3aAy}s@Si)LU_=TP4&wmy76b%bl#(3?PDPK^Z!)c$!6BpI z6^m_i2qF+34FgC6Z#z1`e#|EVbCqeu?4HC1M)jnvSH=9X&z0C^S`Pu@K(GY_lT$!6 zbqULG53Hdjws7zD^@CB-I(=Dad7_#9+MAQ0XTYcXs2TZ*^L2)GmLu`eB+m{Qfumos zxL^(e!c35Myo}##y2rYpIUN7mGNY$Xnk$i7>2TBNBz%H)T1=nRe*rM}PtXDe8GBD+ zJnQBftxIa3VG{5Dl+`{bDMnCymFArzlXwvIHYxfta``J|_s$#N|@MUyv$X(YU3h~z9jq5Li0FNja zJ0NHLihRmrqi)TrcAFAZ)mzi~!!4zK)sngKNgfF0U(p4iQo&o)OcJ!%$6k|{JWX51 zsbuMXJc~Iet1(8i?IR)EGT(na9zTC=;g0!I*o%~a2diK9xJH%(LsqCx^qCo0QGh5V z?cP~P_`x@07t2j*EYXA_`!D0MTB|~YPYMGDn@;;#VCXZ5ImFQQZ3f*+u0kpDQ`K!< zDv~}W%?q4XmbTgAN$ADkKO!8Y6T4ZWw6o53uI~d!mAA3+=XvhY_Pf(O`(T8OC{FFM zIb{AcLWUc7F7;qUw=1vR_PwhZA2#Lg;gVNnZ8rtO*ML$aAsC);n&e~(==`suI<0bt z4@=HCqRkGJZdkme*M&h+08J15{rZJ0@G9YqjVv_|bN)ZS!OFa?Mc-?s1Z>297D6pG zoSiKEA70jR?(aX6X`Km%vw}$abT~B%KzXeCwnuK=ejj{eth>sG>${ok@=Al|6bB0- zePBsaQk08wp8CX=~6YYb# zy|bcM;D>Dy=U!dG7I75o+-~Q;vBP&icC+Q@l?u@Wa=*rMcE}HvET)wyJ9EhCV0Z@o zN-j5JkCVhhr8^^QVvsvS4h!d|aAug*J8Sk0DoMZqeM*rojWAuvNyI9dK@UG|2xm-W zT235yh;@Zw-B)E}#9H_ru9BoCdD;OP(&I2<4KQ6GDib@M&-!UlnwX#=)5;VChXUqG z5io#&v;I#x8w^1Bo%~4(Jo)$`T0MK`K=RlPoOlw z#3C3^ewaf(=={lZ$s)X>0M3#Cy0pg(pZV8!L(&vYP}3MiA%Tj^Tw=MJ9g({0Nu z5sr);{ienYUC*D~YbbiHSnD-4jmj`%y&301dGfzMCGc`cY`FgHr%IO7jzfQCB1`RV zvF03+vVGP$!;SxBA}1SX73fD>83>xyNGa9Mj=(+1SwnNUwh9yck3=Mp{#?>2o`n5c zZd+5jyNMRq=5l2Km`{K)`^G*_F1nP_8ryDhH7ETW|sG2Z-`|wz9x^W?3_D8}H z_kC{(O1Lszm2thLw||;ou>z--Q2G<{NI5xGcxbJveFuF)ZgoVYL?MBH)zv<>Od+Y* z1Rs_-Zwn;i$fCB2hdeBRq@Y^YQ>eH8CbiM#mA@lM%XD=w!)&O9B7eVUes8jK&)Sh?30WQ3iERgyv#<9I3Z8#%blAyg^60npI9{{odrv&7 z*An~0_vQTlN#WEzjyJ9!X?dEhemeKS#SHJtsKKwr5&aw*XK4#Hw^)XnLzOW&my4Q% zMlod=(h$zE*^N=-4s7fH-&czyVI<&Vjk zp#JOSB(^;YNAay7Q&$q)>%`I)Y9Ma#1rY}O|Rw;lL|;`s8E*m@Op#&*3U^_&f!HrxX2#ch|g0#8N~_ijqdR78e}KErnh>m z_9OR87JT{B&H@jH13ZFMke!+}n6<`K0;l1-kH@g87xy9AXMyh9?{O}l59IxBgCP8y zk@<6zFYfbz-VIMG2v+oft`=g4=BkXIYzI{rZz3&m`XI&lIqY1!0SGkobC(`m57d=2 zhpqeZLI(sIi~;S+x7g+i(m&3ko{!va`f6@dl%>xHvZWjY=Ktf>^1yrVK~Vl)&qLcx zPx5L4?|3yMZ|=_L0p*D0^CUk5?E=mG#U&Qc2fTai0}TB0Rnltw7bx;Z{ZldhQb8i{ zPLRmyHO{~k{}xb2nsGwUL?89kW0=;lyd_jt=(>Yw_uuonNd&@`H6Bmy~cSJLa2|3jY@#%KCE-0!`Y`dE;z zCXK!8I4EB{o^SN>#D%D8Ap=0ia?@x-ZO@xUPQ|@*;5gEeaEd~pqZ-GdPPE&+d_9Hw zuyWy1kRyV2s=9eItj0rx6Nd|sujz~~EB`d-v%U*$$5)N&ozy_f`c+R2o=tQPNC*t1 z4pu<{Z6Q^d%av27s1HQ#(D<4um=(G-6XCfH21T)GyX#7?gs6ukO5k0!K z2K?#lG`vQ+rtx{^zDQ0cb-=A|50#<1<+yF!5=5^`Kpo1J#20x>)Q#GQ!y!I-dXHrd z%JH}6PwTDS!R$pAKCSzn-XQ3M@m%()!PR%@;U&@U$wZ6Z$y@Yu)!&^Y)Jbeila&mi z_l}$s+ktdoC7d4SL`b06*%$trzJmy3Ldr;!E(cJ;#KnEhm7|l8S}Yt8{7jr-+@q_W zDsMkb-|0AiOaKA+st-tBl_T$Y3KPmH*9+OQBT7Jl=vR!pKHMn7kArse43FWxx?$fx=hC86^ zv?G?B@Jm+i=tB4y6a;FyEqlAnJH;UJ<1^l?BB##jgKKVUXT!sH5-YDoJ$i1J)m_mj z@|fx4GFovs;QS7}MNY3_xz*~(D;oYYKDPoH>LNG&kusNQ=_PB$6GDdB#kQ=T&>z#= z@428n)xKWUHmp|T~n1a^Bv<0I|NRU4P?WK^r_n~>+H%|dz=~a~Q{(w8o$FKV}D?oJYNIKoM9E6eN6z&9r4Cd0mO=TSjW zOCUjA#Q&vK&nFeaxe@zar?Som$|XVOM-Fwp{b<`AV*P0J)H`bj3314hqezV;wejNF zwSMf*4D_gG>wI{m$ii>dPpBGD~L3O$`7YdZsnAd zBl~hb>ajbi{@<-b0(O;cE;uVT$X>6j_tizUfvGkKTtj%qQ|qp+Itv*e*CKX%Wpw_bt;K zK(-FCpCaXut-}^&u%?}H&dn^V(NDu|@C0NQ%3(o!V(@IVvn@I#{3dM$%Wm|&F}pul zPS?7hEUzu%wv zRrFh1hjRS4HM_(UE3j$iB)%fCa!~{A0j&*2Sd+FA6MgLB=JeR^kdpp$?EPp|XgO%q zUpJLNsgk+YB)@zvj@lR&ZawPgOP=4ryl>O1$ziY8p7R8OB7JW0p9)e^=ZDnnIU93T zmNaMFkwSg&y@*m}w{!YYmLL#TXYwvB7Dwi)@UCFC$}c?`eDA(3@y#^cx9-Kpm2J~e ze*!Mz$iZy*N_Pa@GTU^4eu0^;^d~m2zR>mCI$!h9lZK1ieM{HGnzavR^>;u8U9}v2 z*kPOsiX6ywmUaDSmL3eYMTqdAiKztxbm|=B5{?-7B2I+oFY_ z+mSK#TT@QBUoRRXC|hM+Hx6Fl1F2tg>;4A)K@O#_)3@9~hhJBfftyV9IGYj4ou@${ zEndWtw^k~k-U5?2i@*S~fodDApubS%Sz;j0#HT72fvjBuZ_Gobn~*#qw2EqrBW0r zy|L-nMiAox$+x0}VPZ@&Ck2o+V7<^SW&~n+)FfhjpKpV_@ZZ8&=1ewQmthNpViCQ1 zHD0>yx0oAs0o`Vi++$ZIQoel2U$ed1=PiPgl!Twh@#d`ShGu=x#3H(GJMtO^Wn!(qiMqK8a86?2kYLqC6Kg&mWZ`usKr|~_9WD7L-wOe0YHoHl_rBK4y^S;>3%dT zYh$5Ejav^mO^GPuoi!pv=<;lyy@07POIN^botD7v-Gqs)R<`3M14e{Ur*)j6j zv66`CacM#;Edv#scr@2;p1$Bg`LR%Q!?Bq21I)kNq*cXcdz~!g>p9AR)JeMQ@}KSY6O8A%NivRbM+g`|i)+_6 zYbxyQn{yRTKi20r3TpTnR=098n{`XmgW@qtu$j>GRSy_X;2CN)r@4m4csZpFQomEZ zv=#EQ2qZSLwI8<6-9csDI73g(3F>q|=l#IfO0BzidTA(%Qy+q=$vK9znR!oMlLCM- zNGoa&Z}p?QB4I+78h59>k1fK=8J}8j@TjkBjO@>{bCP&|2B)fcNhC#NXwOGBzyepO zP{E7D(Fv&hdqW(dKg?rNW5B^A$6Vx%o@iaP&gW^`Bv@eiT(3{tZDr;Vm1?`%I88O# zH`n7a*DjwgEdT)Crq6R72b61V-I}%9e$E_7McarBc}`g#K(e3F z+t`Fw^Me|({$DYfwNH7p1>jD?-2|SYPJhn;(g8is(KBKT=Iph=b5c5{)Z6p7<*00P z5I?<`^0TQ48gpM@w%%h@!z81}y^AN)ObZ0cQ5V85Pnk~Px(o)L)_6(TlOqznKW$_nM@ zwCkS@FMQnfBCQw#NYjx)%#Vgas6wj6J>{H2|rgdS^VUv~%k|ue`3*(~3FqoZSv7 z*Rd*fFJ-`fFl|JL8j!jB*n_9Al|*s_G!VNz30<--M<9_j^@@=m)(Wm(`_L)q(&Dha z#dUPp$EJ!vjh$ zy2)D|ZLvswt1n?^P=kGBmwl()zv;XBlG1jwuUHuFwn=45?c%MA=ZOVooebHwgXG#$ zVWP;7WhokOwfteHhWkeL*M~?NZ)0A`5j&T-gvoN2aB(2%{Nf{}$3_Hsm z4moRrt<*q)1ptf%x^X*^$F{(j=S28l3TY==rd7ASAeqg4TY_h-RpfaV4PXM0EI#`5VgF*wAm)jsm+dJ5Bi$6RgU{ey9&W4cYVp- zK1ex}N|T{d@eF1=y*yNeBgNFd&dmxV_ol-lbNTqqbk!F391@DIoRW|VjlB0e48-## zsP-AmEh-3IS&S}_oN1`eo%Du@65~Jc07nO?>rO@`ACp3z@- zHevi0PMMG)Z@y4N9zr4*gib6yk+CuBvjZUW{e+4>ZJ4e(D?`s^7AT;wS4_?uh^w#G z9gE$iLenNhxU-tU5nK82%t^@XDfFVfXhsRBcouNT)Wj8uF-U|pf$t-I0ZN)<&Aw=vp<&PE7 z3(rZZR4*+<-H6|!&nJKukmVY(0TtfRaqRNOF7y%=$>rCMRf+r^PWXcGFPH|j7bF7z zb)61g19bx&jl6MHT3`tL)cF>sU0(N<$$t(0X5Cfkm`9*MSxk?7+rP#xaU6FjyG zTkFz0+YP9iczTC_qWt4sy+G09_Yg*je+loIV{P8sZ8--noAS_O4PaSqb-J zBivS?U>(N8f9LWoe155LBB}xfa~LNrQ)wfXSWn|T?dk$boY&_=dDguv2!%$~RfXO` zzG@7*RK_k>6x3o>1k_=%XW%T$vvs76fGi$4L<65PrVgi&)#XBRhi&^QG?WtV-Vo9KfD_&@ z9`MD#G2Tt130Jq)k)zwC1&&9IR)(+F)wX%l2BtAzXnp*yLAcK^e`)!;#vQb~;~L*v z3tC8>hXm~K(k_u*VdulXP#2lnR_n)+8{3cPqIqB8lLrWcAIcYMpxwQG1++=^Ql>2m z$Ju^(%{(X~C}K{GsZrD1bH3GTxIy@ap67Roe8k&gd_P112I;9s$&+dQjDRIJvlq*C z1>a8`)u$=I(>Om46cQ=kMa;$N@%%#}vR*Hb$Ck_H!|FuiCJ6W!9}_;JT*-?H3zVUL z-i5`Z!?h0zmq7Q733=$aTx9Ue67u*bdIF~_AW@t8>uL0KqfQ*u<0pNB)5mYF=9d?^ z@KlHwB{?hH~UDCL%bHr_rK=I&BTKbs80NNcVvKc4Tq< z17hJ$PSD4VUoMC5m6#$q6uW32!>v^-)Ir>3>U^n`K5+tcy;4=>Ph@>rCc63_ol9lH zzFJ2H3ftSA`~2xPEpj$M79#5=ZY5qk`l?)gx+STRN6Ym@*aPU$Kt+b9+#1=|npR)) z_28EYM>?Q39vg#|5;t)=^X?6sfl^)gP`5_j>X~}Zc0=lf=U&G}hMYaZW21@5d{^&b z9vSFO^8109t2{$+w!($x{MtW%SpQmB;u*C-ou|}As^iPr2SY)5e+OJFufKJ`d86|x zf%`u5*p3_@;IXEPeSFhs7p$Wa$79nempSV81}B%@4D~u}yNI{AMb~i89iT5a#MscA z68IcP+MThx_=&1s7MN!T&5$%A8t&9bkwU(TP^^Rgu!#Gyed zA*ZQSOQbo8SEW+;S@DMb>=3ga(A*>3&~z8U@5&!Dh>uub-P7mAFtejSX z&4U6759m12I&uKIqPO;Ff$OT9H*ira#n(9uW3b!fJrmQO`W!{0eI&HQR=8pa4 z1)Hr5;!p|{eCr2wTMSZWJWNvmwa1sK9YoHFqr3QY?Z}*zXEjM!$ z<{cD$Fu(e0r%E2eJ!Eqv;poUUh%{|xFol*CwFwv5|7NTL=?m<~%YUrv3rYut$ z*^j45D)fymq#5`M#408NS{vS;1?3ESBCI~Ig|7(8PQvCpfz+zd8wAom;Ub<`tO`nh zTntL^`K|sy){{WdS~LDj3Je_fb}EpARNt1rM0Nnhn}{VA{$qd1r$jV)SXuxN+UY^E zfb;$nAouWu*s|m=4F+!}a%)~KJAueI%d76OZH6+#-X-=MtIR0rQ?vj_ra1;O2Atxb zG|yRf#`#xy^1Ae1n+fkHM7WS4zpLyeGJ*WB%{q%GZq5py+(__d)q*+kPM$BOylS(m zuzcxqbf!`U$|uhWZo-X!dw4JrLR}%sK{~{xWk7MsM#s4^gmQ>eVVYa_l`J&9_`WPJ zfc9Xl7DO(=vWY_)dAKA7XZsT__yJJkss@{=M>m9qEd!~2*x}Eayj35nQfW;a{iIx~ zm3xHr7n3m7@p4>^31^k0hG(3aRmyHooQ^Yizcpa8;9lmh>z^y-nCBJwuM~zGTNPeL zyYGC@x;#>Ffi9OQ@%YR+eWme}{_b*HDCXj&3OH7BasMlh_5pXCkTT1G&Zy(7kwYO`XFC*{m~q;QBmC2ZzV@CwFs`Bc=MVI6FrYw1_|@K7z^0R z_Mif=^2c-dvOT9MC?ky!Fay^4CkQ^SYwbC;?GHvhs!yn31iTd_KKcFmkba@bHrEUP z#)|s|LY7UA5EO)YSF2@#o2Bdce~YEvM^;InrlHwh%bR)W1<}}tW%tqxOHb?c&!FPY zFtaf|z2ej>Y48B*kV~$ocK;7Qxo7feTswKekjQU%`cv!i;)jXdsv9225C~F#a)PCK zPC4XAvMPO}?ieoczm|u5h9G+;Uf8n!lnPivMAoG-eE7Q@)Uqj$Esx1Z<^%hOtCx6e zVM(;1HMv*#zmsXMChpBg!ajp4tZwo*V-4j~y>6_wzg}|J0pRGrz`FUw;&!P-O#ABh z*qpy@LNgKeCQEP!0l=_Rm~-#d04-`!PTBKk&vPFpvNqJ;V)JnS!2b;KTK2qX*?>86 zQEaxJ^{`Sm;F<@Sr%RBCFQD14YcII{L6X{*kpmdysJ4>{243NzZXt*?jAaIK;y>mv zzMeuMM+4%QgV{R`*!xfF<7u(82Y#zx$xfn%pxZVVhaAE|Df)fH=}(B>f{ z!$%EH@9=z9^_JuPEH{6dV3bp_jgquC2PzSzxb%3t(4J-i_52tE$gfhmR@% z=l!PFVVqk)6OMC2^DN8MF|%x@43GedivV@N zqWBMA&qg-%51b#0I;UzqzADaFU9*EI^j4!@>+>gd87kO}XnC!oc9}Be;qNH4$&}T6 zpc^xXBCn+>l&g8#cv`Y=d z5SByKI<9#QQ02AHQs)F4(SAG0jwDXe8K+La^a>r-7`q@1GxdSjj1kgzywv(amH;J; zBk$$E3KLzHAoy7aop#IuA1e74q_Flc^}E@jMf6x;DGpEAIDZ^Hh-KMrc^sZUsXXho z)X6hOYYZ4fydPzJK%jNiwr>?ve3gK5VjlL2S=dfT$wxT^LmJ+@B%sMb24H9Ja$io6 zT{xdvkmC?8m9ZSstG8i<&%6G~L>?nGH)OP2&sP7F69X2^7P@WT7o<3J?+v~~W!d4J zeq#WD7}-L^7k2BSDeNzz1^-A0|1o)v#_SD;*j{LOFz!fDrGg;oW^xO-VVdI)B$eFUa2#-Bz2ExU zewZS3BgRayQQul^J2$kjyL-X@ZgANSVwBae_Bd-q?t+9HCq{dbZvli3(1d!H07(_n z97n6v?aE)1V3)Fe9=o1f`Z&n9s=)k;hJ-C-utletKpTc7z}*HljgB9;%SeXvRuieC zQuz`$C0>n|&F7yKAy(F$-9A5Ld`d&RM;322 zF8H{KJWWS6h*&3;FV}R2aJYDNcNoQIoqLJE4d4D>z`Nzm0Z(&BQ1`+J($+_JvzG7S z&Q_84iv|;(`@fyh4}C3K^U!@*E5kOqCgD-FQ=Nu>7WqM`}^>6?wGZkjC6@xbP7l z_~s8>1Q3+800s?uyin}Bx1fPBz{!RQ20~$+1-)-9JBMSK?XG4A0;Sd{w9K{xZv#dy zenb`^f-}};e=d2@eyRDSGHj<=O%oY9JeddE?>-q_U zN!etJHxhR2e)m(4y`ja8sr5xe!!sHbn5$d`4WxJWPlW;v;JYWSl_rSM3r`lV(tsLJ zi!z|NG+UmpH~>mbd5TFH{Q+ISi|;2~WLKMhaCfIE~z?`+4W z_yVfi6WaBCi2yX@x~uH~-~{~dws({nok(c{*(Z5`ms@NC_Q#kKXAPX)d2JN@ zc;-WaiP&m)n3Urc@NQn<(gc9{G10#&JZH$J6E>?Vp}Fcio4@q+mHf*Yy#GZtRJ8Q6l?%3${83^TEs@f-}jv1JFhp)k) zjxp~FY_DD`0w2TF8sg5G>01*IUsyLZ48N<)d9|R_1&$RiZ zv(u>>ah#yxKP4za!DRsB_+-!4(pMVFoEk9gvhPD<6bKc?l~We@K0E~1y7C92yzYdo z50f-Pa`ULF^tqmZ&)~yv?@@n}6RGodJQQh{Ilb0paXXg^foWNa>dPQl)3CThNfTK88ICP@l*# z?hRTW)VbYzdvO~WK7C5}*q{5a9~Ax0Ki6iYQ0$f+_Jk0y`0eKBQQUF_j#j!Jb~cTw zF#)vZ=pTpSp1$)1ZrQ%SB(s+oqBa$_`2DK8^g~(Im%V~5?^y==`N#u3I>4>fm2Ivz z8;7mjQ}L+&?kkv&P!Ur5r%(QwUntSEaQ+m+@x1UY=>E`P$=|cTY@5*sA8n@0ugUE~ zo~fH{^u$pO%yXgCr+&Hk*I&>2_o5f=KDz`0U=OIo6-(t)=LnE+wsYM(s*7Kdv(Po2 z`zMyOfhLfU5_M+cdpcOYIe?u5|88$d4Wl*HgT4EIdh+WMfLq5$iW=8bgL^h#pNG;< z0iUZ)@1!d)XZNpMUr+G)eIW|yin&Bwze@|S*aeeK0az2-Z1dBVGrigMSbes?G}O?|f}C~(`eAPRW%JGA%7g9t-G zBBcOh5^$SaTMFcMkQGoJaltc-)@bTQsa%YP_z=tf?)t8S`p74q2Oyy?tK@V+kksKt z1L%&}%Q22P?bdXX&6}YgWDIM5Zyh)jF=bL>Q*~(VRIJu)0%8q*W}3#ThMIt5asrz zF={qmh;C8THcLyOEIa1V0;aYx74MRniQ*r`N1g)&}s*IIAL|4W1-~l=qQBo zw6|?~fhiHoq4I%1;uWlr#fzqgG#%{%oy>%r#x5j#`lwiWy&22g2Pk6liPZqBNUP^P z<>D7flz|1RME*6;DDXNT)fS*?Z#ODiJ4d_ju7tqREI4`lyxy*a0XOuoJ}vGqS`Rq8 z4xbKr_-YHdO;(f{J>%x{@ird|$_I+PQxSc7^bG0{1YG!guxEH|?Z+eQ=+n-w2|t_U zIC(rEeT_cfUo(5DYL*H(G6s=19wspX2mf9T_X8(fyp4*UIHcbjRFlh&+M8RK&!v-I zk-6DDhk0Sm=Oo#?olB)wNNX`g0&4xicD30$?pzCW{#k%^|uqvGKS%@zpi zEcbD-n_Q+fC#yljTQnX%nt@6|$&i4@*ROiGI&K)h`Cfo>7q(rdsS*H|OUW6~dFG-| ztE}dzhsgn-&m;uC6YL-;t?tg$IiR&3H$emBGNa7h=sL#{|81UnAhV(`(`p19c|#pIC=_lobrl#nIMYT0Iu40 ze^{xu0B>veaL;bZlFQI9ovs7jnV@vR4=b7{G`;-4-U`@}Z|OI(x?w@shadv0 zwtrapl#`>IPrX!;RvEY5N%6E?fA^y}unu>f6wim1uzIKWm%hHiX1tK>`eGAU<2(ac zj#1P*2(+K*7otZpBvi1@oPO4R*U~H66#*dMUxI*{ZU(k6x+CkB%JWWhGD}QGAC^&* zO+>tRnru#<7R4^5<(-RI+NZa2(3qv8y1s##V(F?Ohi@R5jZVyQM#5Ac`)9ix$x!XL zYg2>6vq;mWX-gp<-3TU%w3eO<<(TOGBK2%`^e&-=JCPCNvub0k7eNt5ov>Ry=xzt} z=ob}HpxqU|kaq`eZ^a(6XSlC*M=n7kNM1B}zaNgY26Mzt)#$cQ={SW7C|ff*Jxiga z=HjU;54O27nK2~ayP||{R+|s@IIA2%-WNW8vCAW%|vjjTJBGnDk;vLI2FPZJJs+?W_aZ+y4zgMx`?>$ ztM(Guqq!RJMSA@!mNbeTVRL95>%EqSTdK>nO(j6-`*1kV&|#a}Zz~Syg|GiQO$c0B zhr0DsZIq2tfUifC=92rnBaw*ppsiVtVSqtMPi8KfQ>jdFDMU}5tP9D-QJpr5aMEc` zaF3;s1qCiE*Gd76bkg=_a%9JkA9aF&()nVirtl0Qd}lc7oO16jnB%HOI9B?3c=zeL zV9#>!<+xKhfZL1Q{?}Ahh1Yg}+&%2gMn%g8r(}5U(gPWUd$q`OtNS4Hi?<&CJbGL# z;=xtM;~g)Hu#YM^!#aK!9kCgxp};0@Lr$1N>pQk=oBdoBIlxr?N( ztRi1aO2Fxdqsod|CkuhbstJ*KKm)Ta1UolNF5i0m>)H)f=!1F=#4x4xsv_%2?qEWCT-o<8=+wv4%5{R(is;s>idnEj7{i@)((f}MlCDQf3zMt5Gu z-Mbs&FmvXEJ50|%V(blDzYg>Crx$1DBWaWM#AJKRjOODhOLw!Hc%ZSZsTJdUdi?u> z&Z@i7VNtufH`i7Ujr)tILzyEv?s=?1PMd$F24V%aUS_%rL`rpT5Q26hhsr?~GO5f# zxvz8g^qY;WRJsBAG*y1kD?2DGT`N-f>Ss0|{Wz3$li>&3^s^RF+WLVXyY==ON$hs9 zV5e3xm%G!9v}{o5x8r;J-D63+jHX@^ba6sGUiP5{(|P%6?yO&hx z)URn6wR6DX{VjLhZ%AJWyH57^Iuu_)P>4W<2Oo%Wi@9s85a9_=68e_b$_hV2?wky? zHKF&vYPP4&!Dgf;*Mz3~0}sA+Lah&!9kuTj4nlh=6RPt}!dAl?mfElz(=A9#62Xq$ zg|}NT=1s3}e`k+)i~V)~;w__VG_`X+uG80U5cv?%W;J@f{r6LgfdK(p%UGxR`}W3X6v<}wyQ@!8cw-(qh4A}w`}Z*%GeB*nI1a}ByZPf z+R6i~eyBoy_;&`yQNl>>GJ3Vt@vSxgoR}$03+M%?_`YbpaJy;uyM6dM$+=FBN4Sic zy%XD4$-jmyHV2DL*}ameRr4x)eRTg`{afuCGiLy4^ffifdp{||+BRX|pr_r3 zcrb2=QCAhLQ-HJS^S89=+ahBPqW<$kFY3_lJUu+x?jf>Y*hvYu6%3YEAZlLmZ-qA+ z&ggi)YA(x43w{Wid7HN-eH7Z z%c!)6)<)Vn)CZLNjABy_86MkbIwgOVPdGFg>zRzFuM#d4O22;BH1B@(R=a*GS_e@6 zOVok;WBJb7eOH^BUUx)rRM>q`s_l1tA?7P!}(j8pJxp}~{-4){y8Q7iSH!Y4&c>0PETN~$uI(k&7z#apEh7mdGVC6Qp#09n|Zw=4;@=vO#|No$o`0cuB1 zf2~IFT-K&l=UIFd)=8z@AXv)%T%B!bcuwcf@cYb#$1V7hIUX7B7uz ziTKY5R8ab`nzI|aI8}STsFRyoUDo{{-ilSEif>Ba-YM84bq);q!Avg!jKQW>$SK_U zGN*dyD;q2iArrg9^d&Fcv%7ovQe{dQ3>uf;7{=#)MjVXrbAHH!#J_v!f(t8rUgF?j zZ(96*s_q?A_^8yhFsz7lxK+Ba1RNZWcqQ1YAYhvcJ5T;D`(dF_t$+J{`cL27S93Ca zd3GYP*2d-7fd-|=eI5#Ju-=eL#^A@r2OUnk9d*N9Es;E!MD}0o5k`;*TQM{G@)=vB znatLGS8u;voyD_c_!v(G0iI2R;Y~(S=4Q`P!dOpfG%atJ=B@Sn*o{*=uo$UAEB+V# zw3_|1F{G32i39h?KTRgYRx%)gy`K+le>mHCVXjunI}7)S zWono;^q*4`XupAWZVoRE>NJeZjShZv@Odo#kR~kJHK1Mg*Gn8kfm`G&J|uA8=xEEf zuXFL|zPe{D%D4cP)x&tL`gGx48JEjX-=oB%7gfJQfX1DBms3tr#_y;OOk`Ny@A_-b zwYj)7jc2^?TS8R)qqr}2&Bf)U_t? zf%Xw1Rr;v&&Y8*Pbw7o0>Tg5_iO`f1*#8oLX-E--$Ti!;f4@C)dn8{IXWE=vQ_=Cj zRr|x~>D%9T&93DP29cekNm|oy=4fvpnB|pYBUa~2X;q$=Adnh z>9s8uQ>5VDPvFm~kIb|p`GW5z6hAn8neX&FlgNQj?(%+B!REi{%rm(qmk6E_r@UgB zfcOQTASkuQ5sT9=!2kjA1tDVGsbv8n8Mw~vj#ThpB4&C5tD1s&uF-De~Bt6lJl@J>iN(3hB9H|YN*kU zGPUljocw@7EiYM~{Fq`H;3g^k{Se3)cVArJuy2ZlJ@y!-)cXq`F&|$XS+8et>u3;_HM%%0Qkd1`uerinG7AkTzD?<9b03n5*wR}k66Ds|-3DsS6u zGa0twnsv&i3R~7eYFK70xv6D!wqcdtaVd}u%-uUxpxf~Q0(=n?q-(wzd}Halh63w; z(D$Jhd;t5^f1BWwfB!02^38!+idn)n8bdCPl1IjzzHei=STKTI<~M zb+{5vJuc5|#XD^z#?t9X*A~!&!=my3NcRkfo^6gfQ2$nUK?%e)U24*Rnx^RL&n0E+pjTe zKploB_D(s2I}D_OHNo*8G|Y!je(99im6`$@)lL^dbd||OZ)EFjevxUKxUMKVl@ED% z$wpDY#rYP%&mpTT*wt$a=v2h&6jFWrO#G*3Nl4mRGx-if_#0rFsEf1IpJ}W@xtg=L z^;Sg*1U5x&Ov(%Qc5gl;Id;VqS!F8U@j+cmAf!0+3;9=s6ZzKTSwG=DoKY^V z3-J19Ri?)}c;A&2U%hEK>Z(UJIAanXo)^5lJ#T1Ra_lKHdDJWALk#?*Xw(-4)SZisbqSpd4tP@>2HS0dMJI-+WzL`)zRki^NbpW z;s?edr~t68#L=m!rz^LU`HyuNK&4_}#Tct3PY0q2aB9{h6nhiTQv_+bswSGue`^+? z1#o5V=9RLWtAQ5J>61F|7&Rzn%}w48zE??|d@ej@U;aHOB*RupU2R!moT3(v|Hx#V z3Q88Za5PPzgBO_kp@7RbVE36NE_1_=nah{nfuYNv;o;3)a#K7urBhK=k#GB|Kz-V; zllrvN6`(jUl<_$j>bqa?zYV`ix?tsd^a@vnzr`XCX z3V*(;A{={1b|&M0PnCTJ80NF9du+a>9Qg-TZ$;kK>tD^W-`qqg22xbNyqA>iw4;*Q0 zk@Wd0>c|OEfQNwmfiv^xOa3qNJZ~-@efsvt)ua_bVk`PKfGBLV`eyJ^1Fae&{-G90 zA6adoXp3Gjb~ysoB~#Ab+{br{qP^#Bq{jL>47nTRny;ZnQrS-X9Y1El;q>x-KDG zaJ00eB}Kn?wM&%qvyJ;RmmKiIb%`Z<-Y=bfxb^8J>c!?kYWCz^?cy+dcuwUBuAJxZ z=lMPR7e#hcprR;6S+poM#&CE3eYYo9Pgxyv+W3YBx|=b-f9W%u$83n>b`>*xE#(o_ zT5qcQHz#$NdToB+&i#;#fq0yr7P|Ros|AoQvY5+xMN#yRZg6}9BpFbpHm%?Llva9K zEOafUhy(|&rm$ZHlzXcjR0PUg>vVG;Fl+H{RfKa3?$-RJ5mFvVbK2T6(&+EgQ?u7D z!lgSWJnpP|I8m8oiXE?I{=G=tM2z$iCu=Qy z_@L(F6;n4GklQY^E48K9!zX*84Jg66>GJ5lr7|g4(Zn-j717ESbN&LH!xdNH4x!_i z6{b<|#{08W@qP~q`~~z*JT*TrH*G8G6RntBgRsYSYuw`+Q>^ue!n5vvH_S%Q=I%3- zzu^VQ)PpP2qDmk%bn=?IKe<|E?D9&egT^{sTD)L+0<-7y(kt4)KI>eTbVGn)%9LJa z;)67d2ZZ21mYb&ZVNi&IJP{x_x2A;6W#?Mw{8qVebXGBJsK`@>aJu88)=o3Y*WK>1 zE9xljskFo?`h-5Ql)G6SuRR?srC7S*;`cote3oz=i=Fi{Za|LIUM;=>=?SJ>8?^|! z6cDzP@ZKRMQweUJTWQd}rFfT&l*>xyIvtd!`EmKpQfD&|wuh=rkJ&+rZI&+u03Qn~k0tNkHKN_NHp zTjGJ9jV?Q#m;u>7b6RudJEc*;V0Du-Wo@~A>VTDqM zK*^xpB?zU<%`!nnLz+__bD#Wt6{?F*jP;puzo0RZU=F#TV?AG9@&)|$$3SxJZw0fi zD+PcK4p1*s`C>bHt_KA?%WwV&ox(QO27mnPBoAU|5y!PGv`!@eSz;A)>&x6dp{0^; zjjO|q3NDYWpKDv!g=-pTps%2mhh<^E-Si+L{#ziTe7Kz+zoISzZ2!S;^)3IWQ8akD zybAh&YO(m-4UTrT39p-XVOu&z2Y!;=Q5p(qJ{;8c(kuJt zDdPZe;azsNc@9^VCh^J3+Ue76OzhKQ&(UD(z|j@NZxJhs0`bWiP}2W$6a4uDVktjr zJ1(TW7&F5odsZ%)cwT&hXsgZ}*{5t3GUha5{m|LlrNE>5M%czLE#ump-m`%5<+epg zvZOSuA3IL#lsPoew9i;>IM_+O{_obe#-ml8fAdW}=j*r2a5VJ=DYM3I%*;t#nm%@?b~)m8}bta4My<)8Y%2)@RApp{NyZ zfCSXk0A6oVmq5J;JMr7$83V!6Ea;nj>A{m?7rp%-1M$?=mD71!t8L(|pINkMgQGU6|BeKB-Y`DJffpNI zZM5cvkT5k-SF7y*C!7Ir8OpJKt%Z`;+iN+tn(f1P^B085I$Wq^W`= z`vS;#^onVL@_6*`fdg(n!IS1-?DeBI$4-6$5{4=>c~vvX4kH`x|I{H|$P1hT7$^r2 z{42;q04&D=V7WEr$0euD&+TGHm11UBdiJLLuuP8iHIqLJEWBs~S{uK5^W|gp5IPMP zTJ#?$nECZSQM!2Y3%oGt?RmJOzR`YY4&CzuadHlpZp@HD0;B~^6)7ImkzrnHEV4rt+Du=C+OI_aQ)nq;l9OE!SZOvzCEVrEDKccN{qnN$FcRmUwwm@&DW2&*w!KYnlI5J~@5O z=A&EV?!8>sRs4BY^(FVJ{Xg&jFSa{#ZH0>UBAWoY4K9%jbP{s^m9U*ikzQ&tw$M6PB2`HRQtKHIWs9kUE0O8=JD#74`lP~ms3Tws&X zxyd0Cxa)2A6`mFA%l8)lda-V25pXCVR(?NkR%qh0{?8?IUI}0SX1QR}8^)&(tM;k5 zJ6B4GpE~rl#CE9{lv?|w|6RyInNJrlYWl1!I#W`!+OBZzd}g3Z zU~34jk|8*t*7)kxrd46>wf8^HJ2XZ6xVF?h=DU*jTD*!Bc+ayweb@oypE^9z)P{db zt8!Kap9c4VjulCnzg}ecm`^#gs_4uWalM|4t2__OT#c>QI+++K=hpDafNM&t-~t_n z$Kl^EPE@-qxkj_EJJP%@#?&doz!w)WA)&o$1SYN8jCLuW{dUlu)dNBg+W{R@kyiS`S7saB>;X`j5< zriUf<;S&w+zrdx|JWj1!-znSp{>usqot0nrw>((%daoAK+t07u3(fDX&}wyMo!4Ku z?ON>vz4gs)(?5lu4f2$efBLXRgMYD8q=n)sr}Z2cB&PjJT+3d6|Br;eUlseBrZxXE zR@q$j&%V9ntI5^vY9Uh}?_JDWU>|h)Veh&+Rh_9%IDui>_Q}9#O6%l>I$xR@dKcgN zlXdBt*XG+LkLTH`&H3$KnEO1vWDBPZQ^8a36P%wBTWsQeRqrC{)`ebi&t- z_v)rT-=u%NcY5h`L(#{18;>@6746aBKkis5VSnmSVSrde(3C6jYx7qp_O4J#Z~1gV z_Gw}qFk1(T0he`vfrKDv8zuuo)C2HoMhjShE1Vb@E-(T2R4_1PfL1@Ef&?$%xG7i) zbOHlIgV+?```=sTcT9fX&j!|$Z1jua@4ooDXElWrzJCTT6JdBDGR1dlAg~+@kbm*L z{-Lt?`?{yi*TL(V9^9DXe?HE#TH(C>)WG{sIiPm!FnM`T!0N}U`<+(#>zD0_alUsn zb#nYXs}=jfBe+whzka_X=5>E%rJR$Yo_6lfsn?%9FW*z!$tvQcB?Rax4yE^6c=j{CJV8`s+=Dw}wdiMGyAa#X1H>KY* hM-5Vnd2=rOXW#vlbJw3O6Y4;QdAjl_dnHt6u+{{Oo{9jskqk7c9B)tA;dMqmDROG*l?b7|93-2q>REQG8B7KnNrtxXN^m z1ph?le2krd;4Q&ZMR{EhlhsC~Zz> z=}QuFcvg2;G)DX5Qot>n$B2B%$HcA=Os@`~q`Cch|0qB*mm-w*l}aG9IHwGovenwP zHzDkWYKG6bjJT~^xZCj$+DE7679etjU~TDV9ZN4`7WrS4W^i%s^2>rW3Aac!kteK z(X)D<3}Lnc$VeD z&QDhlx{E!MP4_f>PMb;#&oxs#q36rg6T|1B$f;rY%yvZ%NtT_ILn=}`@YfbB#mDhZ zM&ThB;c-Op(Hz!v_yX6e9kmZOSMVQCQ^-|) zE;{+@8hj=|U`-ZR<+K6EwYcx=%j0a}EQ=B5GU9CDbe^xbgoT67xBEMAQ^)PE(Q*(| z7A~eCI#CI>8j7B)`0vPlxZ{>n-zy+W{6kAj)1)LO1ZyV&V}qPZh%CT4}QS_!KH19J!GA0oRTs%@{buO)`o z3{xWhQES;G(6@4yQFoT-mSuQ5S#uFF;S^n%gs#f~C>ME;5&vv%@m`!diKR%0aR+YbF_boK4B`12g%bu}4uk}wZ1$h6)KfQJh`>WOVM85m6 zbukWKG}3%(C?046Ea`*6NzW1p%(SCz7EP^w&ttDjlj8+Qz|vGA4$BJFVYxMtLNfo` ziZ5B*G5aAnX(f0frCK~C=<<3L6}*^2ABl!+Pw82X29=TwyR&HC?0saJ@Za?bG5HF9 zQ4-9S5((!DA6csA;Li}6h=DR+o^qG5AI$g`L>`l=+TsfQqH0tMWkF3_>WA50imgvy z;ic>Exb;j9{_0n1JAaMTH9~XT>(rHv(v}8c8zT~1d-wSDlTe7-qH`?KKjkF#^Z_cp zxmXakY>`79u0wRt6+zb*2k<;LyZmr^%(w#tgsWPoeq5+9@64bIt~%S&mx0S44bay3 zT1oqErsY?iaQ`g1kVdckc(d}un%EX{0UwuhfdXIK^Og>5Ve@PZR(0()X5AX@X?t2M ziYWMnt~*08*8(4mwMl8cst~!>=W7iUdZe!78TsKSGf6(`mE(_GS!DCx&Z7-={gD19(lG;b$Ou?wkcN_ z>_uN$Y9;woaach9o>WWBr!gd3^+o9_FDB4&a8FUjcsTK5i3qF#ELywa;`HbXRb?279iKK z=Bq4P@h&pX`EFQ|`q_nJ)~-97#fb52OR4tec2MPbCG&4``xwV(bS7uA;qJT1SHeQG~^lvi?4L_?1d zVVDF3%?2XGi5cFG5T-j*)e>6quR}1~SWOK-t5j}C@dsrX9i`DA65F$9mT7QRX|KS+ z;`BP$h&WlwNr7rR@c;VV?i&PkB6N>e0yH)8XJKp}Sna zO(!xVU%mP>Vs;A_ltTH+Qel{+in25q0c3)T4j~W<$*RM_p`IbRN!*N3(M{7zX$id_ z9TX}tSG^Tb$+A;tG zRirZ`WSOQ#EA4qzoW&9)6$BXfL}%>{Y@PvUKi>v6Ow?t>woH896}wp&up&T~zNCFa zG#Yc{99v?WanFR*0b8IMUw7?NL{|;IB&xRrxvaL()(dvz_D>C`PAMmmW>*Mlmd$cL&;F8oEXqF=I`rx<%DD zT;6A`pbqUb*cU<}W{bt-T01P2?1?@aUX{aN&Ua1-UQm++V5GQ1$zx!-1Mfq{zFG0p z)EtK)P&Hj2Q(C2m%x%8lrhM-lO@HNoS7cAa1cm7gwhI<-MMwU!5|9f&K%YyVC{6ug zX+1r+^jdv}@kq{DKN$tNZG}Wj>V2m?k#^WYZ67kuA8VslavWvR;PISwapB%mqvfiC zKw_|Utwo4Ua{33*~Zu&2Z(IAsb@Z>Hl+y%D+U;G;A62B+s}ipIbyw^5ED)ja0!)AhzVyCFdUzw(3Q=HKvJG z$4nn&&b9Tpiy(ixcdw=ffY_e2Qn*Y80-BZsYN{=&^&ji=dVZWXlm;b$3}%c>ms5nk zOG}+UPz9LMUuI&wQ0CSFw(3ZSFB#f?$p-~tBe`7RnBMkvFk?~y%Inc_0dnNOrGL|J zM0$a%_eXG2^to|%0Y0>|!T0tAzDk3a_kn%Qwh;%QeC3|YNUqH453R_tk=5V-37PNl z)T^smy&c@N1!OccEzZ@Rl)nB`(A8;Njpb5w^IIE7{X*{E&ks9Pt^AT-)cId=S3l~u zS{6put^}3GrwfA#ghhHiJqvFET`^UY_U&DD{ z13_6|Las*BBjcAd<3y$%+9hd8Q)&2=%vIQ&{0iBMV`)gyBT1FjQQ%DxDT&RbOKpXuK~Ek} zlnd$Gpq5W+K+{z#{&X#_yrtas+1KK0!D1Pn{|c6{>O0L_L}V*xoQ?WAtp%gRDi_7X zJ-3{%4bwg~f68oWN+A}jLD^|Sw-dHrC;uiaEesbD0$~Ol*RA;eh+n^~D2|#!3RBx! zjDh)O7FEZ`cH_;ZX|4{jh(!o$S^8*PP!U|W@*d$&G1GI_U-3X7zkn2FzEnEc%K6YW`kCQ76MVIbKeh!cC zAso1@)WnZJ3UC=H$JOyJ7O}Fy2SrBA#s=I*O>i^B5E9tazJx)5_FFpTeZj|!hZ}6u8YN9{>rk9HoVjg7cnng;v21x8LkvFlb%qD1eS{m!Mo2pey1 zc~>Bs$n7TPf?sWY$v=BlD1s2|Aga3RxFA0%v_n%9W3TGA_x6qO-?U{^hgl(h&A#9hDySOVyfiUQImVMEB_BJqC1Q|1#kgKRD6(&e3M=Ke85JMbNZ7b#K1@ zf6sqilDje_^OZb5u%C6{zyDkc7E{jk0a0csKc=u_EbUxbkVEW1-qm=y3rIH|nXuWH zAWqyuRjN?1Xo!}(l5klus~kQA+EW+EGrVEBs0AdiDUa{ZFh*ZN|E;G3%$gk7{y%tW zIsCRUcno0tVXg5IUJ7r|ez?z7gr6>NQKz1*y32ai`}?&5TczfF6V=YgAHs3rac3h& ztTKJN$B~R&8e^JsPCse~UOYv@?(yt{*=4flt8JxxUrc=el!E2?GjS-X_Ovjr==|~W zE!!!X`t`-E)>314hn&UgWc`Dke&n3pk5?0bL{On)?9(1t=8XfE9Jcc|L*g=r*l^T2 zWIS6ZX++9xfG}2bYPX={;NjAN;wZ0Q605EFX-&6`43urcc|G)Af*N`wu3#-uEj731 zijYJ4ZrdkVh2t@*+DKFe-G!>Qla$H6-}xQqxg5!jb(`2VuGm@eGr_;Nlk|0*07TV! zOeBAANOO>K-hS>rw)obx&b>Irdx&K@Us}U5xfZ4CU7SY*?bk{ zp&GFq(;gohabmFm!n@fWg0F>TnA}F|x_0BTZ(!X_yucH!yTy}_*ZH=2HALF9Gs4J=8ahm#C{#~T;5p$<>WbZ_nmJjADNs41&Nq9ZnzchI|+j~2I2sXxxaVqhi!491C|L5iWA+cguhYe^Pt`e)06UCzkIj8!2k**T!e18~I#zw(2cmZk^C@ye*sI}DA%@js+;xI9I7Re?R+ULr9P!6S7HU7w^W^%yveC3s z)TzNVMR*97oFHw=bvsAm5_)EgI07TAI+q>0H@UtMj2()5hRGQ0xy8NAkqqx!g;p=a8K~LLGMlE~R zWnp_+`8qF@TloW2C zxCiNV<9Htc&$84xqWY>_yW>89^aTA$2;6QHQ4+|-@2d3bT3QvPR0LOUq}%MLo~S1k05LT<|MbIT>XQw(lgoM)6*3j(B}yw%FQBL3YDNVBqa3lK}jl zT^J%$npdDZc6)fQ-5QVazI|+d@ze1+|8rWBGue|S7ejq}mU&yg*Cd|vfrJY`bdq;%(AeD9tvq=2=pIm8wW82L!)KzL$U?`spGOq9&ns;iQZ++uG#zJlJaK zC0gV{2oLfV2nn=!l3#vMjxW>4WNMo9Fs8fYQUEmdvz@J37%zCf__@86Z|yF9IIrPt z6V{$qoU7)e_m|%#p^7Bj>~(LEXC_Y8hCJw;quCH+$G^-jUFnk=5Zmm>pRc(bg-0=0 zA56s2vUl1K-P;HYF-hnr1q$82c$!7d$U{u<7Myj_@8!J-^BK3o# zReofOmYMU1=A#QgM>TP9oH)Q4>PR|4BwykU`9wTuiyjKgQ?MF#Gkn(8$OH9UR39o@ zauZC}xR#LE1Fqt=tPc%^06x->=^DBmsa};IPzSJhp7-^zp0|zku$;G*_0(^3;e>ci zmOWDeT)#edNbp0(P;hIHDeCfM%T7Qwi|Z{Sb$14&p6|gRxkOi!QjJXZAjZAIocECo z<#$=Co`)Tktxw4L5k#dk;g!a4|BP@4^62j{b+ zvHMP_6K9QYv-SIXPIF7~xZs`oZ7j|kz8%jzLG~cATrQN50AYRZIiJ+plPq#wz9~S3o8M;a>@#HHEfHOEG*I2Mc)me8wi`C;+ z%6KSCm71F>Q(l_&N!ugdz15wxc;h>FsMNHO@z|-karqMGId@mX)Ou0B!!7%I&{vzY^@PJhtR+lxq*j!Q(mUv<4rty`SVh z`N8{#!Sbd=OzziZo~*UorWK~#j3f=bIrARpL-Cf5Rxm=0?Qb20=GBkhvz4=$&2a0afrXJu8 zcNdX@O~0}$_`sOy{Z7D-XrXHp3qRRY1&H|Ge%qX2lQ2c7Fr2`~hX z%ZMdd@*G;PC#_EKL)x!m^i8Mfz(WlDMzMK(;K#71OtBxMuJu7%CsXf2f);C?i-i4P zol&vjug6~FTog1_fZKVU!(7@dPW=iZpo68*+Oy+vKvb<1ol&=Cr#md=zDEu8eN$(p zkA9EJ){V6oyolQ+>4c)7q$H=stu8WSb~?>Uzv78fdPJxlmVa1M~2Ff5n5T`RK5h!-vps_cwOrOtuL_FQAv9V4(!D535Ok z`bKdBQ4teYR2d10WG_zBPAb=_o}G0{cetHXzh0MZ>7f?BUv%T!Eop|BKI$h0A4qIiCKz~3&1%` z{iK$;fwx`~=w!<+;yHk}LJ_{7)YgLYsJZXY<0bvRMwas$Ij0>LOLp}#(9HG)?SP7>$ z1Ug}k)%QMO?M5)RI1g#G{Cdj#p(pId?>(B3qi-p)-Vw^ob57enhIbZmj^nig(O~n@ ztuz~dVRQbJJ zs{u)_C7fw_Iw-Re5qrPp13J6s_MYY)7k@^$M`3oHECrr~bujNgnE!EDyX0$OJaLEB zT|RF+iAWvLC?8vF4%&88UtlYz_Rp?SO@ne}&*W8~qouQ*ouNL2!|4Y-~gmtLpFh3_f2!{*?7$QQSWpJ}1Ow zS=a!i?xMx`q9z*rtvyoWSra0M?;vHLnh(`6ga(I*d%x{}e?62e!4qbosktxs{S$l` zbS+{1CZhFqc3wL#%4-swI39>^`^LX-=GQ^p78bl$o=0;RD#cKzP0K%7N!S*w$*P{E63S)+5Tlg_92%!w9ZNo$@*RhRuG%%oR9#jzQ0! zqpQ9#H#(@We!cIXoxNIg@=S3(A9-iFcH_9Go*xwC?4GsCJ$Io%fPx}!fye@9jr zVL>OB8c4xkVdD`PUb&eJ0&`c2c4`WfAwlD0h>43E;X z!I!WKnYO7{v6V?q=b*~7sH^U34`i9Oz@c}|LL0RJ=R80Yz>B;YWboY5q-53?H85)q5SQ`@2{5#_pxaV7B$Nj=)oW;KL6D+r?OZzI*^Mu_%{5=3lUyGPcTI8d( zp7Cj}#HWKw^KgUi^lS0KY%%F8;S1LZUa#DZg_)d{#1R^VisUPZP}Uk{(m3>}iHcG) zeU56durKvWn12VA5KXZwm$HXY6YCC^J$pPo{_9VJ&?IZ9_|W3drF!Q+3oCKLq29x? z>EJQF`8x|xgMtKUE7XLz#atg_?9r3Br(IWH{X(&{x8~;4y@y#D%z0F)ky<(^!?X}c zGM#%i>4E_Yd(gXN!!xy+C`4=RdyhrU$FDQ&pZ=J`Iigb65jSk)TU;jPeO&k+K;8!` zny3wMDsp%^($N5+JV0dGmTRwMYR^|8DLU;uvV@ZhKl-4g;eGJC^;>~Klskt~)Ecq! ze(a3OCSkue#691>866%*zkHMlu-_p<474m>_e{6}e&qSJV@Dcg8YXo^>(*jnwv`*I z8_o_1x(ay-T511w4B38`f$bh+FFCdlv0%E6ZaNw>}QrYQzbxmnL_So5|hXsC28duDsV!}G@j8qphfwoV2s zll!WSNzmyPJutDne2H@j7J^X)WwUi*ZfGPzfgxHisiweVp~Rk`W19I+z9(PnA@jh# zl<+E}h;Y7u?ijK=>-^rfgrxEWRGDOtn58u(BhEx<`~I>u=qAzzgnOvYPD2 z5q8S`2y4~!ASocOasEZ}wMwJ>UP%tsvHX^QwsR4s<4+u`7th#vt%8+(k+!j6lj02^ z!H+z4NE+vz4l35pRIxz*=ibR4pyOv-{ybD^HCx;6#Ux9V_-ZR2{Du2n+LgCpVtK+_ z_%b?M;Jl;-E_WrYU?6?#e(%GN^uXuCqPwZp$IpkZ)XH1ANT{B*2V88(O1P0ReVyg+ zNxD%g(I8uYa8UjJaET%5E=i2TYB}J}Q2G@D-e|mS;|py*R+XKE9dt3(#j))MFQA>jG zdj-Zl4}48)aFeOWv4!v*nVg*+BhF-l57z#Im=RxUnJCy}8?8xjIH1eEMG`+q7uX*O zgZNEDgmdAkOEYuCm-XBb;6A@BI;%Gx26-*vxx93N78>4-MuEEKyhQZ( zqYsRe0BNxjcueFc;Q2A1jaPi^Ns$`(#riq!d{fy^_Nv@b(s_(&Y})Z8$SC+-xU3JM5mZrq6fM2bHlGRu4}yP>MGG>#U(wn7sAWtwIA*kdx{O?0 ztfoEYfHdLp4vgZJ1iGW;LdTwx?=G!h#`CnK}APh2;iS*A_$5fL0x6G$L za!I!N{j!XY?Qq(ulUA*xc;!))icQSv@#AIxY;SCDr#$7cM}9jyYNw*E{QM+zsvDj4 z$Q6(*vXIX95gKf=K=F~7fS0|!V#QcDWFV|TvQQ==Ar2hWukw@^4+|ndbk}cu44FobrzWp6RJXKL&bo$GA`#p6y&Zx` zWgp-q%ROisMk&%PNk3WP7rMbNjMu}|DYMe>Fs?oHY{dY!3{s&5YC|fid^BPvqBtv= zkEixly?Q~~v`PYzJ73b)%HVvlNbbH_k9m=gOtJUKL9vbKCu4SFybns+)+DbbIOrh0 zSNXu{L<=S0-x+@t1_Dw&9k)Xp_)5R;_ti-%(+TSx6tjlKERz@du1&N8^>R(V5M52r z-wcX6NNVe`|9xRuO9=3D&mLd^!Bni(@Uxg!1MKqDp>2~q#xfa zlGZ;(?(%KA0T!eJd(5WH_cU2+ch+%dfs8RaPN(s)f2MC-d++>sHDgQR0JUa{jqgH2 zZKFXQ@g@siUl$Lb4-?BDPZyQz#b?iH#=Wp_SswfJ1}&fIl-o$Y)MS+{0m z`l9hFffXl4-c;p$s|tRckuuV+q>jg$4ZB4(MV(86a~o3vY~_PWe?U`-gPVh}!r_Bo z$tMAedLCh_6R@whB;$D@K75Pl#~nd+XFap8UkJjE63gIpKu=@j&iq=mqktY*5Vm*E zoKCj8DPm_Fb^{2W?wl0rqRRYuZDy4>4D)SyTCy&@v)Wa@cge1h;)}HS4T5&Xo^K$z zXcQz-t~b9GaE;M@5RVS7{*EwraFU~H;AD3Pw{teNRCXsLGpF6nyYsY-*MoS9KV*dS zLf&uBSuQ*6i&mK!Yr$JwEligQ!<_A@@oHktIs*g+J zpcRrKT;8e29)(;DiecRYUgdL@d&52jGKJ#u=l4N!J*Ud4*Y_p z>U0knLf=XQnzss6$-j7R)F$a$W=h{A{nNuq&KQbF%He|)^Cl`ycKhG;E=V$s74WuD zTTif_q4RbXuP?H%Ix{9&W0Os*UJZ@@u8Q~EUKLc7rK-!=UPwd{%5`3)k5L2Ix zOtmwh7)l?J9%;KR9A`B*8i!;hfS)1*<^yMCYT^)Q5M7)>k+*KL_~uqw;dZ*quvTG5 z-kY=N0*sWvNLnEmd4l+o3H%h$?XUdGJl(<4Bn)ZArz@TQ)*nL~?cUs6#8*HcitsR5 zY`m&7Hd=FQXSVp*ozITev50(BI&y^j89k7o_OU?c$s=-t>UUwmY=<*!-t4mIUTQa` z-nNMS!zl4OZpP)~sg@ta5fwe0?5uf?QZ&R*fGwFhxq!mQ^}bXrQyJ~8@%Tc!A>*!B z+6ob3<^Jb;Wa-1|ovk`DPuUeWv^(8tA3mrErx`e{C;qT=^B{CgG4hK8pBb&LSi1@{ z_3c3IunpA%cOrFqGYWO5B@Fp3GyRAwzsO7KcZ+=Yqw8VHc>}M;ht##W2uH@+2x9w~ zDLr$R3;RnE4W)%=qv*$qi+|j-cRSnjw5s=ASXZhutb96yn?h$Z-tS zdv=D_K7Y0&w5RVm2zo7+L8^>5IfxNDxgW;wY+}L6XYxM$c26EKx#y@8-3wc{)7DQZ z@K3vc!&(9>;Ok?4b(JiuUeFM)ERfU=MGG#f0&|1$cy#_+R-r;8 z`K`2?=(7es-_IgL>F@L+^nZb~=RzV#8z}BsPWU1%#c6_l@0;et^cA^w^4TA+>_6@$kTb&5qu85Iy5=&{ z#>8cw`PPO+IIE(kmKNCBZBn=P*+qZ9IKuO+*=Z3wa7WUOPP9E>`_m5E3ZF^#()~}a zuT-lpueodtrGK^NBF}h?F&(89W=K|D4D(!yvf%S&VKj*&n8@0tw_g51MM+Rfytb2_ z+tO|<<3t0UQs`NgJso5^^0KE$Hu4m|@kqbtz|&s+{$>Nw5&$KLqg)e_b5Hl3B(m8L zZyxCYn&Ah6r+%X?Fuc_5p*va9#o_8T|JPbahVO*8NdI=}{$)%TT{^3^g#GEj>%HJ- zaPwk(l<5T#y2kd8Ejkt|xNP_LU3TsY&MmYilQie6@+Nu=K9CTMW?WERl2mj-w-TPo z92*Ip1^$}}Ngu2Dqgw;kZRWcbMaZos2;Y|To(39LrRvyiIJpyAlKH$@= z3p##F;*W5}`C#T_^ELxzakFRf?q?@;;rxvnZPNoz#7yvKMvuDdZ`a{_Q2DoWZeW@o zKw(zkdm1D~4NiVbje51^l<18&hUgGG+;Pk#$lZx0LBzY2j|9l?tRJEy>2AA#SK)tW z8A{faQXweHNhf#ePN{u1#hh>EzoVGF$i4I?%FSt+3QVO+Jx?L2OfBFp6PL4>G&&-M zAGZsHUCXeBUx7yrL~pwq$V;YxBOvUeebdjXbwRQis z4;~7yDv<*#k=fqPfm`E2eqCCcxu8a5=n$5t>T)cN5}Y~lvc+`0thX`{B=9}U7V;QP53 z%EsewEtBv%brAgZsls)VrSwaWzqdU2u#FELcnb!z%7EP2;CGO7);{eYo^!biq*F(E~OiL8HY zh6n-0Qfr*GTm0zh`;2J?%D)ybY{wE|FZ}{vG8pgF1_e&Ed;eU&(na>@p)ccTd5AOr zLx^!oImC-1XgTl0cOagf(jKhycE7fJtJg)TV&m+Oy*J#%)usl6{(L2GPCV zul5*6n*e-s^KCO^CGa{y;s<)`dfRWlCVFxP1a0J*5q+b)1Jb}^j_86zh)A3A=2okJ%6xl^xrA4;(j34xh zR5{*@m=30{fnvTy1cuK33Fh+$_c>$;my3rh>v*QLMfzrbz0lAUyoL^z?o~*8ake&E z00%*ZqgKEDmEl&aDhgBO{tnm48R2|`<(i+u3L=Z(X@xb@u_PG_8;9|N}?*@y$N8r0|xnYe^ z;CG6ZakfCafH!_OUTj~zbd>^~{WcO~F)+vrZZbyi7izT3+PzP1K%~|%W|3xG;^Yaz z2SqHLtCa0Q`Lh{vLZX%Y5T8jt2xqa~%62t{I(#PeL#--o(N)4Z`TJw(aO{r)`X1pY z5evu#>!DL2ETggy8-Kw1J6q3}fp}*-Ya>xJKOQ5)aKz@j?Nuih05$d$}B8R8vf*KL;XIY9>OV@uTDP8gBAyH@Jhq@R` zG2sVP@ZnZKZ7AIknLWB_OR^Ax6i3f2zQs zdHzKm^q6IqtHuGKy?mkIdcrI^-S9>4VapydvOsmJX@9&jJ#Me;t}J_tMHT@6STxi^ z-esW2_oe^r6otPdb8S+>HNzi#xBd`ooE9UoR%bb_vV7?}chzeJubAyzq=?C{DY^(_ zy{ACfWbgsE536CXV!C;N>=+@PxacbfNc-E;bi0~H({_HoRqf4KSnD$C?7Kr5BHV`K zuKoCfZsoj#t*TlC!i#C>8QN(~PNIHHdH#D1wf)x%me$+15tzcJu zG3R|t`lm|f)84-xE1hGRK?PWmDgAgQVnkugZ1e}-a0`&{#O+1oF6{9)TSzK;rgO5$ ziWuQ^oExX^diw$YRB4@|T3DzU_(Ue}oq=Aj4_XRfC9nM~0&Si3n@ChiM?h_f)C*)& ztCq&+VZNW}fXB0gY=G}EqJ6AdQk*rv{2PP5Ko0~r7Tr|pVLn+#O-sgS?f7Ij6-nBOWTy6GbJMG3981n}ZjXr3A>KOw$RRneHwsA}5vgqy%Ki%Q3MbJ5E*9#1MP6$oXHQy(BH zig!DmJsP*tSSxUNQ1QzVxh4^UL9QOPJNVhxW6rmdpq%&9F%P;jl*&^Fe5gh~4cI>^ z$xZ9qa!6bme^5Ww0Saojk>8+R-RGNkw;ezi?(SC|;zI!quzGj^@Wtt4ZspoaSHU22 z4$~9pR!4**d(B%+7i)!SG(UMlpkkQG0={Nq8;_IhWE5X%nM_|ldPxGGZ%4d|0&+w6 z>W275I4gwD|`(CBfj_3u9yT*wk+<_ z7mTCz$DS%Kiw{^Me;hhb;@H;^TZ4c>*|o z-u`7Q_Y#R$H+DVXq^_;LEESGr?e*sHb9FOXFBiXgfH+@txi(=0+h9o@NQw5KK2AC^ zXgF$@A6r1JOk`mmHc^M|OM4nEYH!?Sfkz%-s+WAYD|(vA8LhOG&;Ew?^1m$>Re>)t zN@_&%0|I~K43iE+wUV_r3a!Y_KGHIor;`kEa+5x)2079)2sL30<>scZlrc0P<;4fI zW1uc%k%vnBq%8M_%8tm z%5T`1l&*42Sr&P5ac~Gb;!hMSmx9TQEKPIfEhuDI` z&tASg6z6dZEC~*|+Ql*JW9YiXtgYBAsr7Q#Y}tL)a&3oK-LtQ*r25KF*UN$qpCe)a zszG9Q4IhKF{be$5d?NX`mhHkk!z1*`#`S-jIC=}l)LB0zfv21wZ`GRZ!dFY|?bB+= z;|_$vUEr8tWZsR5+3*fldh*!uQ-@LLINA`NX`^(mf{g5|3-6fins0-dZD{&QG&y=O zq*@MD-i`Zmp^vNWlE7;TUA?99_W!C06&wn1Pk^N?{rt~htn=wUzA+@t@iVQniqT9c z!OMJ|%rMD7P4Sl*$IQr!ZBPRVJV}u2x!8IO2_8%<*#s3mjK!=lt}k)Ce80rE65b~-8k^u2;rji zm{0ujb)O-?LnN2d-qM}~?IgkuA)8xp(Fe5mo|m6XtN#y?DOpm_1uV?;EE z)O>DM?#=jV!M(x7XE6SD;YWU2qcjdGR+XfO$=F&q%iICnkH284y3PYT|!^XC4X_jNxWpgPgwz+I|3;9rW^_gvf* zPrw5mpRpBUx%T|wsUr-RQmK@BvHIAmahybPQpJF%le~y29#fnaDc*)LWiCz&ndsey zD(+U^xgN1p6;Fq6MUPsK&RwM%M}`jUlIxK8(uxT0l0w}n&gvcJiWpkiz8+XJ4+@#! zM$s<@Mg)qf)T1~pep}u@S5diI@FZOsH_q?+-8m6Wbgkd9-xW6wY%WyLfm(0G)mZwj z^q6+S#{YKaO5_b)wp9ILcoj|---@HHeZzNcHttikuRY4U_oVcOsRgWoqdPl0q81Q- zP=(ot`76)1EyJKUpFqdelg!L$-}h^ZJQMR zXKwh$>8?}~u6#?V?wl&7aSI~|nn^&-bRC>%UZCuSom-*(7y00q5DeePu?D3&H?%t8 zxZdbZ;E#)1RK21{UFU?)re#2EYHK>yg~gpU#&&oje|*H2q9kcAR1#_tU^?5B5+?3Z z=g7u%(h)}g^%j-3)?Co=?1LPM0>_VY)j~k1Xvs;8prfp2Z_{sL=c>%!SwG{XY|}Wd z^ydtF`^A3*XW~PyDso&4eQ?qO->W+aFAw!swsfI_+3iRqi*6GkLXM<_+f^i6-luK* zRglWx+BraP9B=p?lsQT6?L}l`#D+|8DSnvboP87cqKxc$wb*H|X~$S+#@BsM9e*AT zK8uYvDL!`xN@=RpM;~-#7fcmoN~VYwz-}ivrY2^K_$xbp0eRVGxM%z5Gx%pa{Rz31 z9$VpGqti_9F#)2xo+8ek>A?MQWyn1QKh9}sk}({-=*Eg=T(36lp3R5Eds;$fB`n-4 z_`)&-F?imBOyjoW!VqUk;)t<;*k*8fhKIzC7<|@UB{3XX$j%<}tsGr3+yK$7eimliwrYH5tPIj;7oY!fJwl3%Pq{sFj_aOMj z(Q4wl^d7dNervrxrJ@J#-kjRmUwF@boeu2I+i!9BJX&(HYDBaYpyIeAJqal1U-z2_ z>Qp3m=b`v!@4xoXZZ(Tj2iJphY~@Ga;Ks$1X@Hzww#R~jCGEZ38)0(2yZEc+%%JDe zOf|01+#+dL$q0>j_>rAWb+O0NC`?8x#bWf< z4Tv{+m51Mqus0i%kha=LWr&W1cdDVO>&#mo(>N&K@3m0x`&1zmnU=g~PQFtIaPB6( zG38%(3)MgyYeh!Gxps!zM0yhwj+)>(+U#pzcp=@maj$r%XT42=H^ju0;f1J~Zf}sF z`uGLkuPK7YKJo&?YQ?;|_w;*ex3jjFE5>M$D!Nh&`^{{jt`wnnTkYV)8%|qumet@9 z#iOwX%-wX~1llj^F+LfF1bND!VT)P}#;h^?u@#E=*lvo~1)3W7 zc_`n&{+8+kCMl{TVrCg;vO358v^lc?j+XnWMMh7ky7T-)CVn|i?>T4RKlgd=->jLrW*cLUG2VBKW$zUOSSmH1ToY3N+A&7)*<|~sfUbhK<`;= z%g{|~mA92c$;{cXA);@2uutGeM^m!dJ`N-|{ zXRnkhP195mAn!C5Dm^X*Wzp2}mp1r{>xMV_z1W^859qDoekz{yj8VUnn9}*(;^$$g z`IwRa+p`{`b8(bRaV+PC+<9M1BfIljIoDyBIgh%XVP%4uM>8f%4=_gC)Q7aypY#JM zCTF8!YrN7b!P=h>@1}|ra$?fDXVGu z5q^Z8JUHROS?e`fyr15vEBG-}K|1}F3MSb|l$S%zh!5N`2dUb?N8fV7{uR}A487)u z;60iShUJqp6%LFWLygwFSWdWIYQ1MkMbAm^&i-?q^jRhlY_IEZ#yb2%@aoHZFp{rv zwLie9>v_eD`9Z9WOGei?nqJo1b103#J}9tEkM#;B;43^?UNPCemhrv68~pL73;eFR zg(>QMjGWcbnpdi~9d|m%PiahMC=s>~rXlMiA@*|mC|Y~n&n0oLbNFT_tOoxcgObL!~iaGSHs zeA;*nzM`|YH+CU$9gi&v&WXMRA_;nWr+$_c!h{!W?oWI98nt(p&Yb1)yyWa+JX5m8uApkAS;_idg*q^adsa9lV#`{_WUpDE&HmKf5FQovLMnn1&ZO z9mg@eVf8jgk@T|is&BoU8KU^I?PVa64j8^+&~WWzkYx1zyRLUlcE{1>fe$lHyhFOo zsGLDl|9h59uy+>qV|(nqyJAQk#m=gUwFh2JuHt?GyI2zDbMu)?!SbzGLrUUt;!7!}1tnikj=fFKQuUzR|Chey$9IV$W%IZx1gU;w@Iy9wPJGus0f|jO$}$_UXfT6x8K#~* zH`UzbB1!w|#HUow;(m%b$MqR1u?WG4Q>@qii|Rq7OkDvy(S-rn2?E81a-AUd*>tLg zu41`+cBs5=Mc4r`Z2eBDg5@zJ2|yJ+Sj7CkA^3B!Cc-sZiaJS=erUqWgnOir#nO!s zYyw~%2c~_ChW`g^W6a>$ng6gh>~Vg@fHf!-!(c5r!6M-du6q2`TkR%1K|*0dKcVim zuAs3Xx?)!7tW^Oa9iy@Iz;TGB%;jup4|n%Rh5ArPoS^OVx(qE&NX?Dofw4-s^*^h* z_3O~A=i;t~E=4+Q`Q_wYZwg+ALq*R@F`FxKJF0ch0Qil0QfMLl-ELG$RL{@1ay~gx ziK+;}m`6Uz2QS~oQD|Cyw->w_!s8Yq_g5S?1c1G60Z?k;M!6-;rA+Q9Pjkt! zf;sH>Vjzqnn4Gj8n`F%X8lw4;QTLP9Ms(Tz;yGulBODTva5eO0`NeJ74_~=biqNMB zZgvxxEjD^L>E56HGNES{+IcvrIcMZ*x(b6`GxsOYa^;Xi;UaxSU6=SeA~ea^x>Mze zB=oo2m@>rE=2=^Q zR~Fso(fcjw8vXSrkC84WEO3&YBD(s`t-t{0Mt#IvXq;A}#;L@vc=_@) z(b?+~TyBy@(au?CI6LRmBdsur)9I6L?ksX8ocF0Y zrw39$+7Ui^q})EwSc7PxwBbZiExIEZ|N--^D zarWQ>#!;$PzUTvH$Spa$-@b8PZ?>k<=}l9}J({Y15GUY;JDj`DTgjlo*~oI@GUxJ{ zb}cnI4&vR)uiTLu38WO^yhHisI#=&b5g`M1cvzM(x43Wavv+o}5&YRM7gSRj1J2BC zhbVPkj*hJ}JH`fT)>zW=qS9}@0xQ`$xpBK(LrB83hDb z_^|*ns0M%o-14(8DH^cIoFdUZnc-7C)46kC65`Iqn-f^gPTD+!l3&ZO8L?1?9KvuK zM0IA^Hn{Y2U!eqMymQnuX!5WJwNr`%zbl-`kcv%8mhI(Z>V3IEga`oIXHQOozLhOK z3ed6bKQ6as$0=B<9*p<|IJK)z5V$*Y_B9+nl;`%QQiQ5f_-+%rDszbL2k3%NK0nUv(p{I zr0dtWp~N7ISHiC?{BcJ!K`t3i9naV4H!uN-nyFY5lPg40SI1zZOU<2MIB)lO2_V76 zs;f95QgZ+EoI44pxlwBoxFH_`wy~2ux3flm4RC;RL@miTdo3(>h$=FAsO9lww^Wp3 z=tZxPVEy)bp)K;XWoPf&xZtxUNsVGoufWkKN zkzwUi&mak@3%uQnY475KOPb*Q+$lzQd8r`JQ0^gN8~E46)_<#dA{OR^}FYU*RB>JHI6OVQhb>tFL%~)ZXC@s{o%l+E-(HCQe;&)Wuq@5K^_(VOTjs z<#sJoHvkqV{hIgAIvvO8dnYQY-q|se9?e3l2hLoph2Q-$_M7yhlt&q z^Q^W|ctqlEU;!&H+~hGMyKsJLXMM)cTbpbym8J&wVsARC^5*1G((Fki)vEIT=qx}< z&y5eBuh`Ea;$dOYLpL;)>sd*1={B53d=jAtU2Bh1kQRq;`3TNGAo*gDRurUQI6e z2t6nhd=j!&?ub98)Hmzj%(Yv7YdW@DZ3u7;{1v885bk8%AEbWeF_m%k{xPY^#~vVN zzQZSW?bp`fTL|RM<$Qjsq^P)*g=c<&2?O$Rb~6i}HGhv;1y~4AD!3m$n=a#%q1yllbeKQ=F=e2} ztj}V+`}X25yU@UHY0PvCpc(1(@m6)#0G45y5k!|7J@0 z<}_FQ$C?F*5$;_V*DvdF7FL3>=6V*&2`s*y34Uka1n~qyF5?#8X1W`^ZnEC?amw)1 zcT3&5SY?omtm(>WpiYNS8FK4c0lXQ#`$@0s1Ew#}0#W$!P(qRP_TCtqTxSWT;4XC%Img ztSPYEaL0`EL#$lP7QVE3)*ka`0G@ojAk$&!ejH|7a!jsk$Yhvddt#y5A3<1Aj5gXNWO$ulErhU_)<=3=BgUz#0w(2O; z&G~@Dh>oFWjs`=pwjgO$zz3@RmHt2XlWcHwkN~6)sQP;;y~dRID69R4cBcin+MKBr zEjH;=v3lpJv8Pk!0ERWwWOeuQ`m;rM@cJo!LGXvASfc(p34#@I0lGtX^2^w3U81SC zhAN*zaNX>VAf#w;ckd=}b5%j_|2HwM+1ufm&VS4Y`=eL#7;Jx`drm8a_PGMoo9AJT zeA#<~@n_q0z$W)hz_AYj4?$uF*1w!NaV`F>Ao#g@n#NxZtzXoBLwJ=yoD~F(y44T? zxa-E}sJ#J->OUkfAgFFcBifU9GnIla#WC*NmH4wKsZMglF~9zCj;hXV>PCbnSlNK; zr-p8IlDcl@m6o|Df;GGF#=CO)$@xdS@a=rRK>y>6!N{t9A*d$mgNh5>JE_i6?+2w! zgZ6JX@wfPir&4%ls980#MId0qPk-$u}VnzA3H8qIpnDN`Ws3 z6rMATHKIMP4HK2oMALYNjMHm(mWcoW^V{?0=Tr z_~lsx$<$>xu?Xwa9>-ja);$!%ZwiGz5V|n!UH9AU&cgMvy;ilq|L$(~JN|D#7RYaA z8mmOlGa9`?@3gDG{^ZLp2po$<=s?=F|1Q$8J_v-n{-%B&pTQp%eY3`%M;qI=ZM^rk z_ac`8>Zq?KLVZIQz4TWajVdL`4ba8sR4aKJ5K5h2XFRPBs=m%&9ol6L857hC90TUm za0&da$02yT@9okjfnb$$f9PxgBo+^~nON+Fx{PQldL2mtCn*J2e_~j>dkk4@9lT)O zQ8_qbKmL0!a?^x`dq@CSp^N|WKR~n?s8iVguIFLn2|z^@EsFyb5arEQWt>V#$$MVF z=W%)2Ru^`;DiW4aJ+PpQG@!q8y8&4D(-Qy9Q7Z^_wq*M;EaZH}VU2HiQ)-<2l;sAh z4mJK8oU1FENo97wyOK zyV^&0n*tt2Azv+NYe^Im>@t4>kk=`7M@1#R9+u7q-WggHt{G zNDG&&aQ!$33iGjJbFYXv#(*6d-MeXlIIk=a_7BetK|S1kk6BxtLTd~1O~Z3#wv0~f zMRjmSL84G{uB<%WwP*7cuapGB>W)(x+J7Ym*WvvM$h0ag5?&kculUz-HZ#Qw|y%s4={Sl!yp8gv+v%C+x^AaQ-?BXRL5M&^Afk+8k%o61STbO zqpJhRQfDwJcBCfPOg9GF^1GSvI#k}hP3XpPq|P3*nJ~Wx@UQOK>v)^2H5k-1N}T07W{*KCO(&NeUR>NxDv)u*&XO);Owz#SEZ=07Rbil`K+?(gZR8)aLX$+w&-fIDI+}QIW*mYUH%+2%IOt zLYX4GZ0aX#Sx3Y2LF2ESxK9b};m1ZFwVwKh{gm;k5G`yLHqbpw2EQJQ-xTvaC8}S1 zE?@lvg%v8g3sbW&_lJu zL&1Ngm`9x={2mZ6a!7Aeo15Mir!Ok+K9=WC-l|3l-Ee zTrL3oMafO6b|w%_5P~a&n<|Jl=yL8=_jqn=ppenj2~|pj#qKHzk$ddtf}{d31V{BE9VV#Upn&Q)#6gO-%0r^ z6!~|kPfH8^B=UE*+W6F$_|&f=n6H436X>3nr^$lOM@=hx)L~ymUvC)%lHF>67ny9W zI+Qo0(H?3I@BtJQ(wgcMNbwDDGnPB80($-GcbCe+Cln%C($f&anWiN#1uo6KkA$wR z1`z%S4)6o_{HB<*<&-v>PJ$L}0llL2(NvKZ>=E5_WB!kDj-Joo_>$gVnf!q~qt z`NiAqL~h$Z>EyFBKUDYIRbQMKQvW{tWZ-9sHhx8yMTjzsuDg_&TT;tn3jo90}lY*DQ zjY-MNa%g3_rU%U z06Dfymq%~USq*e5t23&MFR{P=5{OJ`+`~rt-WyQ#4iErKNNL($ray19bw*J-``V

75z@gxhEFfsVnDaf+J8BHrq-mjORfI(3 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�G^Ne)Y6Ywfr^+rGDLv&Y+{p0*Thn^@eI7AUS%n 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

(bO$a^0NgfS{f7A)DABSI?m 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 J{>3}!e*-Y+*@6H7 literal 0 HcmV?d00001 diff --git a/site/docs/docs/nightly/docs/assets/images/partition-spec-evolution.png b/site/docs/docs/nightly/docs/assets/images/partition-spec-evolution.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc595f686e12acc58b7ba0872f564221bafd7cc GIT binary patch literal 224020 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%0AWr{RZ0E1|;F!tNMSqzGHLF$UJJG zWe{xrYe9;#13^(&727lnmi`2!U;Y0}R=FFQ z0wafE0Lx;w6^^|LNJ#pN-%j&wfVwh2&X>FXKbRxXjqm|B;4}Daw=fnEb$8vG((C}P zLcbKq@owllA)n`uKC%Erpdm8w`tMbx={y3xP7~w~TNZd-yz~h=XvRP~)U)BP{9)tZ zHA}&_!XM5t{1X&JRigEq->rbV9hdDR1hD|_%~ZdtSyr-J!NYxdVgs-+7nbwn1iuKO zKY&*(l(nE}U`KlREP6%x*8Aa-57vj=Lz==CVNaa*#cA3|KkQW(n&3}PQzJ!IfuSKu zHmR%Z6_t**AlX6#Q)HEo)RgKF6aVL;s8Mi2A!Y&+bo}B%1eRBPfN3L#nQdge5{Ot1 zqG&=&oE=1agrKbdggNfnkl-@W?-YIaK0h8CJ}cLUQaDOF8Xu6cMClFbZnIYl_|Aiuh|0Ncc+{Us5ld`tTJN5oWfg#|K=iqxxb0$*TsjDXU%KDPquSQ8M+^^xatqDd$|Z z|1&H=FgMZ-i>A@w4-A|RGO`2C{qz4(^ino}L4qPJ!)%3b`#0|do*Z4|Xb7U%)g!`8 z5eyoZScc#&Tn|76Ov&aj@612v=X`4vJdO~0#eMrVj16$(@A_cT!~Z^xE4zb>;OO<> zFl)6gR1WWRA(bI5PlPdYb%h{!K+6?)0g!_3xX#bs7?RON{t@s3XtanC0!WVBQ2gTC z{(dx?S332f9UFn7ghKP&<|@*)uuhdLLF9FYTaD()&_+bB7y`Y2P^XDJ8~MK~qz{|> zx{UMTxb22ok*T2Ntz{`&(>@q>k=9T*r=Pt&8T=lSJc4|#Icf%vLB87?1(og-^$T^v z{>`f!;wx1T_n6%QmUPH<9npe^^^2j0uHtu&_YWRfBm5G<)dnQcdE=x3M`!k%4BeA- z0VZv@?uBkH0RVi+H(lWh7>Ua7-eA#y3UGRP1c6MnOSf^pt)d8F$*3g&e`*_NE2hXX zAft?>fcfDr4*plXMWaE+$aH4xI?B(3CThnJRZlYjt^pj#F<6~{-^vG9Rz4p1j>Iwk#3*nf!zTW zo;%VtrI*J>WDn{vE7s)P#41#V+aW|W+;TGx*L|N{-3P{~o5JADup>vI>)-5ZX7D-# zV!>So52pzY-ksD{7~ARsi^ExTQWy`?PnpdH+DZYG6#B&ocmTe3c()YMQgnyR(FKxe zn1xE~7Ka!sM$_6*rA2^B_dG4J7?JM6$&W{tR{_`BHY|B}AUO(JHL?9ycc(&?DWw#= zkqp@6!d77l2vwuEy@ETvQZeoxkvpxfQioX?K}o+VeT4^Iu7U^M&@8w zZ5i_)=b6rm+&Rk@AUS3A`I1^7g*n79{GEPiB}5W4i}C=ro2xnZij_xj2Qe)-PKmSZ zf&*i9+%be3!~(C>2?tzvO1EH#nlBDJ>!#qAQLrqawnxh$C{5Gw84{N8JUQsM&&_PG ze@#n;pmDup8rTs`CNELTtlu}hnjgEX`2I!Y;v=4WyqP42sJU*XP-9%O;t zb21x zLRh7UkQq^$QQ1eO(eJ=~jKTYvq!Z4+R`>+@a0ZcufORuWN^t`Ex5x)T@2`K9+REQj zF>h9YGdfP04@s1_Av3!2uf)nUaXiZ&FxRV7NWGk{5IyvL32j05MfGMeL^VicpYgTh$% zan@qJGGM!aJ?#%Fo-o{XtHz02{c!CVh%!qSM9*B)s%Sc7R*#Fx^2ksovN0#ec_i!H zT-d`fs15>&C;SWoY0cr-zd!*)AK_Le%tmuT1F7nYHzre0ay!Gmz!~Vi_N>n#vdlbm zx5d~_RJsd+Eo<&Q8eVr`#JWJCnVpj@)69s&j8FvhKG=T;$km%!-#&900U_MTbdKxJ z_G|Z=*6~`teKyAku57YT((5>EX~8nY(?d@SbLOj*+ca?PRxDc7x2!O2Qc@y06;9r! zlhCOMSI9xy{RoFL7cyJln5k@5`MP0U`kXXv01TEPj0W?Kq#r%O9zS3WXUx`5j&hNZ#P;^KV^K-VTuaK$ zI+1ZYQ|@&dIg$W6r0lRTeXw^S)y5`XtJK)WDTP@#HA=2`Xbw;3j=<{q$}sx+45_Z^ zQ{7#t-G0dpa;f0+ir_Gl8wG7YL#+hK76BfjLQ8U8RIA%JtDLPhbjV*i>Mi6Fi^Y;G zMMJ#1IP0wj;Zj03?OAzHDZhaz%Dv>vc`+cvHs2m3TNf$&eHCRQk;Mv9y)Dy4NdiLL zf_#k%?RW0CU%iU+4R*LUD0ww#eOB;C1$ZhN3ElB%1$(9)0#0EE6m<_d*S9!72u`i~8{!D?v=lY?ow{ZL5Wh&%xy0 z=Rl**M0%V|4MsU6eTKD?8{3#bN#ax#&w`D27pVFGJ+_(#=+LAAS=g`9Jz48m%_=~1 z7)$rfi-7{6^gCgK1-l*>qT&KKXAuj6v6Y}!*#?HSU{Egz>s|aXo!uZl$GJZ@>3<$U zc_X(%KF590^OqC|k12qAe_6?J36ZguTJlCzX)zu;!8Rfux5(z0ktKw>lS|zxRv{Jt|cg8P7(Vr?6<6&syw#|g*|J;O`eQnholP8(?;DZr~fO&$e+-CIi z$s=vP6SpRdm12`BsR}qEi45BOYKlA8|#Mhkg3ofNSF?sgG3+#`TPRf}xqE5bW@H$=eb*!6?*@d0IR_Jjb-Fhcx4u`p$M7&R( zs&Rk5g53G>AbwkfiNXZ= zoxd1_5S{bb_nKI|1;f4zIMmC7i@W^UdIGw@0hY~#)5H!8DS=NiVxM13AE0;i ziEg)-2x`qKgx`zTp_+)}qYaOd0;Wmi+5>OwtH0WjVTou2>o<*bu=Rlqqw7O^LoQ-u zkgy@mz`lHu{VZAx48O_@c}rlu_FIj6449PG8lfO)0C25#($yuN$W|fzD!){v-1)p| zx#}t5BL*S<_0g01>dwz3IftH{{oS$HiviWZ$6LdKep<#D!d5Zdfp!6huq4VVsBgNkLGsJt@d46-Y3*n5kk73>!DvO*?marZi0eOtB>=bL;6`Ss&Eub zqqtZPk3*&0)2&za1iu41DRsQ?J3iM&S$d$}vKA@jM8#@QO`Lnjf1DBf z$Q}kQPkJ0zDUdMajP)|^d#5AF*L_)RCiE&%(#nNi$N?2?Vrg2^^*1nQErSLLn_*8M zPLTvSpmdFYny+)faRpFxazaH-ux#*n1k8G^fOLWDVU(JCqaCe!L6J)};-C1QjlNo_ z@&V3{nh>g;>^pw24vK-TcNmQ5#HwBqs@HN$N(;qxB9(uK4d3SUO~ls3w&Rr#wT1&jmf7$9X^B^>nG>LmzNhlWT9TS{bpp!HgXtBUL0g}czo4tpcQgNT{peM z=?uzEvLm9R0`BQ{^BUzl8?&N$z8NmAaTw@AcIVp%K#+1OY=b$n*_u5+aWPfPpk%xZ4Z3@gqgBbK z?OHLhzt8GV70GAqfId|;jP0%`jy3A66MKv8yX;d(#PWB~RqF?rBu^O5Hz3h#7r4_n zNYwO2vhz;&k>PH_Dic-GHS!16v1=B>H60$b%!*l+}JFg%38I-U1_(&CQI8`}alx`h>XkB+k*y6~<;Nst%kmz;;87kQF$u7ULQz_MyW z;IpSQbS}p>G~}di+zJpKDD63lb0uh&=d6-Ew|M8V!RY7tF^gn#sM(?WKY@2#MabuNI}!V;;9#S}QO82>ru?)+r+GB!{036_15W)mj?P zmu6X}izHTe5FRQed)&B&@7Li=;5G3h!h`N}EB5l-zyQ(*Nz9eG^Qp7rYXd4hbC^*& zu6D#a$Vk%o#fN*7#^>`uhjf=(GP^-ACo@)t2DexDW*y}c{#-9Iqk&MPUAj2W$u1L6 zl*(E0(lan8r~28cmgZWg&n1?J1k-31oqw!;Mm9Do3*eCgC|yZ6r2?S}wy&X2fJRCAQkFlSLkN zmi*B1puIxas zjTbY9viyQhCp*zaf@V$ylA(54$v)b_K?3%>TUQvE-1N{)74bB_dK1QHhOCA%_}HTF z7$5%RZLHwPz9Bs*;+~*&NObi)!6eS%V?F1$g6WqZmC)lQk;M+pEz%QXblPo0J+c`m zWhXG6IlWfIArl);m6f+Q(mTs=`~=Ir!DoOS=fViBlO%?l>eR{OH$H17UmqC=;?}Q! z6FqfB!z$8f{(*UZ;UqPf-xnXb&t`Xfm9a?QhY}};e2^$1s&o4H{9HjR+PFh;-d3QJ zx6z9*qd9&+7?XJXonqg~yBIyc4YZ!6BjiuLr;{qaiPhh%V~nqzI@HFiY2G(J41vp{ zZ*ud#vGvg{^7e1M59U`Da`>yenXOo?Obi4R0i|)vly|R{=qyA^oHmRtwYvgh!n&K$hQ;HH*$J6bIX_u)ebXK5oLxLeHP7T^>%oD{+Nv(=_t_s zYAUF?9P&{yw%eMhg23>rYD=-R13TNRO|Hkw*N%0#LZjFwL!DCh47nQP47jNn1P16~ z)6=JrQrND(b7X!TaHd= z98OQEFkK5JKs9E@Mt0ikJAY!wn1Pq?)_wH)7)H7IgREmrm#q%#z-hIkVMon4oBf zYw1+WT6}GLL32Qt;L0;^mpIQ={Ef1UqN=vsAh^uDRp^qm^O0rQl~Y>+K27h1r0TZ_7T@M0Y9ycMlDgp^tWWkf|Gr) zrBcoxE1Dc24{hTWHGdX&gKxaxnns|=ZBL0|q16{Ebll$+f6obOb~*>FT;hZ(w2l1B zv2%_ksJ@d(h0Rlto-g`&QZ_q}_q4J4C-ax}^f8;i1|6)1@|m+c_O$@w*fq;sO-n^Y z`o3QW!N|tvXrbqE0=if_ znXO`Va8ZON6D%W?3Z!k5ni=G@K7@fel>v6@aOfi*Y zf}ryyj=egHdijm2U~3ZAi5mOwXr^+UrQ+y;=XN4NHrH--2}k*@9lNj`6Sju1z0*1I zR-ZQd{KB86Z47M^U`7Zs{Nk|4hpcnnGzJnAleG#w8!U4CHPSwv0Cb(f*yHD&WA*p= zhm_wC_%pJ_m7R(8n)aAVFcN5;b6G8p{N~Kddh;45-eu+b8J(yjjlstf(mIau;)tqO z=Gr;f;oaApoq0))tes}*zLHHF=|soBwBEeyv{i-H6z=R>V;O?_;+G)VDv@ag>E zC%Z=RqdyD`s+%1*@E4NLV>IQc zKIy zgfoyE?5B}I$24^M!mpwXYw|Xt1tH98{Ze5bpRWdcaQ>UCTYefYMIGnGtzU)vkdc3E zOY5;}T~Fx2brp`z!Up~7Jd1JA1c#b&kx|Dr#&+J9*eJ$~F!}K7 z6cIntPW%o}CYP@gj4=1V+AndF+;ia9(DH}u7C$}FnmOg%J}Z}_)!igyX#9lGV~&L# zCyw_#aphpGzFOys%B2J2GI<%y+gj78lKs6@N&!BtE<*qkFOF-iT06 z-AJW!`j7;g9Nml?Mf6XP^v|#totc2J2vtpwIVa z^qJ~HCTl=1bV=KI`H2{3U81s`v~j%?G^L8aTr*dJv645UTfUGgI-GC3 zD??IUFwS$SuuM9u6j$&`=k>*zs12YCwV*vwT=G?v?tW&4yGC}cShjT?h}F()61Ass z0y`m2)#w7&AJWr~w*`F%r>WAPCRARoBt3pFzQ|9iesGZ6SjDy>=+gq&pmeSG&8T>D zoY^`ur)w1zKS#A?mQ8xx_o*k_L%;cBAI%3Uo$s@qqW{JQlHMfsj$ZFh#VpB69wN2- z`9HHKpSS5wa(|_AmKN%iH1Cta(Hk+m!Zsp(^ZbYqgJPUsU(M9G1EEGa!WA1*6nNqK zGK`->P3WNR>jqEHb${1bE5b`05Vui}-}p{hGsf9A#eVCKL=J~Ff7CReYHdbEmPTu> z*tHR1sCd@cvwzt$d|4J!ML+|tj1jejVvE*k%=QX%cv$-Kx|&eAI#v7a{i)Y%I-vZg zBil;2Vv}g1n50PVwM?H>>`5K;=3;iBR+FUb66U_y%X;Zr=mt}l2JtvSMG~BLuTG)_ zO)u{ELxD<_4g?NII$t5C>0-~4!uXQ!CBu79jE?sRkf!aU(QGebnN&0;J<=KV*d#SK z^K;@zn|M=Ftg(8sQV_Rn4k4>7(`0CM=j)AEDQ}w`f`(3W{Bmv|lSxkZK>jw&^otvs zL5;la)BuCk55t3$!o6g+@1CX?h#q`mCjFIWQnyUz(&R=AjZ`c4P_AvIvW)bm4U~|f z9as@ZHo3`PEV z=bfWR?V-I&mptC_?oN@1`4%TRMrlISuXGCXaztbv;_CgU$`Ll=`+LSP8Y&ceCyrP}G>^Va2d2`*vv)5~wo8aA4m6Ha6 zqjSqS_iUDE+Pk^$@>6vjb~3&kksjD2SYYVdS5mlVgU~$_9obyM9S*Nk;U=kbi z6S5!{sY?@NR17t{WgF=SrsW9={QlijCp$} z0u?$YB}nat52!FH$3oXIv1^Jde5Y?P_ik zm`OGYV)dI-b#jYsqokHVSvl$ z3bG9&=jNf)eEDLiQlwosacflV7^Kth?O2E#9TkV^^sQhmstog|Y6J@tKjN8wcxg}= z-b+or>oZQ=9Eg|yEw3LY+SH4@3Cfb+=tnBV|bLMWbHihoOp?Hz8fyB(+U{h ztB6b2#nvyEFm}gWD4)x^J}X~k9Z|Sx5i7d()Y*==*p7z?{q@juJN(Q$p*}9Vyz8qR zJCVG(Uk*c9_eERMu$UMh`k9+wnT{Qq8z|ptaq8=+Vlt`{_?1f);A zym;|6EdwLz9qs|rH)+oBA^$P67U#}2pBDl-zmx2xysx3%`m~%R&Olb za%Ig~ye84eA!RDvF_RJb;*`C_&xbZx&H8V->qan6JgU9m*RkbWcI2-E)Ib15ql$*|JpZpQ;PGnXNC9*`CGj5dmd}KXb~N{vi-z!0MxlhKMJ;b zN)o(o{<6^i#iR!omSMn_;ThW_>$m~0ptqq;24v3zvsAvia#T>xQa_f$h^BVIrBe@GTCoJF6xEc3F8#at| zCl(Z1sq!xdxP=^9qO_PE?w*XI?9rb9?+2pi+8d?rWph1+c5BB97V=EbEkFJ>eYXx4 zF%z&5te;GCk!!wGY9622>Fu!O5!|+B>Tsp-KDHp)XTu1Aoi%7%kDbJQZT^`cL|)D@ zQ>wFF({+n>jd`iyN2Vkyd)Qpg@su&8rZnn_;Ul^#p))Hv+oK<28;l|f+ug;OwsrJ= z&F%aK6-8O$o5Ik5OTx!H)k@Klr7 z*ZEc(84KD9v!2hBZx85lEZ;!k`ybli2SfE)c8d@5UCEY+mh?xv|>f+dNNktCV6~ld033o%Sl1u?Wr@z zdq|Gr9MujrE@W{hPm&iUcufqo#S6Zeovc%pVrSC|=QRt-J@XD7ca;~h_J88KSi2w{ zB-ocV*?NUZjBnKQ>(LXp)Qhajv`Jd8`NCW;{pQm-(vN!17K{r+n?BVnLA`8F1&NYT zriHX-KZj2`&(~8&cnT}uSJr;?$oc3;yDQ@-wU;V}Yi42ufau4`aFUyTtaONpFNf3l z0@1UlA~bl|`b`nnKSsVkxxU2WpGT=apXSD+;+pL}muTBaWPDcXhd}^4%Q-ax7R|En z7V%lF_s*)hwi4*{c&I#uLeYF#tnSM;hbPGObxmpYK7wKkbukxl=dLCs$O&HW3{Y8=S z98VrFf_N{JdJ{uVF0cff>I-{hDxbW5;Tqx3|6 zJX3cJtC+GL{jpjzTl~3M#O=s1-ryG1Y>7O+hNJm~YLhCi7hz*mcFvV^Cr?~*c5 z3^Md?6y6ssFq7cF~`Y#K2UVQR-eAeRjm#Uapd5<$mAh$%(ct_TaHUEZ|+uBFr zkM?pkLyKcKc`jQsF2tNGRpr_WrMWTeMy<`aP$eo5G|yXY%SEbYtIg6lY*f*hBKkW= zb=6LzD)vs$VCyLkpRoHhYIsR341=*=I}1K#Q5yq{YQYskxH}J4!TJIB*2xRngTt>F zhgMHf*!F}bJ2Wtq-hWzcS**|Uu7Gp;&h6fDPrgd#>=nn9?CF)*bERhl3h(E7)xK`v$q+h{fcBlYn8?Fw@4gnz z-aXA!;1f!x^n6pj`eONjJl*yxjLaR@?jQb2z8psTyTkK&zL{Oeh;2tyt|Ra7*-BFR zAR5w#JCk#7C$rz4Y|_XbV|9ry=U>}mk%_)-_^$|B46LYqJ*u-57_kQCC;`E|)Y^l)Au}aZn8j zG4CnL7SoN2lFI_vfBt!S$L*r#u(=)+zCcBC-B`TgSqpSeu1IUb1LL|`Ikz0+qrs02 z4r@nuf1>WV8FMA|@I{V`10eo#I!d2n=gK)>bs=aann_KF@2pcGi+YcYmPPd~hP!;F z*F!7@39 zDy@l8Tpnkpy>O&5rEdCS1@y{jfO+NdK2U-?r^S9D&8kG;e1m6k8yxgN3gwn zk5IN|*)7r)a$|*HB0l*ot%{s? zR4Qj7-Ef?)DXKZA>e!9fe{bmmc2;Jk?wu{mdq3U`G+2**TdkR((&i(jap%PDN=#yz z9KNa6^s$Dr0KdGmup&{kFwm3GQ~dASeJu%ZceMFoyKjZ~Xtj?ak2g0jYoUP?bX>)i zO?&ejZKWRRtv}+xC#fNkJvy74_J%LvnR@9)^Fz!NPhS86jNU~j)}`EodWE+hN)>uJ zoqFEy^O{;wDXJ0^(m`6Oc?I5e$vio|R7FNF-xU5wvD+R`H9$FQ(!lxI?M_YDO?_M) zwvz)iddAEY)04GB^jC$xe6NO|`IX{0L8TzAMDH?A6fqU+eK1KIx@`Pr%so~4>{2S} zJ7vxY+qZ*79n<-!wIlB6GH?+Tk3fu@*0G=chuc=)Us;0(Zp4W-M_% zRA5AK`1y;0!=o)OD4JMXuv=I?lPsB4ac5fEwbffL>tk)YLr%PFf3^JR@AXBZS<7E( zRW~c5KvVnw@%0v9QFUwoFexC?0wRrsw15)QDIp=!-Ka=+cPrfpqew_2jdV9CNXgKp zba#Af8_)4M@A-e%HR24+>{cl_>LTeY9(74=5^PxmRXsMuQkG%Q#{dBuQ1;;1$m z)qcL_`rR05twtoo|Ln1MY3We8R$`tfLi+RXW!%(Ke*~ zficJ;kXUjJ^gqp&X{owyA+T#4J&#RX>&o8>pR8y*2aGW9&fU5Y!ZgSC8k5qBV7h9* zQSkENwgP3}5=oV(ffoQ9KOepUbihIwo|GP<&>GP1&e@Ri815jwOq|WeWS=;7G0w9q zdD-fFd;Up{3~xYlzb69?)@op&4rr2|*LCEx%m>x@oM6U8t6^kiopv-%)r4)A34Kb7 zqzV`v>M0*k))jm?hp-RoS1TPuua6~xzJQx#1uv|7&-hvjF|Y*)_(ee^*kA z*-M6c_F8QOSgzGo-|}^|J}UyYJ4u2$1{@*BdXH&wtx!tL3fYK6qFShHgmBnUph-*H z=go5@ZB`qSh#ou!2KDL@s$=yBe zgE{`(sg-97_O6;Zt9}-u+e4hq8rwI}ywMez68rMTv$ZHbkj}pqb}D^g$!E*A>LA~; zYX@fiBTjHsfuazf!RurT0f9+gn>;Vaajh|Ohy|KOEgnSj`6+$4!jcdw;2AMxU+hG^ z?n$BpN76^na(jn%rlzy{+NWAXh#Sx-AFBi{@lSlot!8OO| zs2StpQC2p}*^>arIqqC|zgvAo-El)TIq_aB0!AC&2QW0f$+28$F6Np5a*g~kR*QQI zQ*#fFIiVIilGU2|f0!YY0Wial6(2$L7@td@Uj5hlubSqDKvPowO~;ZifzSL>w~uG6 z@Ug{a@onMNwh5cKclXMeT5l6hNzj(>~)K4`5MwCyx?3*`Hyn*%rj^ntDhIyWwd#-QoTjpJ zG-fULt&-IXwMNQWT$=3=)rh?>4iZgCOW^*KrJxT_uxmo*+{ndiPRw8E#Mr6DG(Ri7oEI{l3uB&bC77@zPo*!I~G zx6Rb*m0+Mja4+?^w+0Mz0UP5G0S3YNnLFJ^^4lAhdY);gU> zdLH^>S}H7$k)Aw8AXc#Xp%QQKCE_A-GQgbq5IYaWd6fL2gN-&F#D~AUmH<4&Fb~7~ zB<3b(Vj$gRfS>A{0KD6K}DhcHsj;fs}}3U*w|@Y5r0 zYJ(fn&(Mr>b!tl6+S;mf+46Sh6YgKG2hbAU^n6L+O;KIghN!JfSHMNK-&BggGeX@o z+k|1!%p!%4Eo(Rtm^yRZI(G}DhSRhsiNemEL#VtKj4g#Zu6kcxtdvFgW+9xWCSfGd zlq|N*?E@c^4j2Lv26y#aeQ^CKPJPuo|-g6r}+g?Z1H>p;>8Dcni5PkmkPGqC{sJuB6r z-FpP)=S%x9>uqvg^|F-kcGBG|!=xH-ZO$W~CIz^jNchHjXSsVVIE3j`LWpbNxX}OY zs|PuRS$(4v%YoDJjF>}<^w|mnb71Oe;-anSR+*Ry3`@2y| zF^@0cxE=wh>c49fg@T(8MmpKU579q+h-6ayj8a8Pxw9+C3<+h z7J=iIZN5u#6V{Vuc@`wx7gTSLV@CtY(-&Oy7JP2B+<;ThD+i~C5MvGSSw2#;wO()+ zwd$qrM< zDO&STHqn6xMTXH-PhFrNRiHB-Xgo8K({%>{^F_7uAp^Q?Nr*WOA`c4HG}MP7gegl_ zy)ci>_BAOB6xDXOg%T_X2Qi?b4?)1Q8-^g}oz%Y3*8Uo_NY(fuC`wiH8W9ztB%-Sn zRq8%y>H7zr%7_7U8e?Go9{eU$)Bp@uNU)v6^~a$x07D}@tCpx^np837S@ZDq+-7A% zLs4S<5OnrtOBCa0rEoGTCQ&dMxGV^&^ZnOE>G9DYA=$LwNR0Q&S7X@OY_TOY0fa#$`xSe=xPw_}K<84BUE6RiOXF<;oo$A;G8%C4}4MEc- z#hIPhgB_`*2aZlU_?^!Ux@k8{Uqr&~Nk_sE}w9bDW|%T<;HQ(fo7Uk89DUj`C>rhoLn>Y*1Me6dyoQWj3u0Y#pj^-;qhc^9 zBfJT8-gw|2m9?!*vL5qZVVSfRJbj0vjPfI*YmY2BW3p`<2 zaNi5T3gLOrbWo>Xy8;c4WTBKpbZi5L-%x-E44`zA^=DG+x_4yfApElgLZwB`vl*0Z z=ef$DHz5m@B^Irj{6gX2r*GyR2z!l}nS$pMG(MBBZ9GG(8LZ|Lngj9mKH%0WZVg4z z=+nqkq();9(0758QsQV2w`SNMMUdIb=NdHo#ee|;rZu25-Noo)_0v-p5hONru$P_Y zb72AIj`wDeiTgP$;s|_D}j&>xnS1$CTxsCiiF?l)a1gyPf1WtME>ory0sFeJZQvY~l{AR*|1 z7G)!1y6nc?65PERjNuFBd$(;7e$D~~;=`6kNqzE}&d6x{B`v<+gX@<-0g}h)0}W%- zp19EcxsO*s2T|aS*SVJE&PE=g)=hqrcbREx7glXC%>&)Y)rB!9|6#8Xh`o>o&3^x; zDWVBLV`ea#M9IuwmC5L+S{{tOi)GO)y&De3iaZjsO%46cnZK1bl?Qs=!KgMgM#?nU zz%T-n(JFStz4@)E|DrD%#lL_OEg}F)G6-q%F~6Xb4>@Anx+_T6$4`LqRn(xBs5HN2 zo3B6>laMKs`5$^r7 zURnqj%Aln8|G8Zf#OirpISSQ>9>*nQyf#e_>I7VOr9r!F{1TXT%|0l6Rk!v~^N-~J zq3;g502ucK{4QH@|BoL(mVyC^*>*P9xnnq_?JL0k(wH^MFTj7@zV<= zHII^Ie^nS07!EB8nl%^l6w3NQNM^4>HB%Z-an+da9|KHt^Q8LvACf&rClhc{0+mq3 z>-`{Z1IgM`U{7xfw__rPV zS%^lnsJ?3qLp>uSn(?Zj6hP;2ePZDgNj_-}<4AkRrMy*pA>CiJ0PZ<7L~A2 z(Kd5{1kw|Epg>g=8+rVLyLdJe-%YD@XhGjXC5xeR!CwpKP3yRK3XDcKXf|E~s;#Ii zQ#M`}Kvz0Yoc)pluBXVLH`zgmpji2||hc0mTnHg-BKt?*yfFJac z-XQ}J@lbpe5DRw-#e=;ib%gypNZ;YNt=gwTzAsSPYD99X$90)?3w8g4eO{sK=%zol z(vdd({sm+Uppjd^A`q(AzYY!EY2Q=RyZP8rTv=U^nsG?~>8R_1iB?S|Yb=~MRqMnx z?8X?aW5t!uL2xpph?^o1B!&3J&IN9xD=mfxX)4TeC@;zxG!>)++PM-`Q`?z#v-YGG zl}rPxn>tH3B`*sTs*hvQR0dIY!@CJId#W3Ap@1XWogU#x04z1)swcSq(Jxhq#w&4G{Qv&JBW58wi2OAz411N+ittJk3&=JMz zjUt`;RbMXI(U0LAQxIw?N(OdX9w_<@mPF1mQyJC6^8cZ2A7iv-$1!`uGuGiC@oECn zU`6~9nEutgA7mNr+GKmQ**n{j9p=brjmkaV5jKw^^t_N(DTsvWilKn#?Ll3jpI zfv=(n!Cgu)`dX}&^&Grk)vmG`c3tix88d8`6YfaIKCGAoZSag zFTy{8VH@3{1PnSjU{G#LjG(*I*iu}J!6)`z8pnr{*+Y83y8wRC&zr9PY&IL@c_fgfy5mxfL=j%j7CLrdDj`} zB!eu9R!z+Qi(R{@Y%Y{u=}aa`PE3GMYZpM?x;V7nm*;k%CA@|$hdt_alOTp>ZzO8X z5EW8&Z$ZE>(!vkrS$F3HfZ7;n^lQ()$%20_pKs?wH|yKQaBnHNhSOn@IK9EA)ek4Z zBX3_UhiGW`$7wW9#GRk-F%fw?*|u5gc67TpI*wUGO~^`6faFzz_%CWnKSr|TKa6Ib zbRtQq1IBnTg`ImeZS4}6Rv9o|1?8$j4*fwCUHB6dFkS97OS0ScQ=^~mJi zAn%3x@mM9dPq%$(q1jp*v}aDKbpOtOVMqWWK=L)M_+=4GpZ6*}pQ$O1X#-IoQ(&u$ z7&QY?L`=b`f&?&}Q8t2%kJduXnAh)Yvyg?dZmid_7JzH;S)dZHNjH%oep++r2(4iO z`e$Jm+_Urm%B5zNOiv=~j|tphuk*mi$=vFiGaw|h2z=(ENhamY5Jkhrt3=^AURt=0vk^r?&x8s(!dh$YZ3!(QjO>G zv9mVjtWsyF!I7XlmS0_ZYVwn3Rr!z4+M1nU-nS{h7e#A;7PU9obTl+K-=XDq*IGN- z|6_k?$-q@HIujoJax6mtuWC|w6jb1&>bz*N51GvLUhz?-GCFS#!vUqekCihdFib0i z2g%sM(j9(ztWiFIU^G;S=0F4REwt$nK?>6qGU?_>1Pp_5PmCL)Ate{=q}a`+I6}}C zk4}H^{mhr;%)X-Z*}bY4Po6+>0mdK(Jsi%I*{~nhEPD0rwfVgZbCREe;rkh5Kjo{o z+PcPbEj$)_Cc?bC;#xPZ*KdI2OMg(7~YT8 zD;N^^Ee=GM0XZVy4QSgh#gROfs{5{Nmc~6p<+@^2Nnm5e{tRpo1yE z3`<~Gc~M6s?`#Nlk!%P~#Na{hofAQ=l3PsVF z!a!Oh?!7+uytr5*#j(U5LOK51m&2BN(Sq(cw|D31NZIVqi>%00>1hDwIMCby=a}>; z(Rw$)g_s-QRa5ij50`%d=wFf#$$}IL5&MbUY2CZZ>%kAEoBcbSV-14!!KqZHUqq4J zdh17+u4%LOLLNUK#HI}hydIqpKS;H|GJ{BL3Gst7l64)|V=v8W9lpK}tX1jQAjeFb zcSzZ{=oM0^-*sPu21G4=z zJY2e+VhfNa)$?o;U)?$n%6uH%A|Kx;_d6f|YfovT`q0;>oRv~4yd?RU+LpvU8kAxu zmRjc}F}o9Cp13a#SU_k4w;d2ha%a*0dl9saf#J!j5T$f~Q7T6P7TZTKE{LVdqr7hP+%_=JA(_+E;~mO=?g1B8mR#^M^+CeNromoN4D5t}mPzKFLI%5MO}prnI|r$kuM9Sfze(!#gJ^{FlSU;bCrm#^w!{^K+>W;m`D|u=);@vRHg~|uT&f8c zlcmuPZimnRcHDn`wTKD9c%^5R$LWcQW~I^V_r6VjIP-e<-XWJ`K{~wDe9-FI-e!-f z>G7jtzEw;v$oKXkftEq~zm`F*w9#xvjdK8G14uWR=~JDDIqF_A@BaWMM98KhB1Cci z2_V*f)wDhk69w8d>KKKsL+|FfW>!);M+~)PP}>%nHlRF2VqPRlEK-JHv-9;2GcIVp z)xmPXdYM4aN&`m~OZ8xLTJ*M%Etf(KvE@f>2q!_Yu+!x?cd#L_I9vTOE(jtI8bluI z&vSuermY>8L33cDq^fd`!UlRPfNV>kM?PZr1s63oC4BC~Q2rFZ^qmHYTC<*Ci@S+J z&kAD+=Vs@$J`Iw7J^vs}^)->q7r{ypuYybgHA;5B1{EwKmDYUlAts`aa}=n6t!a4q zEa(n6AXz#4G09SA&^Q#s zdyDRI*{(uzv~(0y3X0J1s_QuHvwpDqfRjT=B*kh6Ah`pl>}2;j4NfhYen<1%$eUgH zr3MVee&MOA3jPW^)ZU)X!r)+;$O#}p5F)do@sSIr#5x1#_DpVk&<5pkhoSCP3%--8 zUI69EfD^~DCQ3~PX!pz`%9sbBN-^oC1VBD*i9Qh|A;?VOiqV6Wn*{vSaAN2$CqF*Y z5t~9^E#i=>9C<#w#V8P{I-WmhmS?c~{7D8N zlo)X2AI7}y_os(1H5VZ;8r|{&@v}VtZ{;850<}y=~PgEsa30* zW}{(v!I^XT0>JtO?ZO*BCWRGdX8=17{`@%&$SrVFNNNFap^= zaU_SaZV1qac~FR$5Q59%iO&X;2=~4;3&jLR6`hV_}mo$y8V&;y~;2=M;y#bQ?8p0u3iAN z=lF_KOy|=1CC3kSx64@{Yp-0m;MITGP&ydDUSA=oN{sCF1Aj_Ef5Y#rN8y!GZ`TFJ z#vHP|1!(SvrBRQFVy4{eWxS{nRI${I1TkfeQ1w_0aGMig)`^8PjY<7m% z5JF~C6PP&(QeTGjb(YR6{^z&AEXcDZaCA}n>|LGe5|hDnUCptzdRSs7W&KR7lLp35 zvnVnT(kO}5brZLtm*C?Y{6{@A`D4AGu(VAsf(m<%5)6_d6T)!(+&I!=EoFZRmr>=O ze#uA~8T1Jh;KFpGivC|57Q&4L5QPLl7ACh2e>>IG3tb7UsW%ywB{T;;ALMnRZ5`gI z!BYE$VWaM&xXG`wGCLLAw;721z0h}T#VjTTXOfKVfQ>pNnbYufER%_5&iudISg9#ZpA~MIM z?}JnO#E%KHp-R(*s7ZsLZlnm3jQDKMNSW!6O4EebGf&I&?iZ*CKY!Sz_A68#^T2nB zd9-Vn@T%*RcU4zHVXXkEMb`B!)5Rzrf&56qpJ8ASCIK8Q1vr>Xq0jVZZ~zVL`F;Ek zhX!0rB_JHi26P?v?QSs4gbDTf%@P(E`5o#?(*3~^A-P8X;D~Ejnfk>FyAo4cqKW3B5QRQNkodv>+6pP8!t@Qg5a)M_VA2mb zXY~*@kQTHBDSuF+1|Tp0{jRe*8?)}i`-%MF#7=Rr|Bg;bH@!lbk6_nr&`My@r?VKd zP5`s__=75*O^?NZ=zHf$ax}Eib-;{eciIMJsGvYrQNZ6Z0TCpamyjmpqlgc(>r_*L zva9RQB|u>fzQ%rgTN`g)j;;U0UD>h97%=voDNcA`!INU9 z_{Yh9=%oNj(H{)?9!j3wP4_4cQivX8jwt{>BvlOhI=o&{t9&hYeHYA0T?GCGH5Qth zCkXEmp?d+puDlSFeKZYb!PcKDWR;po@(=BQKWSnGN4h&77o2ZbIZkc@tw`Z6qUT-_ zKDnCoGos7_wCe&A_5XDNC72P*sy#rKBL<9_E*&tk%9)@`9=A$IF_M zEt%8*WOAUW|2)uxngD(~X1UlbREyrCY(_AFd_gE|0HviMefa8-3Fv{d>$Ar)0N&S) zFX%Rh7Yd~bxGFns!hbL(fog%1wuV_0$ouOqTLKYSPg+dr z$0Kc^`YE{iU8K`T(dt=%)z>OwpCXe#rd;u&La`!|rU;Fpq9CCO%wX8>!F*bc)m7 z@Ow8a*#Y<2Zhf5N(E}*SV*YN0_<(BHH6G+ISWP#XsKJomD9)^Kxrtg?bP3^Ko93HI zeeXHIs^zZHt_wU8FK!ky7QQH#{zr5Dv!hU?uEhX~$-_^}yMb z)dG7Tvu&N&p5eJr@3_3c5N;(8q+6uDIRU}I;_+fw=0W_qN2g-}# z8QsfDK;hg#Q9G|t`&9o>M8*t^#%}OdjHV&LyC3+c79ddZb1P+=AiBF=HoR&njNm{v zG0HAW4qZa%Hx;&_n4rK)VEx}YG<0_8o-KsNQ-5cs=#AW@qS=kI!^i>1lBPl$p?H~H z;Cgj=u`^GhW9JsnY1W5povZ#%M}|?qGE-f(jN<1#_@Y2a&lj=G?XO$%HGy)qrAboQ zGLVb)<2c3^(6J@7HtEz=MYA;lZX=|*y@svG5l^RfYw7BVDI|En1+(GEo zpU0deAc;#qnma)Q^ye+O#N$DrCHho8LRBgl(oHOEFwIynVEa&W6&xi#&9F+yl*Eqc z-kJBy=l~rc*>?^yie<=o^@l4d`jSPN@KtKSg3cbWy+5 zt8XqHFp|WSdHsUWbCX9do@Mp<83ddlz{QFFCxZ2D0w4hCEHOe;>2T3#4ywa{vWY+K z9lj6v1{9wjrdT>aoo+iV0%s)Az3V#UuuP3uHz{DjsB?-O&gbDwadt^0x^Q6wWJLR{z_+z>M^^)ov z^yWjtrX7CZa1AmtE^|R~{P4di{eM5$#0*wgN7L;V!w=EJGl;;@1JDDJ^S^Vc|5y&_ ziSJ#AgJtPcT5lu+dPd=uBK3S( ze=+f2KP520x}I=;Av61v0Rg7)_P){Zc$1$Nci;8 z|4p$NRNwHA#HikCjP#bTOr%q(HD4wA|LelO20-pHgKqaq+yRUD!n;NhnM?!g6wxIA zYsDg^bbs{o$+1<)*SmU8wML#rza+6S_}8WyK#5dxYJete0dv^W1wbjLE_6-AzpoXC z`|2H>L z(So;#wRE=pea}yz3bwe%_k&3|0sW}&n4p&bpS}AN)&Bc)|L@S)pB)C<^alb1yZ(p6 zpe!rUMn~uN`vZVR_5)#jj9^Uvf5-wTBO(pn5*QE}_%{m90HL>ryUEY6xlEwjkm|Y) zWqSU(?9V3y|EQQ0KFP3~%DGCCBtu3~W!IC#|I%e3unlEjx^0yCGEpEQ5iOBY&jQr5 z0LQcZy&1C@=jCpdEK`+zrGD;VYVkqwt7tRma=+_3ehGj`007;@a|B(R47b5WLxUck za7#q9pA4`*0*>0hm-qL}P$;x$L}2b7EeoDx4(QxENTt!gC4a3Jn1KKH$M7I&2~eb} zg*^i_qPxAiIEgFSu*hFU_RHS8#o+Pk_kMCerRv_)%sWQ^7Vgl*5982 z&n0-lyYYm4*S#`;Vn}@UA=X+P7{e{Noyq^t8fl}(p|x?_Rubnzd%fBh_@t3>H}H!| z3G@Ho$8hJMcORpao`nL}Nk8MALIc*5MY#t1-spddYyNA2&`S{`umg$XZ4-K#z>_7; z$)OhkS84Fpy_N!1Gs{(-wgkCAdNo0uX)+u z%f@?TFtpoU`fQJ8&ua!Z_eouLdEOVA`Hl|ke}zwd)WO}jO2!<3kUyZ=*$sx4V?Y%o z6YifL|L-eCv7rBfZF~v817r;0J9fB#ed}@K?>Ehn4yZLtz zBhlMB^rH8O*`zBAG-(0TQ;hVy04lVO-sZ%vK&RG89)$U}97hV+@zCEi+I->hzKj#1 zmjY-u4W0FoH30~A#D^!cwIfS|%3+gNZ?|wH8@}t-lDRtI&!!7N30`{N0NSPtbFm@S zG0&ZOx*EHK7w5sxhcly-k{m*3qVHNn0|g?_8%%m1EK>Bnv8b@`vzsT_ltN4}uglj9 zk_f(Z@~dBCxOKI<-!Q&k>`m3~iDzX*-Cc3i2^LyNE?Y6_+8#u@xGz zgf3J(bCovP#w^c2?z5jb&c*oHrKMX^yY9kQKe63BetyVa`lA2zxb#S+@NT4#M`LNx z+ z1(jG`&?j>n>xq!pSl6mPbhg<_-~9aUJ*U~53$7Yl|3_IblVd6`^ezUkCG%PMS~d&} zRz$&T(u%zW^m)!}+m8e zp$W`1-ILlG8BE#BKd*O7dshPvzm-hi8t<{?@p{9dI9fDg(gougv>e)*Fee)+^X#-@ zRGoY0Q@2$&A%42QKL4RkmA=f8oPc0K=7QS!9tQKVP1h~gi;YMmw-VDSv6}ZvzO?J{ zd)bV@^qD5cy6>h2R5Mg*v+@W>?hBqYT%ei4iDyu697hLlCB9H)ei;0=JNK%-abr|x zD9c#RR2Lpg`q4(wE4JI4L7}_zJp->^@OW&tBITWAPP4dIB*SviEmddJf%YbY%hlL9 zBv+cXPqMG>>nH0rj(s)P+8Gn(biNcD&h~R~S{r>e##G-D%gmt-EVI`k8rTf`|G_8ybW|#RT5d z<(hEJ!c!+Lfhi>|!C{RTf=W|+Uhl<5debI@nABOA2c2Kgeb4XtMv$TMKIdT%*_0e> z+q`P88LFrK&QMmb@TXe4VgK}2E)VOaggh%0(WNe}ypBsxU8?5Rt z8Qyn#Sf*O<_%TLcf8Fj&eP~%(cQSz69Ei+=(XumH+(=WXec$$6cjL4tmD34c)ng`f zwTP^*tANiY<>1l~Np0cJu-f(2TB`HPy|@9{@*q=#_RgScUvQ+@Ceq2{`LoNoreHE;?0($7+7oe}N*kw3LP)~?))ddnlu*&V&*K0jh`mgR4MxG63PIyOL z*@ZcHebjHN^Wc38b6EQ}y#wcZ?+y$eoXa@$ca6~0@RO&DnB)uvXW3q7q0Ag_%{CZN zd3yG9EmpWW7h8FBoO>*y2WNbp3SlvdQgy@zd}7h;5egq@C<`35%3Ztnmzugklk?ZO z(D&q;3%L?P<-WwX>&C0)yLA`$_L}6rl|)YOYM!_z*YPTCAE6Ot$2L+dmF*7afVv^M z38u(fqZ1bOwLRL!j+if`^D0o*hUgkEaKFk+=g?4EX&32|JQMtITO_m;=OO;Wn-GtZ z4!69ey`Gf{QWxBkYuIpp6i>+Jzqgdhb_JzPe3ayc(aC@c;OntHtq4!86>Z`0` za^yUkW7-vcvo%uXU03Zo|M_8e9EX1_wQI^sG=o-mOM5T!YMXPW-qg4B%2?d^CyRR# zdFnK<#z*j2cOIV0l#ID~U8AjTRT@f{g?fv=F2o`Rj+$@}cx);=M)A?2lTp%SduX`2 zuosV17%}sDrCHtJv7VUY zci@q0e(8cQyC_aM_hNpk%B{mWkw%{^Oi7}F1Ll2)-O0PxX-8C}w9H4GLStb{ccR*G zgd1wS6?xg3hZ+$eX6gBbX4kOKpl<$%B;D$={^rT&NAP_Z|AL%MB3q%MuKb(*_vKq( zd$+fkuabUzvt-a&g6j@HE4&?c5c@P52N^UFBrj=A)o%IRte4y1R>NshSoF6Tc(hhk zd4c`SEBXOR!JP(aRB2VQ6QIo|wJ1_-kH+}S7SD!?9G{w! z!o8>4VpPrsH;WsX?+9OT8hEX30@&@wDUlG`Af;}Tf%Ne$3^$=QA z#mFE+D$;N8&^+Aazq?1wqDFDr@}MV0cx9YIVPcA=?Axsr`7GH*(qlbJZFL8mTIcQ0 zcr(>R7Y%Tm{OOr#%0@5tlG}SZiX94Zz1$pwpctPK8O;;_^Ai1Qir73=rg1NZ)Cal5 zV31;)F2lTl>U0$A(^CbQwp1-jqP-S1t41`=y58nVWhq}Zv&Gk0yqbI! zTSixN4*fpSHdTIy;}C1BYdBYZvov4yzgX|lp9D~pnb7Z2d*8Df62m)bDmC=9e>Z8x zb`Dz`3%!~-*nBvrb@cAr1FR7}0AB}Sja1-Nyu>??B6}%&)lwRc=jc}{8x;;%Is#-D z{U`hFOkaQ8Wz&y{2ZuCw*Vq%A3@=;yi9fL<d!1qMN<;~qb{4JTWf z0Fb-UmE{SRiBONb<_tNYwRC*>_YF;D0k9A-9P!~)FSj0GQ}+1yg{%i2S=}AIgw#y@6TgWM=M5%Vz<5?Qk4z2Aul1@g1XcftQM}Kn zw4d(78QXZ9@inQI_QwymZE1G-HsHF1l-MpZB9FPMjU7_`@J=qZD%3VTKi^Wsi1&j zx9YW&s_)`?`B{6{qf;#+Z{m|>j>Fo4f{2opw`fRUnU@2_tLZ$^_%1ZLx^Fns7L$Wl z3U6Y}`42eTzO}-)rY>_7$wqVh(IStH41SDZ6*w3cuF+xvtX_99{n@7gXC#lhEc-Q& zXR@PmF=_PRKzzJr9gnJTK>#O%SZbpJSrm2907#tjm7cZ=TvY5cFnT-^rK4c5TyRDj z%~G>fcoXJP_1t;J)T^E6=9G zPt<9K4j0vED5YMH*M>B&-4CSMwN)%4I>~TUOpl_WWP4ns!};_i-Kb)8O2cEap5ZNd zzBB5&DEH-FKHO=IwTDgq#4w@!r8&i>!NrWQT=0Y(1O zUPRlXAn^94xlCVoL%o}ee8r&;nE=;RPm$d{W_Y$FTt1Y;Dvcx_KrHyK`CB*y&yOrk6iZcB?M7&QfPbP@GlN z4fS>n{g7)UZ&L!7#==}+W3Q*M5ko5ZnWz+-Y>5^&~Ma;}< zm^a^jG*jaCz7%k3tvH9j?g}Q|#6?iS*b53rbz-x+FzuHRs?~0|8`(=HsJBT~G7FtU z=iwYdE=*k=x2=;nonRbfdf$pPKZYP0XXzT2hrFI!L)vqGFlneD3ydz*I3(WGV^B>*o(+nZ+auo?Wu<Bs8k?_P z+!LR#?l~P1L4Ky_wE2y>RVpI3-TTAQ_sb`BduBL4hRee9VQ;`iNv53(n0QN_Dbnl-V)OOA>nmr_^rWWKY+ny0QZ_Fz~e1s~xOY z!K*?9pI@bI4_9kGN`7!u<->{lhG^hwV815Ne5xLY8|0p^u;lZzGki;P3V1LoMJiMSDZPD zJc0!S!-SMRGbze;+r!?=L~h#?&lp zd$2pvGyc76@FFJ~+D2SA+c{B~jql!tb0PZ+sr_u`(1(iA?4eC%6Cn_SoX=)Ce+bf8 zRti9!D;3S8Que$x^fkM1uVldtVOW73v<64YHqZN_eX=pUrfw!KOuZ`EZ|I2eoh6x21oJ->5M@&){Z2|7h4SLwiZ|f+xH3w|$-39Mk>nqZ_K< z!BoF9e!Ima(LNt{s%nf^H#wxQU!RBe)t|V{agv8r`)ggEiwEDp2) zG;~}A{87av_qF7XF9G?(e8Y|RRFL>ao20}F}o7es15Zc41=Ir%J^SY|s2fg%JM#Y08x|&-bEzLH!Hni5i z;VbK9<+GQLAe-nje8s3efUQ(DNdRCE0vn{0Q7qENxPrkQC5clHk1TF51aT`KUU{GjcDub z?ZM0~QeO@N?t)XzFk-G#BXtZ{^N7ne{x_42S5Y_FRJjbq3bCgSxV9>~cx*m1>@M^B zBd7S1Dp%ieOmTT@6MNI@fzY|j?CX{Ra;=__W^4ZT+`eFBeGSoP$kE33ZZ>23L8$?5 z(Ktx0y}FaV1g|I2zP{cK*+9c2&%cR91Doj0O67GjyHSsOqB6t5h4^?f(z-E~I!V~m z&w`=aWr)7*u0)BuWy{8Hg?E4A8@|%Oa<7X(U?>VCJ#UD{HM1JQ=|>srv&ygR%iL6G z#OkFs7FYDIlp-)Uk>{j7%MaB)8%><5bxd9y@U&F{8J27US^a^N@B96oD|C#)<}9`kmEV{;AoL&BA_*0hi-Rcx=v+&>|WcSS^yG$*VDL<%12+c z67(0J;hrFf&>h<-Zf>N*F{5$pO_#CpU^O*&H7lZ5_UkEbIVV6M>o;wo;uGyN% zXV_f9#vrgj_TH%DE>xV-L+nG`n$t!6h(jZKt9{eDJ87^HBPne75%(>tj|-KzuzMK9g`#hc`!Bi-%xTvyTs>JAr~qPKQ`SaQ0qDh@5zY$6hb zn$LY|T+w!O4kd!#_I@dO%-yb7q5(K%at$rc%twzEyk|aM6|2;z?>1kv5YZVJ>cl)U zcXE63y_MQ{@t{grxzhLObs;HH?>9$MPc`qn#jk}Ra(Z}%KQhfr4F*Tj6kom?Xjthp z{9j30vK9jgA+pD+xle>=5pD+$T@~pWBoU8L2hFJF2=6)1T+tSOYs6PlgLS@a0fC5R zRt=VeQ~?W@W_V-k!>GyzYXPNdnMDw(L9Iq^mTTQXz8a@s;y>}r*YnZ)4%Z6uIyMur z^OZ{-bm~L0pC4jJ8i1lOx4VEwFEw)~p zQt^82U}Q7RbhUgULZSgABdkhZ9*j}GB7`-vSY6IvD`BcX7jr-Pe$k|nW+f@~^$f&( zJ-okxykpcch;?U4Grs5DFtMWEeqgFl8MX2Wg&2l{ zbYBN<)|=9fFSBM|+q;;{h2_d!bRro>#Ap&63wv}A!sh@d5+fch&Q(D14_pH6rTC^z zXC^J^56FWU_(qDG@m-*=Zm-05@QuzJap~&;*-BaO&(zKiiBv7P@4dcqHrZn8PG`+u zYUKb07Am-y3D5$8~}>pZqu{mS=!(x#1i2sI?lP$EXPG`@PDQ=fP#zeNpA z?h0NdIdKu&BDWQ3J(76a)mM1*DM_Nku}syHi?Pa>M{>>1IT_n;{1f5QdhN z9F?JCWGII`Yo13xdEf7x^KY)X_S*aIUTaZ1Jqzosah;=y7KczAiAsHSkS(=q-f_qMk7(c!brF^~xnHk_ljt$0A5A>}Qw3VRq_ioHQwUaOrw#bM{ z(iz{d|Rs|BgB_kDLvZEa|Yez{iO)1^n=Qu z&n>KRH-3}pC@~5x-!P^)Lu)d7iQyYAudLf?cU7h~@^N8{8dxJ5t3sILYM9-@5lleM zF|~~EReW5yq{QN2MAou&@8aok;qeW@_m;Z z=1elYRmGO;jnsfM{Xp|1!rK`g%?p)c`7KzCxS$ zUUj+m(n-#8q;W zXd`08TczlVn5%GQ2l%3~zo8hETj+xV*adgN(d2oh4ir?dp5GwQcFf2^82j0;TvXyJ zxK6npFM=%B9PGfZFXwB797+orE&F0+b zfo!|#0un#hP~-tshCk`Ug+M%zK;y_yJZ2EUH2e%XQ$G5OM`w=$2RbUvu=Sybid`m`Kg%gr4iO({S zhm+t(;`cHYs0O7$`y`v%FAqthQB(YO+INq&o-$&*%71SD5M1rN3~!Ty0juNO{+wZN zmuU3-PXgc4NH@@hfypXvwT0g?3Osx|Zw1aZe*M))`)$xQyQ$u2`dd*0p#h~Q2i7dA zjb#}Z*M-> zcE+k8gC$q*s(-#F$M(!O;x;%^O)-YZlE&>WrZxH!sZ>E$By!PXjn$JoPjbf)tkV;l zk(JZtftCgn2O`uJfBZ-QqgAQdE}r5Aja?NEZS-|bCjRrWKYUd4UbqjVJF~dNNp+^} zh^Ki*?WSi(s);cxQg9(g_-6+o}8xufVAv4?hs-Y?HDbfy)dE7F)&b1iNSW zSJ}1>nq~G0i+4$G2*_MJHkL3@sP*^%=e9nH68x z_c${!#_yW$C75yPY0>;U=lfp8P*z~4V7CcWOLG%Icw$*vudp@0D4B&-4;W~AJ`pno z3}yZb(b(^HmH;@!0JZhQ6xS6CiGnllsi9KDT;U?4m<#hnl;R5vHWQ6e%b3Ecvf@Kb z%H|%;Kx>oR)^b_6(!>SN3)or%I5bIK4rT2L4@A=^=|gdORH^1*1tBy zn?YO9lTHa7gXQK{btW-*<6#}xS&bVwy8h^X3T|iUY;i=N9VF(H=Pf>sJXpo2^*t$Z zNUr@a+#YD_I=!>_Ll5v~n=Ex(Oq1cyAA{Nmi;(v(pPVr@57a$ys{AVwsibcAbI$|V zu0L&(V^#h3DVXD|-Df3~q;c=G$Hv!R4Yxom{X@f-yO{jz=KhC{MIy&K#UFA9<)a9> z8k_1pWMM;J}j)iU($EAkXC1N`EB3_{8F}O%$Oa&x$^;WX<}GHHI1#;jsBs! zx%AMu(V_b0g`PCV5Ij)}HdwHCYo{D)bw`ewdCY-hd8j~nRRg&J-UkL$ft>5CGC&4ggh;lNl7UoJ4&5{(QHVZ%ry2{=i{sT49uR# ze18cSE7yzbo%Shs(`oVqetceBtNy}gim9qy!B$rpXwhVOi20R)bIX+pDiP}QlYC2D zA7w5y8;U(8b<$%u<8z6$`~G`B9D$FZDYZL`S)ppMuhF$YAB;rEbWG>wr3BGB;(z=Y zVT@QDvs+1V=2Kh`$8-+`?1)x*NpWLQqTykyrw&Qae(prGY|KJhoH&((K7U+!=B(#- zC6DXaIWG+PYUpU*r30Q96i>m>Os03;i5oM9ap8ElH|C;M$z~(pmkG%&M`!jl+3AlOmTl_gJS>3!3jlj^$M$a|0Oq>kK(u8=`k4( z<61s=o`2_yW4-}GsEOQG00B@t7N8JOm4Q11Lu+7)W+NxQ?E7$FtZ7-Wne0Gy1uCu!(G~K0Ch#YARNZ=!~3m zXXWDRJscS%#e84WLofD&9RZByN|~W1ag5pV`Ed<-GbssjvyHH%ey_es<>b#Q*Q9DE zK{u~A2_`#B`QY)gri7&*Ran>f{=^1IBox|ytrcMk#_VbB#*Xdo!N6FD@in?ii(RUK zo|z(D9{i&j`FF2om^>0j8^(3C3`j6d@G*Q$t6PDtbOemU; zX=AsU;N)g{IQ9#)ZW*m3>^Ebm6mbQ!48w<&2TebL>VshoO9O}PR?y%*UPwF35Nl`% z|0R>Jk)hCHoxs&8H!EC_=xvBAby^I>k2)^F2a0Ed4oO=2;KOCa(~$bTyHSY;%A>ke z3%Vrx?O)EBG2v#G4Q~I(HdOdvJia5wb9KMyR=wf+ioE+Tdry;U$O#>bL4lP-XRkrP zJ}+o%w-?oC4l4G1S{*Nyw_hWfsP-wve7hNxR>sY)m!m#+*V~O#S-HsjNi|lq-}Q`y`nUUyDHDC zt>jVG=NJ}5F)&zS=a64?CW)Z=B?Iv(Ri>*| zY{bQOtj7?VP{bYXvxv@QjgaLArJKDguqCLy!MIzp(eh4&+RGYOXM3j4Q^Bv5#zPaZ zp!h5UMtc59XTO6&sbcLKA}z#ssZ!2uu#=4d*Vi?mis91x$1Jc$O`gP_Ly{^B@`+SE zk1AFPUg?%s(DErw_uKCQWr|qBN3~?=>DDAm(MHy3)r!F7wh=UA`*JDm9&|$r-aZW} zFnD0@xXyX@VH#v16qHIM*#iEnql=&NxzWp!pD_-M=J8ne-xI2^-U>FUol!4;Y?Ix< z3Sq3RbMH*sqY%hfa`ex?j_xxc%n$|jN%s#mHd`LcC*Nh0Fh}fdiPiVh9%C^-CFmlk zr^~W?^w`fZ{$TGrSzcB7`8nb33(A<{7ZQeLjVZiGp)Vn>;G%-}Ne7Y+YXzXEKYq}7 z&uQ0ZB4)FYbyE*TDd5uBIO!xIp{~VwoO(@x5!08pnLXnF(}=m_LePoRp;H-8#CW&3 zl^oV3`_5=)UsiMM%c&o=u_g=MgYM^JNHF5v76K>p!(L^Gk>*1_ z6iCt@=FrzZ+e++vJJUfqk|FLkI%da+p%V80M5cey(T(h+{L@bI)~9IYord{( zhWc~w7~Zy7RDx8|ZTS;T7MNqp5J1~_0YCg*BN)`Vkfbd;vd}Cc&iW0 z#H|Q=Y3W|HzSrcFH{-&2;2IZ3o#_wb^s-jfZQvQ43m=W?U{^TPo%*oR2sf0u)_V6x z!}HFgTvyMu;HDE1*WJ|V=+Z_PF)rvV6N_3R*3z>PFENUqX8g?M2VgFI;6AGrAFk3K zp->j|LyN7ax5J>U@Mg-SCz9vlMQJ}!34qY_PMf}23AwSOj%pAZ-hW|WG<#ir=D{0b zo!YmfOoH?@-AQ%pATDcE-VSDpLMPR5A@+tB+UMNNI?VS(L7jJNTpSEU!Rv;YMh~ZU z@S&?5ks2j;(?;e5B8j8W)i5!lTAs1o{Ydey_Tp{~yJ8mNPn6dF_= zXH(qFUPFtUumDOh)IE5Bg8zjE7}T(ks0K!=F)^OiSl?@koWv}xplIf!DoTI*0IHU2 zB;U3@ns2CXb(9CkhWGAJRL4LeN0N0LaOigayi3{S`NUw~viLXI=J%E>znsrj>|s7% z@Z1hz?NKQd>)&*=&o+b}B~vKO`zLOjB5GUohc#qWt-I?2KHt>D@fja8pFZv1Bm{ac z>JR>5^ipNgR6oZm83L36DLLcrTH`Fj0b25&P~$CZk(ptej|!OhBCjvF^5|ywi$hB? zu0;CL0xdU<(FSznjl=wCb>(Zv6~GHrqU6_(ay1o}VuHryrO95EWOk5bCp|-uWfKpW zHsDyt&qIpaok=Ehm+tzl)L(9%mGsWv{7n|+EE|hWq{OO_`F7$-KSyIbb2&>*TIDi^GM<)rOLZ^lYF*c z8-_Ccw(^Lu+pu&x_6e$=Q;J2PJv2Re3v6F+5xGDqLz@azDS@dmEDqN>Pta4ZfGuN?it4<7+`7D&=-iWpT!w63V0*zYb<{ToTEN~pra8%Z2TIDFkn!l|8 zIk$eo84vbj*BRaRZspMN+;w~cfBrS5fdqR-YsY7MBxc9r$1hV=MqJjoZ?{nJGJzhd zX>T^_!(F6xm9%i7pjn9*<}j-9Il>#M>H%sN8{#v;zCiKXecS@?FvhOZYm|yckm|=; zeC#4)BEk&ZFotG8Rs`0jb(-uEywS@{odc>Oc>0c&XU8vWt*Ym@Y1f@8sLX`rfApcqJO);WbH zhyz`Po5<{l0Wk)KAb6$$GHQ+D**wxNTe_I(?A*#&brWv5ZVp|+nhE&1qE*Ln?Q@6h zgHT^9XKXLi_x23;XcqH}$WB}g0Pl-ye)YX>)oHE?=7RtyN7N1hs699`077V^ihqi>MNsXkTB zx%lyo1_W(Qg%;hrc92>*AmoFt+!aF}VYlHYJ_C~j-sxzkbywKYlpRNA_NBS|SdDKK zk$mwmEj;up8OKxJi<(PVr8R{0qDz`Fq*79D+JNzze35Qll-2tJ*ubd%(wB$?rvBKP zw0#PJ%lufQbWB0jvne*S2mEZAa`RWzj%!lp7gFT*chCC&+O!I+)CrI82pb36s0Jlx zdh+JG=R{exYB^RE;Ii@4Z zdOVz94|1`tXiGMNsX~av2687U)%o-CBWR}`=BR9o85(7?D=QO&PB12X=RMJ~^sZ)m zh;4Y`Y311WK=_a&vR|3eXP1}W3_arTKFt5i3zoqavA8;e!@!0o%eTGr$g?VQ!=r?| zXNY!nv*tN+n?%&8OQ{-9p7O<@VWKJXjjjWiLur=ly_`oRY(~D_uWajcYoPDwA@5h3 zZZdDWcl{w%qVHJ#_cw@6Q6VPdKMYhJ<%YDX6keKRN`m9}aB`c+ESP-BQx!L*;uG}%oi zW9|x%w1c+}m(si1c6;+WBTv@#^|-fNfBw4vI{SM}b=$Vw2r3lJ-aNxM;=_tz*IetG z%zmf3fj$Y4_9{hDdMG@#3A;;k+Izqb*0z48E45=lbBZ>F;dBzB&_YcGgI&O zxwhVQqIicyxQ}Pzx`TOC&B*<_YrXt?EtZcJe$FNal+&-Pl7bsk%|2I-nA0PL9ia%a z^}cVC2e2opep1A*&Np2YGPFF3Ah?-nZDAq1{3+YQBJI0?<+rplj}+jz zp%iS>F{@yJDG!lbz^&Irw@uj^$^_Cl>z;t74Q}km8P~k>{dsFA`Es_`)=QWZ>#ciPNy$skPso?0_H=r%K8u{aNpjrS)b9#h8_X{ei~a=a zMmY7o47fZ7sn8vv*bb06nWf6s&;3TOZPhNQw^3+WxsN#di*?qWaFieJ*Fqu)Hd#eck7sO%+6q{j*#lbrY1hy6EY*jH-R#HkUSC z^5A4&R{tvenWQ71KGeYu?6r$LcEyxe{^A~*9#ofu8C81+ig~bMvn%jsJw`9TjrK&L zHv)q*DD}YCJk(}XJ!Fara-OpbPNb2DT&0jIK^-dz%kBQ;hk7I8XTq=Vj5%gn8D1lZ zZrSyL{CdLK?e|FM-4g{kG4pX0WzVeL3c->aANCeXmT4%Wn5H}X!qwZhb*|%%EFMQ` zfzTO^f;a5OA~Qb^1h)0A5Zri%@vE^3oz|m!LD0fs-% zNU^o*t0OZeCRH}Hvt@#U35{@h%f8)e)nQiss<4rJR3e#bu!nA4osKb}*Pza7j=2!I ztAUJRHpj4Vl-7MOdB+u%C)Z>JOg!h05ljGreLQ~yA`|Y{vG+qai(yFgSxo$l;>6-OPArt0(Bj%4gS1_Z8O-!g3qs8&&^PDhw;jc#xU`^L zWAsqTazOv{v(QoS`&gyRzz0}wzZ`oO$;T_X)Y2*$-TYJ0RB<%Lcg0G4tLzg>y_|mL zD{}=tIpM8OBw9XS^XJb<5>}J{dbj9(!G^-dcWt&wc|~xA)|LT1Xg-e-QroCWAy8KQ zj8Z=vZbg6514R-84njVTA+Iz}h%m$t6qQhAS{VyRck5cubGu@t9n9Y%$ZUA`WPLn8 z%jtQ>DECElyV~UISazWgU|kbJ0~lB+@QgK8rY4Jf)0#$(b=Zq_8SVToyAwk5g$~H> zoIc}VJ?Xsx28dLj`ucJnTsu7CiGmmj^U#Y+>kNj9aM>Px|HJQ(S==R2q6_qW&(sY@ zhsvCKQUc7!&^k7GPyTNfN6nW`TQPH*%V0A4@bU& zZEA-UPv=FwfJZH`Xr=7&<=v;JGP)1^!9ab&U@J+XL(80ntt`ZC zChzSImE6Y{A@IfH#@@bkgInfI%)lLHJt?}E2+u1KnoP*3=7{dM*w;=54C_%=9wuWB zUzHY`YVPJ?-;@u?z21-f1+k6;E?jKbsa8bL%vZ--T5$f0?YDqe_tS!CoR~u6;gq`> z`}lQ}CuM6K+1CAcnaVKT`G%*VOyA`<^6@b%G&wR6hl~w-YsA%r3sp&1 zKgDXg zXHrmyTm3uL{F5>K-0ye_Mx*s@1wPg*2U|E&7E9c?ZM|U^nr+m!Yl_Vm^%ud&+i>C?X`waOxSCa=t`Vd~oktyDiS1_@Tx1tJWF0l)d=veT%&y0n-Nc76x+>pQA3D zVD3MpHHiRWqL5Nm$m26H=v#LR6~r=OkN=ewiX+LN`>Q4K17lX?5D>$GgL<|&4oK_A zQ&zU-G_NY6!`seZFn>#UvY9J_b;I|v>o~X>^wQg;Xx4>Yg|^gpYkf~lox#oNb4$#q zeUMFzGzEoG(mGC5QVGQlbvEjnOH5}0T2`I#B#!{X9^R>-WM8Wv?Y3L96+&vq9&@_U z<^BBo(@O!5>75@Bc)1?m&DY8{dz>+Cg4tOSHVBNk2DwaE`Atl8Qf|Pp)d|LIhRJ^+ z>P;B;ovXch&4%xnr6c^&h*)l2zc`m!rG9Rf6%W5e*Izm3LuJMW3(FK;AHd`~May zH7l7J`mc2Ne!3E)+!0b`pPQfvf7bIMx&HPwiV0IpxF5XPN!s#b5Tyc~1nqy1aD>Nb zL2dMCH#*5>k;?M*jk~s(eA-fll*JTLyN=EFOu}yYO#BXm4Sw<%<#{2$v2!*fjdty^ z!=os5t};}&r{8U|oyTvJ^QqTWc)xEH?7qntCFGsU)2#;Q6A50U+@~GsMxU?FN|;1% z_VQNZ@66Im){y`uTiSWsGhe=6{3&Fs0_b2}lQG&O(brZQuIIqC z4S%9ZRNVYSn`~#DAKG=(M#zYSP0OHA5v1%pjVBrd<}OCJ;n9If5VE^GOvBha3UD_q z=;RzQ!(@aUlN3ZPDB-sqS7HX1=+ZO~5O2hMwT19YSe&X6xjzbn+E+}Sm&)CT8`QQ# zKHV?9M}4pT32I6`bMIx8v*?5Vu!31zjhy~sOEa`;PJekfE+o$#2h!|om=aGgkEkbn zIx9lfLCV`tswFm}jlAHXFCe?5INnkh=rRqAA#jjne0*nwfsSI2yg|a?<3>#gBP+PhT3%Y{r1}o^^{Fb6vEYn@v(v`K(T!q)<=K};G0e8)u`QX3p%;04-);I| zkqSu&eUQnhHCrwQGqa)H;m#V&zL52e5j#Kh3hQV0vk-U&aZ&xE4dP?p@yLex?$uYN zHoUDPuOq;Hc*=6|_c#UP8?{zM^x9xWg(RJWQN&r=NYi7Lg3=k`u!Q>ZkIlWeDaDQq zx;4G;v1b)H9jyqq`P10gkXZWW2{uYbM#D7X9E>4ky9e%tv+sul0|(YZG*XhYMa_39 zE8Hf0a(W|t3N^O_0@Cvry5 zcJEX6c7FFgy1CnghJTmD7?RQBh0pZC%x!(dAie0IEMNDXN z*w?45(dnw>wcvrSjpv4Q;&X{Je0yMyZC z3^UCgKRS+b{r8{@(u&a=10rNhGrL^k?|ZJ0gfX%}J0G2i;kvHFr)US#3mcAR{*K&C zgf)TCM~N2ibGAZ}Gd{Zr<%T^(3C&(t!0YA%t0+UgvX=vI>a!%o#)4qf!Z+z|ke#*M z_H8)A$C{2SyZrn{hh1-ZgazfJ=z~vJQ6Hztzzs8G@`%+?*W%Aoqe-8eX&$nBU5&fG zs?cO3mS?fZf#o=QUaKGlRJz>Z#e#+%8W10En z!qQlxak7MIGA8)8rxnEZu%MaO*kNjemt>$jN^+!4Xh$NmeMkGi=uU|Hy~>{lG#l>O?OW`>z)i_4p30J*uSB30?7m;q;$8*b5=b2nZ}gk0jYnJ~=h zPox>~shWVW$niSV^Cz!A^XPb~@#vxVkHkCT&g`~(LKTbeQz!1TsAu0idW>6m2G5PY zR_4Yqe;b)l=kq3{V0y_=g_mO(j2t<>OW#h5^@ z)Qf)sK7+@{i2LrRM)>nO5v^uToySG=rjgQ#zPryE3-*9z^io#uWc!42y(2{R8&cZ$ zkIzXT@LebW+6RMD2f5B9nhi}i|{X=hBDZJQLjq+~uiru+^S3ElP&kHn4G&rSnv$bL9re^5^(O(uS zUr5-dS=mSj2C4Cwpu0p-wEwP^5dPoR!P?dLV_Aj)f+FWOdhU_2OytvykCyWwyT-^0-Z^V&$+e0}uQmBr;;>bL}HeDr{|5B+72mxu(~2THGh(@diFM4^v1@h67eW+p>f zWpUVungs+I>B(<~va}W8d^eMMVwk`ZzQ^Z)|GoG}u4QG;9qb;_^VLrx9`jQvYX zinGAQOT#yV%=rh>4|2k8^COVR<%tT!C$AWA`G!cu{CfqNL|??l&d$08`P5*uJCoOnAVTjz*nj3)a7wo(*bfDz9n(|5iWjjtmXr0f6 z3UbErPmN0xsMI_o&sR+rH6qIGO*-`+M_hf-;{pQ70xX15)b4ZfL|nJeQ#fc0A7MO007B|nqSJ&- z(4dZNzCUL0f0N)k&`H3T`FQBD?xl8e{wZc*Qr6(Ykm$p9ZDK3}ciUjWL!6*mt z@!7HQs1?A0Rt4r$AEyQyv3T|-%h|tNXu+|zh9T#CB#`v2cklDr&&P1ZZ7)-vdPETr zW;%Du+@PYR=K72>aPq90_dUf74L7I0^16_iI4b= zTNRLs++x0sr`KWzMD?Or{yrjM3oy%$7jKgMD^azt!JCBL=zB&0!{wVUmJ9$T*&$N< z`>mHRC2{h0PEqg_wk5!JE!tvbXCDC+uYcOL)lT#^(u}V^#rz_sK`$k3-DY#i_A+vE za<%&BEabY@wWEf_gn#irbFKuWh>H~YvV0xjD{Aa7$?7{%TdzV!PAe5&KnG<)ck;Sj zribP~=AgcwczF>2FJ@=(s_p`;v)S)v(*cPikaE_S16rjX=KIIt{%>=zB!4DG+1Q~= zfeWDEz5Kn~o*QexLizL*aZX!BK(aqnj}63;`c{Vniz$vjm{ZdHU!VEF1)%Y(rsHh_ ztX$3xVykRLxn|qnB{(IL|M^ow9zdhl{a6i`G6Sx|vfm&U1vAQ+xqGIe;A4uv~p=BMV3%sX84`1kBtm2IEu8{a-)# zanNW}@(hOpaWV3)RZDb}37?&TKy0sP191g#yL@ zJcKhF=>U+e-sLZ80Q>3u6fIqIj140`;uOoCDSVjeyoSvZ_u%mTzqEVVgVRCSTZ{t{Knj zv%?U%DfFoGe_#E94RGEtj*jaTA=7Z!XI-pY0**-8+T9?Uk_jsRhF0g2$e5qzr}SeV?m*EW>?jSfXm!T4{WsHl93LK`^e@c)URfu!1l7h0d!cznwC zyucZ{#k<+GDFD_9Jb|=NW|-`l$&o96F?)XvAaAkc%=!2G67|5q$&+R0^WwsF1QE)& z6=iLh ztGjLU*lqQ}xClxtu0vSqvaYnp^NXBLLY*jaRA~IHyY@GpD4nHlk(C-PK~W zMw6cWrkXg5T8WNVzII8(sx5=*X|#2u03fX3hP8))UZren5`MrbN#tBT^^Ee3XM&Zl z?WgNlY&jUO#=XD4BjfeaDCD%gmY@feAji%Jt#Yo#C-8bEaYuXSpv1dp6f z#Q-sNa5DQi@ePI#d|bI_Rlyl#tj}}EPJMh2Cy&j68mCBaA`YHDARm4$2o%jrvNKjI z0Fj_}=yv@tqrTDO6rU(mnc`ELM**s3@|>9vg*dg4F>1- z{%y(UpAym*a`5K=!qyXqEzI8njmskzb$n`=6~qSQB)6~#Sx#p*5*Bui z#jrLutcZ8|NBU*_zxbayy9dmQYowM%GA?4zpGODJ-p7j2F-V=#mPPLBdfp* zkW0S&wMbU30PyCn4 zd{5dUUAC8;5h=_t15is#b=SJ?@Z)H6@g~->Q%T7G(5bFbmJX4y_v4ML{BApku|G%i zv4={(F#1kizDDqyPe<>5QD+CkT?Gzb8*-%`inE2SJ@?kpb8rW*M<)>^l;2(Ia>0?CO{fz}dH z-49F;(G}}#|4k;7Ah7@;omS@Auu5=4o3+F|+?b{$AM=|0GYe1+YP;rMSz~_DQBgOEXM>4P zx?-2NELtiv3FbtPMly$TW%P%o-;Zv_I`dh4eE8>@?e%fAMH4rjr-M5j9 zYvE?&$mP`%+3)i6DO|J4d8@mI`#rUr!+wkX$vkq@UQ6&dKN5v(rpA#^(PtUv`#PK` zBa*H=P$Du3#Z8zr+69!xVl+Ocrt$?S3;NUf;frvsjyc`hsd)4)qohZ_CpOx@NmzE7 zvQ%(%Jm7VZ#jh=5i}t*yrt4bgj7P0 zymzRq3Vw!=ID(21VTi3iOH7$rT&Kl>r}&fb{IDXDP;1%mJy)b1tlb+4#V2W%1vb9< z@QRMzw=fZ09?VfKvU&>fU;FA@S88=0`)r*Vt^?ZlJz9-igknQqp^<4!$WUTy*#@O@ zf|u~7N%=@l4q`Kuh3I9*uBY)g9$>~*ZD!!GUT^n|{1;%-F0S>c;Ypg}$FNTab^@1t z9#BoT9*wdhdRa2>TK89e+;wno{5}8qxkx+7Pn{|JeRo`Uhkc6u`JIoCmi5tFyu5ap zi45b$YMtUp)stXMFpN|X=Jbz;^|EF1%F49JAmYh6zBe9g)A_5$yHSrE4TT)XnKE?B zBOh_KkL30C33gF#F(IVtxZX_&q+iq;BhEK2S$uITgX2#ap#}U%W%9kvMqHRt*!@t4 zj0yU>Y?;*(FK0lHHayvrVAWVur$1%SOtnWQkQ&zuf%;Z;{miKKzU?vK=PuQ>N&O;E z_knG2|Df)x0;p$ve>7-d@>`g=H{!~ll#cnNyWdWt@xXNE8-6P5O>Z}e^r1-?cu;A{4pfvHfM{vW!K+N=qjU1~0 ztMoMs(VAd+6Lq{1&-eSBJo_c8&Ai^GAO0O>O6Ltb>4PDA96ZPP8v9Qk99n-xv&C{T zl=hL+I0q{f&T)Wg6b;DIa@q9g-UmH_uS*n@^~;7xo^%hY22XShCXVK^H@3A1VO{ON zIrgktO!|nqjd|94dU`gL2|7AAwX$jJ-pIt!&@)25MS#9K$|Z@O8xj33EN>+jaL$W^ zyk2X(ZO1;C9q&&;s>KZ0d(!iasn$5@)zVwM(uQ)4;1|sfeChq(kAZe4G^wV#lIL3YqR*eMSR$-oii{^K9^+?{6p-ub?06~ zmV1yGW}`du&|#!P*-re$%#By0o2*D}8L&qPjh1hEE9N|XqZyGGe`#$Pk`+cJ-iMSX zkA*tva9+5&05iDPk?9=jcuek_MAZ)F%fgwI(#;>RbzP597X8NdLMn-tbimDt0c-`_ z=3slVy>wr<_LqZKfpg~v0eLbdett^CvNt2`=JjKLS-EKq#yqE;CSkYdp2*OPD=NGS$u%pS=u$m0aK7>PhAjtO3Ut%n zxNIh=VVjj;3Mf|%a+RFDORu$JKH|ya`=hvHeH~27P27hUQ zv3+5z$|adE$-z5apcbE|R%T(q`%Cn9J4;c6fq2=+H~zToKUthNt|r86>0WbB zLCo4~IAsfO(tU%OODXX?y4HGJYbe)Rxa|pi=D5i~`{CCc(+6$LkL3CCgT}F(4G0LF zr$DQTR~a+k^x$4jaz3U#rWn_0Jw`(Q89i%rf3#i{L1wFzkFV9;$UBKH&^(ysamLs~ z#8%E~Z!x)Z5I-d+m}62%=%2wS|IBBHVKE%h-?I}x?&Xf4VuDUus~#nIJ<%KDb7M-^ zBi-DoBhKVe0M0mA?!%i)2Kogqg9=TSPD(Kith0`{*|q}?cE$}kpv8tnBLZ&I`9TAf zxe?c=JeB)anhs_k9_zYx_aebWGE%4anytG?HDsQ_UGJBxf1VY860d`TPSc7{MK2X$ z$mAZ+)-Sg{GZtsR%{LDy)3y&X+J=h9UExRS;n#VA!t8hGr;hI9?K)k6zF2kk@hMI6 z)rxc~>@1Ai94R=L?byV%bWjUMxp>N)a?5motyre!rnukSNlW!&*W=bL&?{<->*FXY=hNNz^;3@ANM8^PmAw?^uB2-- zj-EY|`32eeJuY%2E-r3W;kJ$~I$0zy72%2$JA7&CoDr!1dcWH8)7REV4#g3BW0*L# zOb=oR&0OcT_(0_`fnGH1e6xH2z2Jd(Xbqo9Iz{9HQ^b|hVhD=dxO zHpjfG%{E4voG`(_KNO;stCu5Bo(=q2A9wg*QX@ zz(tdCRqaJ1tbaQXr?yRypx>aI}#>dz2?jl6t{!7(7KZv z#+rKdcWC>P3WTL=ALJCcKrFxYvEceR2xPgR*Sp5H977>-kF{r4(3OZ`<*u)b2Qq!8 zz|Cg^hkWP|NVv@6pvU*~q-Zx$jj)e5QXDcvvHAVIZRYGb=zU?kq<$#mP=T|{9qzDc zl>C7A+0!1uMdfH)O;+h5Ac`WerI&>b`vmrP#?K#< zdzCA|RoXN!Xz)fQ3+6LR%2u0^-SHqgHRYAL3(VX*{LfeL52 z86)h4$uiKZDnc%)_3V}Sqxm5|g>(5Wwp{Ynxt0sgD40XE=mB}4P+`g z268@WbAS688we*cK~7_}C1KQG1kF6PR>_RQshpc!@#Z<0_ZBETv;HSzc#bnvF;&Yw zeSkyLMd?g+pbkQhXY=*3L=xA)bsyE;jRReeqgt7VRW^5(#dn^Kz{T_qf;9^EYn%Hj z&)3U!)`a_spP(TpF`-vymjpXTsCBL`Du+-w?H64b%Jb5Oq_kM>7e(Yg^z!@e;#K4y zpK-%MtGC>|bN;YVwK_ObYKm>|W`O1`dw)c3n+(YLFflghI=ve^;u@MsRQ^o@F` zM6)e#^Pf*VVJkU5eEB`kh7);u;B}I;E8{Tx+37jRy z^QY+)PA@KDu7rSqEAGSp6cUwi>GmrgzZU_3L1Y>V&IF0RETk_tsu{RlwtIl#ya6%# zcg8(k&z8&W^V3qt;%v{z8dbZmwODx;9~lBbc|SCc^4R?pDzzUAtjEV8#)cXKMMf46 zrnuemRrvK4`}E{@FY#gZ_=*>KpK@{Nw2*1750il154KA)O~oU5{*mDNdX8RH*(Azt zFe?TZw9-f}sRc{>+KNuzeRk5(Oz1>7g=8K^xz?v!?4W!XFTdej+&wtvaPjD`^bJ-> zyN`s)ST5XjfHe0CWE!W^r`21ohm+T$(?&NnpfS1qaSIOK`>tVpp?ZBhMY;`72Zu7t zt!Pp-8MZQD$wyO_`gTb9iz|>q2I`mrdq}|g$7M1n5gcu^qLmx3x!EEA^fjt@czCkh zzN_(6^go|bgWaVOgowZG=Q*BBD7`Ba!JFxFIqfK+H~JHB*w-Gt`cq=~Mg+V9=gkm4 zbx=a&nkTBom4f!{WG{@}{I%6(tk!6e{oyEO7rn2+*+`pE8Z&Aq7S&K-*@sEl{&B;r zVxvu!_>v({6_`u3L=9To2PY)SE=7L?K@6+8^e!=fj1-ma#qT5JrP$qqDESC(6fqUdCl@>3-eGYy+xlSZS;CvLErRZA>Y~&;3asr&^F% zjZbI(3Dx>DWOj2HrT7dIm5UaOZ}j54@{mz;r@cFEV2A$0Zh7N+zus}yNKx~^80Y4R z$O|xyF8tH$WI$iAiA}2X-ks`qgN)dPzB|nP1yFDDmX#N*3%H4FOJIJXJ4OEEFl*uE z5qLgiLTNMy3|3)&ydgiH9$lL)6@XJZpY*(l-8WrSu2@?q^hJJ2x@fb2^+@(D-ww^a z*s!hYtPW&--`&3X#hjXMrJuvdNH-}51HYX1`2C|Smk@q<@v&I>L!%|+iXpNn4qUeV z#FEGHlba1v$S!|x&~ke2tgX9flAo(+XXZJFKfe3Eef|Gi4zvRc+bJ{_pn>@l3Ff>`+ z0sLXbQ3K5@mqMNIT8Ow+--$!4HXOe(p!4_m`PULD5hmj-i!}kP9MYrN9*eOv-m^_o zsy47LGGFStEY?Q9^{z(5^cj1@YH*w|bXD>jE|HScq>0&q+V3)t{+@C=Sp*YJ%nZ_& z9K^mR=*44l+bC9Dn17~5p25_K^EZWCg>#l3W>qq=Jbg}e`B{f(2(#r*GQlIJ56PCW zl{7DxnQGkwg30VKLnbKs(T`#Tr{BkUQ!?layWA!tyxuTeP&B#;B5;o4$-Ujz{Jg~V zQd344Osw|NqR4Plc-{$-P*F*z!gYfIt9xHA{;3uTF3?pR>E-=D z?7e4LlUuYds#p*aQ4x`@^d9LQRHR8$P!JF!y@S*U2_P0&NbjA9Qlv}oK}AaFy+c$& zFOg7UsCQ;9UCX`CKIbm?k9+Pt&;94)Ly|e?H|8ksc;E4P++&7Z34hkWO5&0n>x>d^ z)01~M*lB4>qBx(YQi`nofaTY%O95C4#vn>W#Xz7d$@Y9smoP*t76>=C74=1n%vNgS zMlVKpFVC-e%5Gl2I(=c%qIp?B&yXI9aooE_3hE8td&s*~seNhOX2-L-8=?Ryd86f` z{A;9`{rwg~>EIgK-R;?NT;_iNit;Aw%B>y$Fh=pKLi~4n&PJ|0o_Ex5cuZNQhg7Q` z0nks4Z4uq!(jQgXPs%rI;h2)aWc#tg3un}P|DqhOo;t_=NIBr~-I5v>(LFc$=Ku9sCF#k zhoCFnFBq?Ar?Si?j#JT(Xo0|u-5FhcoS6DH^QEji^QAu-v%7bm2{9)Hjm?P-?cd9K z+GJzVqU2n<-IUy5vVEhy@wCF)kQTiDL;~|)_zUy?v`+QzqU#3@3W6PEQnvEMxGJIO zO-n{swBysQQ+IR4-a?JJLYPw`uIdk)b5Rspi^kae?7;ocvcLYYtMd*rv7hVj^0nE! zH3xQvB}%M|V>FXH*xj?B8DH5FRrFi*_E_b;HSLCNFFQ9>%J49Y>&*hS>&-AGX>48r zrY&!s?wF4+nv-ZTQzHF(>r=`4qi1M?Z!bmmeds&8u$1Bs1`ULyw=)kqnEawY8tERtC6{-z4vQ}BYC9V@w_!+`aeKXRACtHD#z|0*&q^NL=7Id` zQ9ae2u40+Am&E0FbeY{xBKK1UTvJF4DcIdQd7kdcS_09EQwTsI2ZW{~A--e<-}VNF z*s+$S&mWY8m-vmdVz8(y1|6Qc{{37?@N%Qc7Dn?~&o3{kca4rGAz%i4+|Gh24C}je z++7i#soTVH#|I6Xg}lC&9qV@h=S(j_ZWK)C02mvOvyiXsThw0yiUKNoeayARTK07* z-}&)BG&(ISfVbAK&O~?|NKZewcqx=oVU=Xod|4vjr=aCs7^KlTXZ-hc*QS~HV}lte z^MS^#_wv`r%WN(+25xMW>uDStmab{K{{N*e}{BJ<}0K{FM#! zA4vZYDqaxR*u1jPv0mDd#-*cp*&K<($+j#$R%yRQQegp%ouZLRC*`}UQ+;U`DKH(( z6?OWm=VG9vgmr+HL;)c8tlqIVoX6Nxv1RCN&5lKCdFIjpMvVvjO9l-K_Atksw*5*% ztaSR8K#0dFKhxJ6^YR0j<(+~&Er{PsG+l6^q~Zb4S^i0b+>h?Oc5DkbdAr8Lj-_}W z2y>6ElzV-jgRuKrTOPmd6VtF{>erE|_aI`7dJ;!^4nMfUvj9o5`8JpZn*mxQ@qBf9 zghA}viyNbb&O}iE zlo)*|I7Cn@-xZ4Ock}Fv^(wE4cr@?6WxLaWbMi91$6CFcgx~jMPg~+FNLGyPAw-*O z-r#?_WQbV`>G)CEJRtSQJYM8<^MU)JAE`Ml1&DS6zDaSu*rGSOgq#Y~_D>{GI*oNq z)*-0tswOUfS8X+KE#}KJyqc7s?6=zUJg%h&)7mRZt)KTz5Q@gkxW;ga)=7fG7hIll zzc(sSulKa&S6)%bI-m<*_9AA^Ar!NKc` zRMvI5K>(nh=h6`$TiXLK+HKY|Z8t=Tm?jMP)L9J}!3nIFl>!>D&gRXxPEW$bdJ-yS zrasR#-vmJI_jS0(_Pw0D?Djdg-h6#K1*`2>p{S$hm@&C*f94CwEPAKI}GFj>zwamlHbgL09j7+boP6XK5oPAzjWkBQ+^%{=OQW)wT+?i;jg z&9vU_fIb~l)n4Jw4s#bLUMF-&Z|z8uI#O?C$h&Ucq3TSDrau!6S=AUF)4A1O6Ra~e{%>hPV!42NS1 zV-ZCBjX?o->E1LGV|8TON|_ijsuyxLy4%jDOT#0Lr zErcMwX=uT>SF7lKIoBKDO?nWo^#@q!K5U^RR(HKOA6_5ZKBrba?xa1xgU<0LBc~lM z)sMgUll{_1Vvrf3wTjhAybLmy>@+|PYEe_8BLf{pbo<3-~uTELiIqrM?u<=L%sKJ%&hzCi=#e{&D=+O#|o<7+-qFV|T4i~@y@SS)|H-G^HH`nk&2+C7dI z`01i~LE;uqHt)Vb(kmCnXcc7ux@q4thvm!ffKJkfx=B>);$VCAT#BH^R03ON951aw zwq={*Jd)xqOBW94RThcq)Wb@I&=Qdc^ZRZ<-5A%klC0ACryRLds8vJ=S9nKz%1BCF zh-*#Ea||2IUD;8Q&LkQ?`|`X!ZVH+CZLo4jQNM#0UP{h|G}K*n%8q+hyPl@{?By-^ zHK7zqbmc+H=rlnokkdK6Z+~_(?JZ|?i}eb*?RoB==cgz9bj!Ge^IYy zlA;P2eq?qayRz!?Qt~BkN{$|^JNIRLnb!Tf{q?Me?g~WEXh?Bg=27{x8=uNSHZ1+< z-(Zg){tu*INi#Bo1R$u6tQ z+CKhhdAZ2c>f{aPZEXV=@qUa%f+(J2?gcq@bk$SrdMy{qvixFU|B zj=C=p%@~&YQq2pcqCBFqphx8$vp}aKKO*Se$$U-2J$GrnTpGL?feC0B_jadDyzH6_ zWL~cvt_A^4cfT+{(U*B#BF>uJoocbK?eY2rfyf%FG2%11ftz4G8$s_+>p8GGp!{Sj3u#LdVn z?m5+>Ov$AIN|1AA=Cd4<35Z;9!bG;t*&Topxx$-_eAiV3VKqMOeA0Ux^YHJ-N5Bv-VF9gxEeuB zzcTnPCGsa*x_&uN61w7W%vx@-(Ixv&KVUIXL)Ou zWRSyHM6-ES;oIX}7u7_0g4RJ%OmC&d2mXCdf;LFFWI)M8!`;1AdApPx=3wGjB^evL z7FX~X3L>A&EtHRUS?q<*MRNK`lDv>X9LG=wos@^g(g8K?Ucs`=gc;s(vcklPM^ErX z<49q1s=-e#z}$|;o8#d`;#yN!(=!=clivy&CTshf~H}6OfXwO>9h zDzYdPn|ZCdK@t(3Kahs&)~E9<+qfIV2K=bA}%)WDV`NMzMz3R9s{s!{Mwx@$8UMPhxmOoasqiOLm zKt4{3TojeP3wJSbX|lwFPA+*Oe9>()U^^aJPcuCAFzAU#FaR>)S(ry&PM^NN5~1a` zJny_?73grV8u+Sv07nOTtXyKP$Sx$7fN=TZYBJq3^OT17b7(cq49YI{sW#Q5tjM~O zICqNX%o=)0AiYd3-=o%uI|24QjtvnKr4xd$S|F~RN02z-g?p5s@weQqphJDN7oHMCA<%& zi-`m?ZAdhq*QfFTeTP}pA|sRKh)$VEn)j1I>-Xy-b`$)`-wQ2@fhH zsyKm$Ra73kYJ|NH__Lww5$f#_A6BO}dcdG5;zEC!9{~^M@qR^qRQDNNhc&SVm zY2%YTL*u;vz!pav-MzZzxcL~wgUa!Scw_#75aMJ`bH2#;*)FE|Z}_&z=K9#pdp`2U zj#G)DbFsJ+i$BSOpU(q3Qz1Lr0Vq?VEkXLX0q7+xSEu=E*Mz!#TStQXuGw%{r^QvI zc7X7{jJKwY2jjfxxg+uJZ4UWX`KMNgo65YbKpQi~qE>s4P2?8!{?3LSw_wshxIJ~X z#S?|KJB(f{o0W*mBrIJ4^G+*Npw0X}4l14$n{iR0|4o6I z&vwvrR#2ITkJi}6%lYpq9`L;=tP@|j`eXW0plJ$_iE#*y%d_JOcU!-$TZJy&4DS-; zfTHBG@b6@%>W*n-*cV+kE!`4{t}d}TOTTs>VoV^T5|sPq@0NUl%Hn%OT7W}JPj?K@ z;ZTtJzx69ceC?d%U#U04OaEwS@9g!MdV%pIUZ(kLl(!dIGNDCVx_kR=6@unYmhzE> zMK=oPdiGM;!ITmY*&>AMt-lHVB)N_7xnDHNhoZy39QMajysi8KTOAX$QyKcAxF5sz zf)Y)ud{P}?+B7GfzyTh#hv8tvxcEC(7QOr@clO=Pnl-N<5|dXK^nYvRtM**HH(qvvL~!A?p08CwAAn8-S=3gN`dgttD{)eQdd41zQ z5W1Duy~!n zVqZ8Yqql9fn}WbvH9{DzkAZU9TAit(9Mf1KI{g(qZXaPCza?H5k|S%TE8(Kcz?qZ~Jv)P9J>2>8|#hI^aakJ-hRt{_K~7 zs7e7#Z9m50(*l+%9WYkQ44lY+)X@DWC-Rv@m(LFBM;en{!(Hi6d^5IR?Ue2>mi3=M z;R;qGL>IBaOp>2pw8MDvQ>K-{7CxB)WT&)LwNg_=It6KaaDfLg=SU zw7-0!;2)3B@v2Bh2FD@0aBf@!EV(Yj>ZKh}N}hdO^NaH35%Zr)N%C=h z^?DA(u+J_ep$F2 zV6k_X_UT^y#dEy?e=YD_okxfC<7IVS=fRJkviAPv$v*no^8V#?6)#CiYKRAUlN zhJ!vDhyOZb**&$VKJx*+eeT5Wcr!-O$K$4$=!RBIifoq>6gD4Y6E~mCBTY!HG+zqT z{T8*msxk3~9tgtk+-oZ#+4789Kjad){WgV~3Y)BdXC`3C0ph5)yRBsq8oVUL($eRV?;xN;r+)chR`<7M*`p>x7_MK`|~p_Buf^)#`|~V?Ecfg>S`*~ zZ(IV1G|(;Nh1!b%N@nOCb#4Y5{GiQwQZi<{NTQD43koKgG&TH`l(R#_ay}l>bf<{Q5otbAU)qFB0uY zT|VZMnz4d_&M(Qial>ywxou@J!_lIj}LN%MUH^9%=n|hj1fjiv7yzjDJj!G<6(ELnO9)n2|}d zrN2r2c4qYUL>Mdp6~+!LQ`+!2UpWodGu_I(awfX__JoT^0Gf_Yh#^}Tgb%R`nA|>L zVAMH&Oh92F&ZcxC;|TbF_gje!mp@aHsc>O_>cS`m{_=#((cO%V@7-zTZb*c-#ZvVS z$Ce~&Gx%t|akT_Lmg$fc-Ha6tZCeWfZjn7(DV`*m%6TM{2_#d|3|q5L(bf>?Yf#|N z0-u&ReFOOBOPfc*R5pteGcKse#)e{iVC%MhjAp=m@+tYfZe{46_)RXQ4XuE*{W&1Z z$pfwfH5QTh09=QSzQ)*5;8}Dlo<}ufHBK;c1I5k-lEwdZok&vO(SV_7f3H85)eZNQ z(;?g5$xkZtrnfgMP#_8~g0iwm!Xk@Q(bjJ~vXme}ih}6?QatyuSsi$~Ceocdep3oa zz$Ad1tVrLqc*w#A*VWflaBhEznos6BT4%xq7PL)T8CBu}kWA@7v+j)$aWURVdwZDurJaEL7MR+w=l2* z0{EMw=Wa4YLoPd{90is|67HM5dJb;C1Gq5Oj7B=go3ZN|`3YC2;%I$(CACgGuGIuW z+f5*p{vSlDaku&s5EQ1<9N=hvke!*pHW|S-$9ud*u8>HxO-Jz+`SZaj+w1SgElCFi z`JG31F%hAv!xTNu9j3e@s=CD#B?|9)ClOBvfq>C|>wCUqJJZ;&h-%07)c0NBk-Mc; zN6Wu1iY`mVQTn7>uUdr9FL5}|?SL^OY1O0Pnsr3IHmL!Bs6jaU@Y)`rXJ2(!KcT4* ze?mFw%r_~p?r=&?uQT7y9A=Mp-l~ztf2g>N;uGCH;C?m#cSLv|34CwQ=PNuf0$b;o z9Lc7DYL3pQUhDn%py_{Oe=Am=5uvh@$}6Qmi2dO3VtPfdaQUaz+>52%Hd5B@mYUDY z7SgwTfzpp05Is#&D!tABuawE0F<`F$^${r`Q2R-g1?k%eNq#6%mUgN`$wH@X5pvR} zcX%AwEc0+t<)63u>ZbrcmkgbjyBRk)$&r)75M3qw9l0H+Qx63Z}jf*d0>$`bYy) z43e++Ub__{w4OyVeHIG931u{R@0Yw6I-tFHsH5x z&@fvT=C*eNe<}hft^#Iu%*j+AB(S+HrI2NHlt>7I-RfecgR{~R^huW)f;%S;-uz>%i3G%~;bIT|b5UT%Ds$&I%~v!nI%=VeOXS5~GpW5%MGvclch=gvGyK|3_bVhP zU7l?qdpbo5YmVZhP`wFVOrlV$hN?^X6aXhwCpRH>qIUW z$AMhpcKjZ z@j+y1cjv+}L;0Md$SbPhOwm@8vWq(e7hU;3A`YWq1tCQtA zTdUiKZFdA{?_%P_Yrc10I_9!q;KngsHUjtKJT~zq8r&lu)EvBy16NnvMzG&xYktnj zj%a^p)zxFcg={<`gkF^>+R^nEW10^aaj;UXuGqM%uHu1PAZ;q}cZ?8JFTFG^*zZB+ zw|6=s^NM4}Q0M#T3U)m{xnc|xg zh*s&Rc&RtLgZ!``q732RcuQg5^L=@{+uM%TIgu%w(bjx=Lsx#c9Bzo|nSs@Y*|5{D z)@zZ)n>Jxf@#7!&?>TG2HMpn}S8?;@I$3He0j-5N&-S#`9c(z}AV%Zm`3N=x+Nfas zgNy5&R6C*-mE@WVa3U8CcqJFTcS^E0$nX2q9h2?A*HPpLm;$)y{EL`N5u`ymeYf0i z20YQA09i6=vI})ZY7*6#*)c6_7357D$2Il9fYmqyPOGEYhJzGyql$II+tJvxn*i;Odgt7PUsv7o_l-0^eJxpZq}>e(_jO%zqJ8P)Tdqm zDh8rq*zksoei|Qa3v=z>vrLQ8CG03~{W(^-q_hQP8lLl;$^G%@=hoTueBa#XYIN7p zaG&-yZ;%n7(!B$caG>|igl-An6D+?ePcKX~8-`w1^l6Z^IqqaN4QI)nPad2rKLSF& z^6zAo0DN*O16ZXbaX_R%9?2qLEa^MV%3~MM`n{KhUTotbCH@Cl|oO9T`;ewVbfm zfa1Zt5OJK5LbslnymK#b@m%|1kX|`yr+_ITM=iI1VD1TD8U7s5^=#4{FMT4p5X?yY z16=QmOrX9e0o0s@pz8qBLhaT1MOrf`GFGt0&{+o_eQ@}Jh=0d}(Y8zXBo5^1#|bm9 z^u|uxu-#=Tw2%nlAM;Yj#0Pf83}*|hxv`4-y}R!LdzMhvdY(mvZJo^3}4r^gJqr= z4I0BZ3xW3<7}?{>hwhtqNjE2SZ|`+E;)JKx!sZkWP|gaW(tZa8SAFj{+-3>_?~GmR zoAk=Zhgw$z!4cL|0=4_}FR=XtBNhBYGF2*oOwic|EGnMQu%aRoI$G>RIa70hn7m3j z6s044s|^&MsIynWdkllIrAx;byZFD1LmHxYoN?TW&J#>9ml2`wDV<{+{vvx;4g0&} zBFp>2t7S*{mLWRbUUfH$BNiA;!z(#4I#WB>Ysw$DHjVDC+Gn7d4LF*Dv6MTxlumou z$8{p-(h_EcY{rqJYN*eJnVJUI@s>A3)^2k?!3NvmPZC10dMEVOb=4F)kGz&YapU=+ zCtv*Rp_MZ)`s`j2RRHt>e0@* zT2(3}tF@(QqbOL4?~4&6dXLtM&iO&QV-K6CRP{U8S61KmoqqA4-Bywsoo&KVyKlIr zohC=@(&LmE!KZAwwvVoCP99CYcIgnA8tTxcan#YM?ycvr6>2nHYW^tiQFpE<*nVkd zZvX?mJg3G*l+jsbV-+yICBQSsEV*ev5PhxlI7dj1p{xPkpeS^2qC(Yj1!c&AG>O6u z@;alUg9tz{HNHHPC``^v`n`gfi*oS2pm-3lD4 z!#SB5ai^6X(8K)g;ZwvLs||Z+Vcx;oS~I!ELYqd~wO(opD{L-|UpbJd(H?;FMIgLq zOf4kU6xhwz2gIVfX(YISQM{zML_B{a6M#;i8T~DyoVP#Hi#RXro*QfsGMG=Yk^u%*2l202J$$r(Al<0Da!C;QNnfl=UB>P zvcU{$htSp!ZEDIxu>FE(54Nx2i7jz$U_aa75iXRPaU8vLMhNU4wl7!MzuV#=lU*+( zws019_WmSZw>qcMEyz015L zku@iUhs8|rCv!Mjp<>TxVQxQ4yRomx>yXRP*~5o4MSVn<>o+3AWJ%3Xay z+-ZuDRSjA3Y%=Tg+jk8iX~I_)rlV68+f$2`8yW;<#G~_^s&5o&S&mXgK{TEyXshCh zC*u3`fzcS!T1zEg!@p>h54jtOFui3BCf_}Tk*O&ZQUGlBJopDr{W7s54$+THQabxdb0-s9*u8fP=6a}w*{4{I_?QRNCw(ytI~o_2Sx3DzC)qPs zlqy@~pRTufBx8HE8?nLR4KHb)>Y&6mxAdeASf%`dCrn_MjVnCH7EMH{4ZC!HpBi{zND*c`vBm8pE2rVG%^g zRoqI*eCMJsl$1$AFwT_Y)#yAy}v zT9Dl-qii!*s+cuC7j|09yVU?G6iOvc+@Q&rdn?4Uu%OsEgz($343;{mY#Ye7;-E^$ zo5o@@`n4mH$0UPNv89{Oz@kC)_zlmFBRC!_@i4v?WFH46Nw&qS$1sZ9&)wW#EHAG1 zD(#S8m|=X>NIC9JGy3gbOt~{0<+oREGE`v13hy3n-c(f2%q4F(RlNgVw{$u#MOV5_ zYSE~53*j-tatB&5SL?ImY1pt$ZdhuT>ahETJIB?AGsmztV7U}Cb?x$L4`vSN+wU6p znb6tJ67HZ+#DRR^brIab?$!}G@lfpg^H5To$P8SvrmMUFZ~v}xDG{5zF=dfhZ=vnb zIpT<$#QD&@6a!pUCz}C*bO;C1`L^xtk*(xbqpPQjDezQheGh3wJ0FTry8jfR^qlp5 zA1O-1?~|eD&NB70sUAYpRksd0Uqol8~_$2u5o?XDQAWAXlvK+EZ%cmCFC~8wJt-SeZ=?h5xt*S^_<6x^9WEEuo301 zQreu1f?VnmFqZ?nT00xi0KW4({n5e0Q4pTKd?5LYxdE(k6U=-|xDFiqO$f~g{AO@IiwD{uoN0IQDPoT;Pt}A2$s*0MME38`2_#O8M*Ng+G^1_zCbc3V#4)Y+ ztCMSc<+4y<9z-_cMrS}57`}?_=ci_`y@sc!PtMMYM5MhF1|5!DnjmSs$TI`_VVDz_ zhzKQnOH(H{V|xCm?$vwfcm(_P_p6Siub}(w1+f$JLue^1VEI@L)Y;1q2@>F{%?AJ0 zRii41eG!S!Z+>~SL{=ZqiTUHqJ9x61g3r40?_|8hfT6aJY6|@3%4DO={KP2d8z)BU zwSMz#IHD1$+^|UMI6)rI&Xg3U9O#?0Q+5)UOf@^ls-qgb9hc|4N>$XF+Gr6=sUJVk zT*V*jmTRR&OR9~1C{jj!5T$li##2RkezU(!d}^Jx`jQl_goPGCM8DlYc7B-^oqg!_ zWzGmhz)|JoGw)J>o5nvGWjQLq7NLpPf=VgP3Z_E83Na4ZT?$=-zQmT!8>w0IPu$QB zjH)(o9iCY3TS!6lr(3LE#EVPTy345IGnIH#gw;@ws!DUcz^}|&W|_2*62i5D%i!+V z@PZ+G79i`6KP(UcvZi!W%35OkM>Jxzr{7OVGIFzYWNa~;|8iI?0xijQ zba{&FzjroK-EH?GuX?uJL}3}vl6gAQfJ=)4_oXx)OnAOT+-PfTb&5c!G|%SPav-@K zG(lVPJg`*W<2R*2h9gOa=D^^_Pg*QhJUuroV4Rg71JP6Xf5yPQol80%h;Uc5GZ8Q^ z9cpf6^xm#^ReEDZ!4ZMDV8Wzc@lneHU!MGAvUYk$1LaBc4i5WGKLE^oOl$cPrGWsS zOJ~jCQtP)_rAP#d^NqDBhksi2+r^iNyO9H>r-_S+!MTO42DIw(OW?>lnifv}w(I+U zk1xJjj^&4a?kxe)8MB>v1r!da{8Lg!7(wxNkL2a%ReaS`%4r1~Odtbvf4&*+4&f*z#_Dc>AfUY z#@B1}drCjni*)>>loiCng&u4?e2&1cpD>D4l=z5TBq`c=bx$6zpPq-GxkWEP*%8C@oGdo6(&7cbX(`RIAg?>kIcIOV(A?B!XrC&;>Ry(UAd$TTKG5_ z*l^*~d{B#*sMu(NL_+v>FVE3@-~dZy9!QWM&N~4jP(NK)gqOx#d{uWqIfL5tc03y~ zzlK5A`8Ya0DH6@zI*i@WC1|5^=o96hBDAz29ym4BK*&c1e=KFN+)|PX$UcnB7`8wV zzH%OEbRADT8dm}iPX(MW{TDNmb+7>h0Fvw}g^s6^-N>%A`$<6 zSNGR~E~2e1^VKfZnsY+&p(hU4R+35qQrh${@e=TyoNWpOLa?PG`Wz9H#Q@b%dH!T_ z{07KRNb${K9*pk?mW;mwsx)L|v6meKdX9pTL_cZ*hyK!E7ff&d77BvBWSRa26}%xm z|GC!x-H8{l@jX<*)e0wm9+%D%%eI?Dkr|rif&5f{V%~7GZ(XKU7ZfGJ%X2 z`mN*H{Yb>K4CY-UETi1|B?Au3C0p`_3sI2AvkQfPfSUIF^lNR9esA(${jeX2IC+Ud zmke#KcB_oya67Pf0qTU6Ywy`e!6$>j)xON>W>(wba!}BkMXAid9Lp~$W*2uAe4w`} z(gGwrT+mvIk$PEF1J+*ha4jtG_Z}E9@1n_1r%N6+y*@Xb|1-&-GZyi_pmJ{hAI~3A z^h-#H(&@gRQb_Vazn`iMPM#-%p9C?T83V2TGjaWW!&(e|vuLf8wP9W#*Dx zbui+c!?j}jSu9p7^K{~xrH1lNl(u9a zB100LFenp={QcVi)>m(f#b}()b&p`Wv-TupX}k+6j|dP`k^}nj529bJD?}G5nD8AlKKXUa@2hQ#MsS!`X7lYqpLYJ`j};R)Jf*_s z#kCc@aKZTz6)Qf;Ofa*Re7kwE(JX|=*IW$dm$scpD)?{`kVwHOHAU z%KcP{cd_kw%a0M(mixx6Tpc8&-c>m`ct(*fHOW&x^*9^R{U$q#?%(^{DuenHoCyuN#3s9H+Eu^eFC2tGf?m)^yl9rJkEc!yOSSXlAGRd zk&G7%TMAo|#RhsXS~e!JS~eUZ#PS6)vsrG9>1AI;EODMB%J|Wnue5FA`@@qnKVBxk znaM}q&H`Wk!l;*eay$a}R601rI?q>VNRtDzVQtmc8knX@ufASe6v+j!epPS?^Knv)1z{RyJq3@?%Jo9_tDs9i^bC$^rnqk*i^Ad zM9^@o7I76-+eFzAw|0a(XdPMWmx9C7NVnEK#iZ2t$#iHnTu zdo$Tk%Nw{g5kfgZch;rXV@@q^$Ejp%FDGkuDW%Y7bF>n>HZ@HnqN$3P=3Ysbs1bB> zDjhNiV#l}P^tAU!wgWrh=KiPVMcAA=l+0_@>-Cm0>YUu^0a(Hr^hx2jTt4TU$-On` zCgWf%OQY<~#1hMMTnbyCrMx`al2^NBH|+(jA0#dPc?g?at`+(EK9wp?4R4k1C$>Pn z2WkkAu>_pnv+Pzk9Zij@qbUTpy((Y5j}()Os!V*150>p`*ySACVdJcRH%UE;MOu3r zyoa{6)ApCS=dkQFfO__MsLApxBZG*u5GcD+O}MuA#~})FW;CJ09MUSv{@_uOzp)Gy zVo*^f^(AR!FXQ1e(Z%*7;jpePR%-uFRs694nduiG5o9;NeglMMHio)3N07ev4b)zU z(!n9-Pm|pSS$gozzGWJ+Sx_~`rhF-&D}y;-iXX2{3Gp+3yX@EFtIjPg8B?TK5W7=5 zLAftzLS8O@z{Xp0gvOc8n(;kwh8dHCgr?NlI%gZW3(H}f@s9Wr0lsjg0IUGoK?;_bv34>V~SJ(jm zk(aSmUEbZ{KBFdXyUC3ipy76%BXS>Lh8u>*>|A>cq(>F zl*dfQSKQoslsDCA;C@gsvy}4HO?zFM zb#5=AyMvi?K5nY{Cl^3k8r?McqnCBPrx{lZJmmh|x^z5JXoVH^W~69c6^CFWVD|sm z*%_~$;sLG4t4*vlV*^)sp>u0VUFH%n->?E$nLGWX_}v<>;Iitv;GOk5QMg!j=TG(R z&aG}fpS&uq)R9@CPB7N~rF_}XiR9(zcNr?c12q2z+V#Imoez8!b*J*r>K3~m!*~;F zzdaFR$Ab2AsNnR8r@%*s8M}@TIpUXQG1&Lds3UYUHHed87dEwOf>H$EF^|L40++m7 zC6N#}4G-V4&=eQ-Hx3&1vX}})k=&XULD;toZ8T>BZ}PE1NofRlVm6s^(z(GYh;527 ztC>vUWIXePV@92{M_BOXRKKb7KD#B+1<}P?tkL*MUSbODp@h9#Y>Iamy4WwwmoP}6 zS4X`*NbxL5so~K9eI|pTVui=ne#D1EEvpIi!Q6EG2Oc%RJ<4RB#ZdnHItM`Pk_+`{ z5jeUqIlI^mqD0{@S+6;P@6PS#AgD?mRlCgSE!~Rfpe_5^O>PsUQfC8xCgnJX3oS1sat#fnrNnPB7Fobp3kDO>SbdX3X*L8nV5rj~h z>;E2|{;yCekiBsbh4LpqO=37ZyP|3K_aF-x%ja+r9{N{pfp??e+ZfK40r|elj*kKk z;8r;Rao>tg^;(C-D*I%LQRBhRA56DvRU|CSk@m)@a8DMYQfT6wD^BE_ll0z4y>qVL zSKtap6u5sftS zkaNRE*r_^#3+!!Z_?AZVhYCc){<5*HpCb&G*A-NBnz}MduiNqSzQf)k>!(OXS)C)& z4OO3$N&&0dS!+&L*e*nictJjMVRz=`n)4}*MCBrhBiOx4Mb{Q8_!rZh&F&=LxkQoo z0SM7pL}zzZzx-WXO2z69wh#w*sje6MB2GUk*}b>I53({E+@E90D<$I?RPV++`Z>_3a$u>vBvzjz-TJ+-?R$DC#@5P*K70-! z0lZ_w^Z`Du%xtmwvH>HwfHDU?vDf^VO_K|INz*xmnf{uYQP& zYd1YM<@lm#Me=ezW-3RBcG}lj59K3!Dz>_)e$MLK*A^Z1YP@A|XU4mCx16Ml3PA4n zf!TZ<+WhV^Mw1iTOOwi-=2^GF;N#qeo5|}&afWX)Erl`eE-SgZryB9jy^sf`yC+#w zQTXqG{u*IhbD<4t{VJ85yM5AM+~AeT866MSa4y@XlPU|NxyYTWz6;m&jFd)X6 zTzuhJ6_oOF{OKrV<{Wc)O{4Y?62hPuzpt}Bs%xjArV=4#sWLnZj>Iv*s7d1e9#;JpL_Iw&;T65-7@?Fdl%dM19i8oPy`u5V~Rdp0_ zD#ihdSDIhgw?V%GvdEUt30JWwh}u}br3gTD&WD&Uz5Vk*JOPVbZKzcK9Jk*dsAYa`1nJmTMzKi(VoK-b6x6$v#v?^PuiA~ z68k%hKHs|b@gMTTCry2tF?4WN^YaQ4AV)1@?#J00)_q%wIm>d()UUd_(ZjWi_CQ^F z=+oi92sN2X(JU^)=V2nk(Dbv) z3PH@P$A4*EID%y@KRviF0~#xgXaBv22C%L4Si}C9Q2c(L;RFQdmme)AxAf^;>EroA zf02U%>PcZex*5aGR&7Qnw)pd(C}To%+Nd{`m}F|OaG^V+=$Vrq#mnWeW2#2bm`??oOtvjMs zWvO6+QXgN<$PbU7gSPpM#1yeRBqK+;v{zI7HVupA+HrbamS5`fRtj?5EWY`?`TE_R zb?UaZ^iD=hhbF^AJ=AFe)OY)?lfv9;1{uUqDC{Vo_v`h@Y;U#H;|F5AAJ4(dNp}o4W4Nc-@kvbr0%wtD;@FS=u@-d4m3J) zTYM4y%Ie6ue}FxwHcS}A>5X7lz41`N*ynwjR6VZ)xK|-4k3OdM+i%pn0`y=f4%$Py ztM|{9PU@JQ_9_e~)n!kcnZ~?{9%{*n7{YsJeB}*DQ!2f{KVp1_^@XoRl1uoJ)z4B?}ZFDmiD#qD09-GKESk zQXx5)LXksB6{(1Kmap$VyYD!s$8q1?Y>2AAz%FZWKB1bSzfnjX>Zaz zq%^#{TexX4t_qdzj(7Q*V<{1=4ZGw>@^A(NPGB*T{%Pi3_Qjq+=}QOzEg* zq!FLA$%JmF%fp1Dd?#^rWpLqv1%Ja=a<^B&Bc81KwvNHJ_i|_S9qhI@3J5VxCb7Uc zK^!z6xypMiA^Zb*q9lxn2iD8*HdeCwn640J$bbj?LbcgCVF&0r66%8`85}yUwD!b% z;ys~8t{tW3tnzpsG0u_=C*ie7UU#x1ZzKQ$`-gpXhg+ul_MlGYu!l(BK$kjr7EDc* zTPszITn2_M`T^$7AEoGRAB^ibAO^;btDC-k$USuG=tHF>&UDv-(p0~=)V}&|AIbO# zmM{3DZZylw+|0`072)FN^E;`;?Mx4rmw~{-CodPf)O&UCj%vtZQ;lT&jQxfBx%$hE zhHA|}fSrupmx0d zJB3@OCvm$S%cXV5#2NV~o|c_*eOlK&0nfu;6dve^k%UY15z;O)_V*zofKV$4Ja+Ci z+`lyWoV-rRp`m&5?`VtS!b1N@6)SSOxq-`1;4z+MI1qosnpF%=BI<7mnOpj(8b!49wfck-u zN=>5SWZZbl8m8T?qugNI(BOA<@jECn#W@ml?Qm@@C5p%XY2)oB3f$--4QC8zFHJbr zP*Jb8^QPNk*YU-5iUVAA@uaycQp|I`u4dY4eutA~&XOeN@0wHf4`yelsn86JQ+qa+ zq~<=)PZxY~>F8TspLb1~rbL_1B_g7nRn?}snK?nX$0i}Ood}M@e0xn8c4kj%ziN7$ zU?k&1eJ4CG^ASh&k1OFJ2fX{=@@S&x)QZ+GF7(?>M3+*)caLgt&K*n*YSW^pt32Q3 z7(MURkJVg3dwk?PniZ|v&7CRv9u~1YuEE*3v(}I4a((&>Vxx*e5k>N|C#!`J@P797BpF}fi95|J>VZ$!H0slE-D_`3~KRvPrsLMMx$y;EA$TpuwQy4kxHJrHX4sumP`zeSxy2s&-u-an#cFihzwKh{%>B ze2#pUCw+qtK4?X`4&AK?Hz=oraK$40w}!s6Vy4J9LDqPDyL9qr=4{$`!zNSv+uilV zQE@ZsQ>hqNN4e8`VmIN7yx%#x7;qIYJBAbh`60{Z;3p7J9ag zqltL*&iZ7IOJ&BaSgzAzho`z_4c+>Du$fIQ&l1Ak4_^U^wYuX?7m0kw+BE;RUf!}- zEANcJ{Oa?j-F6cv7Ov7-oGHHe0c=z9P4~t4?KMiQxx!V?)kCr20=0CW?raJw%r+Y? zEv}X;(oeSi$_r*Do<-{PD2Zv!otYYFSGfo=uut)FP_92JscwBM*>{KtUlx=Gb}%kG z?pB7|dn0c{GvRu*;;pb-0t11mo$zSKnXND*j}`lkQqG1SXX=_JBLZ{cd>X3f8x{`S z1~7;2giq4QC0Gs*gm7l70{V?hsc+(Hdkr8=eP*8a2V;q)E)Q0B!bIEKP-65r2+MFH z(idlZqxXb^?&~VL-XLYhGspg0`;OT|Sc9Zj<-yNL=dN*e-g<8W-r(?r4;k0aAPZ?*`dEtN zF@~R~7X6Ad4`8J_auyR3tR zc2hs&grYW$8l2PDJU+uTiscf<2s6cZrdqa&bJmbM=u+&M@nZa^I7c-#HIC7)pgZ&z zwZ^!ZsHiTCWt0+AT2x+Sga~XMrh~Jc#5KGQs>hWq-Tm&bP(;K&--*ET$@jei%SQ$} zdGNoU zJouv~>NS!KkY>JEkStHRf9!;f(#f6xzlypx)p$ zLI)9dc|3d__mRH9ikdMqqfW^9j|!vzK+FppSTRh0!#wVb_>=SJSr^qkQEw2;oslM!R+gBB;+*g40yxg!F$%*qF3^lt~MbfAmk9FC?t@~Arc_Ln1TT+^^8ersc!yP{6;QCKw6(<0Vx zqKvtk8_=S7oUrS)15km(=g?*%4>6SF+9MJ4fT~mgf^<@+L4Eg3a6DLnGj#t9*6$PB z@<+FCbE)10krZQf{|(?80wtmY;DUIlM}rUPuSwA*o$xVmgovn+<+-v0zb+=G)C=}h zckP9zI3r|??pmcptoq=SXUd{r$=B(*OX(OhJ(E4xLAsN08w-HH1h7nO)*3_r_>04P zlU^?c*e{flF#DgJNvt}axFr3Hzr+t-faUz}Rxn-tt(4A_+NwejFQ#e?n0+Ggzop3d~uI7e3uIIVwOQw-G79RFP`^_MfjSvtHN zjs{sU^o_M6m`V^}y8xyVB)}iz$MZmJtpS!bN2r)95X^#@Vj|SQy&P1?f$w6CMTDz> z??TBW{Qy`(K&JmDH7zbY>i#-0Xw%EJNrA{x9{e%Q4-Ife9w~u=muq?3ZP8Nh@+z=z za0<8;WCVN{k1QrR2LD}YOFU5E_aG^W-*xqc)L$lK>1Qfx*m01|uaPaiRqJ zTJf`0u$zF_42BDefQ`4-eG7b7u7U*bukRu^4paxN6WfVsqQ^`iW!!eMDqe0@W>k z<(DC-%7gs}zYi)k7!U$6${*Wp@Spsb048SnKLAWTdM~T3t01AxxLtI$>&wdDEC85$ zAe^kNf`>$&PBWH5>J>>kp`zP2YUlE`8NTQ+b@^5!8{Ca}4U{0F> z{BhKCR$_p_14E}7I=F8NAZ7+;*>UQmzpc31lYiPlsA#5V-b4< zgEQc)*=t<}s6CVs4B>*AQLrO(z_w6wGJsqL16!^5HNqeC2=HC_gZ}HgLO5GMG)^RW zc1~0ZLrL>=f(mrRB0~Ma(ToCrEb%pm4`>aFwF86`XLT)G>ZNeqXB}g zzX#~oME==hy!}o2k0F36d~|36``vZHB~u8rO6GXqr9tbXdzn_T$@m~fvSgAG*F75C zvR)e(88eWh?EUdW3!@=p|KCJr^goEq&5beGLji{|#?{foyYpR1zmUQCUDhNg>U8F4 zbO|3G6e}{WGx5dY1cl#y^(~j|?pn832PP;T=4s<4jVbt%Z<`RAG#aN$HPSk9-~cxg zt}?i8Y#3n#QGj*I_63RqL*{v|qssW#@w@e39lyV4@1^;RhNW(eHuF6VSB-W2J<)9D z-MCcmlD&OYV-DRs#Itwa+PeD3+g)Q*c~+8~*GYu$&WY5kl*yinSi#0OifwM+9Pz&R zDPfRv&%+>?h=^!@I+>;u3yNQ$SgiimMJnKEK6@}SX<y0IsYfB>_}2XTfH!6#;%w6CVwXZ#jga43nCr4Fosn0h($m z)Tjhwxu_Nk=499CWib{mZUxB=Px|Qby<$TSWB6iP`|TmOZ0PnflI4`9&&HCkiwZ&V zR=G6Bv)5xTFf-dD^)#k;v0FpVeIvoDGhN(3A-QQsMOY{AbhQFZ@{(_o1;qFEL&Kxa zIrGe;N{SV~Yj!aatGXRFiAXC+;qOlzS!!B(CU3wO-kkU8 z;HkMU+*$$*xc@AP8#jKIRsZ%G(mg+5J~_|-Rl0z+X8Y>ZJQgt-eVEo^w{iF)28xKx zVD@~N+jUFv32VnOQxH?&hvEg`j&H`mJ5z+q^ z+_%~L_m2tm@P%u)Mmv%-_zC*9j=H5LgaxQDJH0|s#)O~_j%v<$I`=asbK`p7*q(<| z^o@3S`px{p40i8i_DhMxI{t0~Zy3oRPff21{2GGRTGlAf6m@=K*PLguv5M{}=01*i zs(~w0`e7&5qOF<$9bXX5MGo?87f^EEV<~mN@+nq?g@)=NnZf^f7h~kvxEtqGQ3^(> z%WXf$M-j)O2XT(@=)Td0PHhF4Fqe9K-oBf&EtrjOc;B?pK<8KIS|`xd!AUV~+;B{u zv%zW=RP*DqjvU6~6#Mrq1=vgf%CvHD|Lt;l>T@8VuK3wN&X|dOQ*h-pLH0ust&lLa zgK2dU5b++Re|2oOi*Y()X%9(zkIQEN)zYFIvNs>lG*$zV;!oWs?AuVt0 z@KeGrB3s}Ec-Mf#A$M|^y3%kGV%nD0!k&OSm|sMi=GD%m2;{XAU4~Ok?V?MYt2@-J zgwe7b%W+DWCQVebbUL;Me)edOy4~3+11}Y%?mY~96CITn1Zg| z*ZMTwuRS)%JOn-L*mP?etMo!WAmiFFnUCG5r_`Jgj(QyR zk9{&?lURXqL-aKFjs0`W#19bt*uyJrp(5f{9lxTwZM`xF~CeI~mH5H}d-<0DY10@clpTd2=( z>J?z0ITar#p!+#{=i@w(!Tct;GKOq)yyq8t`gaLnX3u}8>~bvPEMKk+NH)1*%L}LZ zV%6`drx()UT-AbRXi2F-z`SJMt48-k#2*xF)sZI}^)pkG5#KyV4QjI1+k}1Go~|=* zAT$+WYQ1-Yy>Dl`U`hc$(VcpdPQH6bh!OG}`PNFVkrpAg^Qz%jbBpurMl3|oF-9QC z!^tm038R|&^CNWg2N}-kh#5D(2(g&z$zoLxUgN~&`fY{-@qH_C4Ol^2%AA)KR;c@uLpWWbtPQDQNEx`*|{JTB%+ zLg(i~)WHt+ido)XtPco?xEiwCv=%3KX6H~znZDu0ZY$?U6}mTC4v{jS*=ccSE@XUh z{#7F68vy4gCKA%S5CgRlN_li1ZuLNEi{SJDaC&Qqx6}_l-vIMXkT2C|oQ|~z^6Zi; zG+-*T%L#wSGDEIvVgbEQc_I~Y_Oru(DkXaF?%X~S3LVh>m;7rD91Hx zSFMt%{(SwfHwPJ!_1aXZ7LOZnVckRduEa@p2gziGUtm|{;o=XC)lB|58<1l)xN6=o z5km8VMi|Y&&v5cNH3Qr|T<$^>o7tV@?>ioCy%Zg4ZYe*x6*1@2(JW{mx+e6kC7A6u z_}_i6WK#Pg`8Y=823k+Fq7UxujflYG1=m)0LGh@OP3 z-I=>cza#xMt27k$BvvOGJsY*4bW*L`pz1+|i@mL%=YNt)@Zvlp<5mwO^<*{6?1?qmr6nmraj`W)@|4g$iQ{Ce}2ez3hUE2k)%*6(8#eEu7OxG;UrSF zL&KM526~H!Uw_E{G9Hm*6$aD*PMh?KGG&HBkuTh}VwY{8TlnvbD&XZzqi*wh)i!S)^X z`m7@oF+BrUY7RGbah5S+c0FRedV@MpZ;;@{LN)%n71U@LR!cN5g#8z-HHwvIK*yT0TeI+5-Q$&=3% zBwsH27zgcR&`p;wWj+uZH*vzsIvW?Vhm-k$V>}DvGhu@Kh@|EzYjMNO2f3K$P>*AO zW_wtmk4S=#9D$;UpNc2E#x_pVV)~;6rTx2a0i#NlJ3giw{qa`-LYDo95REK`sX`jV z#DjAKFUM@yK82f|E1mVlDa;&cETYVO4mS25zlC}{PCxEZ(Z|3AHP&ggQH!dn^Vsvs zvuf5N3@!-AyXgl=ChxV!6H!9(qu~`OZ)_bmkM*-FN!E*W2zWV5`ob}|ZcKNt<`=+R z)0}TsF07nG1{k{jXtAZ?vTZ&Y5pUAp@~ESLP>2p&{R`7ev)|}(WaG5{v&lu>NLq1+ zb@oEtqKJdG-%~()J5Ae#_i;DWtDnnfHSQcvV)PK@POm3+V5)r$D=N+Oj|~Jr#&Y?M zlP!Ncyqq*ItXmVGyM8CsEDWXTRp7~)Sb#Ewp~#nqF^9F=$DRYr1AB>T8gp;!D>^bP zOq&gxS482feo-8_z3JL=>Wk_zb85#?i@vYZf>A`V?v_9#glDd8X$fSFvVz_gqVgEl zhXp=#jF~%EztNtf2Sd2+}=RXCha-_+rn&=sVEIjGqBB!SBf+6B0` zIl#WBzvMRhaofZ?Zpypl_)Qj0rx&$8xLCY7Q^E&C6El!dvmGE7@rXr z^-4hzCzA!0;0YW&bd5*+&KKyoynl-ZqE-po$l{o79F-}h$;jcW_+%BYLs@*N>|umv zsaHChY8`yaN_1X~)+V-ey{!YzC3&=@0sE*e$lBDAp;*w&=ljmP-nRMdl|-VHPwpM* zUSIet(5LrL0ul_J3*nK}lV(1t&LZ}^_g_Uru@FC#_AOYq@qT>+O}tBKllXT>vxRPO zlB{kFU1o$l%GU321$pZr`OrH*#>@+J=Z4Pw(rv;kG=aiy$9eG5vbT6o+nwvd2oVFh znt*Pcq)YR`inq0kCq>PR(J$+ayN7|S6fSBG!Ys9+zJO13wFaVNYx>M}LJIm?m8T!a zLyM`93}D)GnVEY%E-$CYglTcS=7==;VEAxEt*V0;^C|JKt>bH*;{#jw!3oX21v)R(=bP^a_E!2-51gUg*f5$;Vi`{ zAzF}8X*sMsPZx?R{WMQ53m5za7dwuA5Jb~_)4!FW-#n%uc=WS!%1hdtbxeblO49-D zjG2{JzvTqN*~YdvD8PSq7pbUYiuE@m4iD$NaNCO!8XvW+g_nDjH8@3WZ8B7DmaTGm zO%3lV7I+w}kThI$uc_TN z?9o!XI_h|m#tG}BhKhg|G~=fr2$Qb^U}(nUN<0_|>pYrHPFhgP^KH1Zp6}BCI1bum zkiq45RCljfK?f)gI!uAU#O-94x+x=??Ib@zC;k3K*-b{sD05y{y>_?J8VF~!y4IT< zL%7AnznkEYLwQr&S5(s&hDjtvdX@$ZsNhLAavxow!#()Y_x=K~*pxR@9PTmTT$Nj! zeWRhr;Ro(|uU+mjMs*1(YpL4pf~)a;R+RRl zy{&Db+{qZ`T91+0-L>dOGqhMjS-~Kba@b{M8@w8s%%3nfu)o}^jOrQTGfNfq8*+oGzMM-?|1q^j1ZVnpsD7rLpFT%Bu1eO`2!TJ$yw zN<>T8U$ja)*dWh<+&94kj3aJ`oTQ(i9*g>AIa0adDQsX;woY#-iBD8L1?i(Y;IV^C zSFhd%i3hn1#=;xaYJqok(dmU$%XslgGGzHPtfp~X)Ggq&)q&1Qgt{%#E^?=P>~_aZ zY^!MxzBr`~iV9LYwSZk<5XzOsiv>By6e91(s*7tHvjf!t)j$|6={;cMp3V0<&^_V7 zca5GH`U{<~Eo)Y*=(9qY9~=BFNRs>8uimG;(u9`#e1T9Vzm1$0**uOfk?YskG!>bY z>j;#bD6(mb>ye1QPtp45T!xD8;@%;908Auxta?=UL>S6N1hTcE;yL?eq+)v)so{{l z@+UOjU-`eMaEk9Q`#Og99NV~W+TQqwbm3D_N~y`O zx*L9Ofc^q9g%*PBw|P8v0bb*U8yz^>A2iOezjKFcTfORw z`o4Nky=DJ-2as)xy4ywF6Meq&KsaAx_?=;=SCbuP8nbGdd_43dS=d5rK!Q!@C};?` ztM9B`=rEgx;$6opMuINve^=Y)KkL--{Z|*CcV0m?wG1>uq#Dc)Aegxc?FtD$K=03q zIH63o8KUBvcBdh-xPFk7KX{nw{FD)*By;(tR5*|&C8Dzh54z~;EluFNRJ8amoh}4! zrYJJ0SNby>@YjX2TRyxBw8*PL^`~%wp~&4fAlt=2;sy+A{+Fh z+6t%%`Wv-0lR#ipS{sp~AR+FO#ZDjt6n405LMkByZ-Mc(a&Uj@8hP&0Yp>_Fv5wD+ zTJ#9v6c@I?kH~;tg-AFkcs+fj?x@NL@k+aS{xl=asMH7uQuP`5azPOFHvieRGdiFO zu20RC`Wn2*5FnLp%LkUxQopVVLwvm%4aiWa{OuKRSC2LiU_!A7kJbzd(EpL^Xbr~m z&K%Drs{s@F`eO#D#(FJ0&?ODN1@2~u%&M96{$G*W*QMKp*YMZGi2SY>R8AJlb$R7W zaR`9L-EOke0ZrG8nG`UqF2sEatWb&n|4cUutYzrnZZEC>uWLe4vH2n?K*u;zXy|-2 zG;4O($B8i$9W2fTdV%4QAy)wm7pC{!*A9qOUDOr_HS(>VfD&3#etf{e)aOh01g|_? zHy-d!nCve8niOGQ%1O+a7$G-@UK^6bDQ>Uc*Li>mkQQ$OH@EC$`io#c`#eXqs9|ie=e$p2Shq{=W#)@Ra=s`#^0qj*$;!epXl~d=3J>;6JLx!Lsn0j-aF{MSJbcr%2Jq zJ9jH}@7h-8OP+(B?DMjYVQh=`ep7|}m89Cdpw8NTlaibslp-mQwjV3s2{KiA#e5mNp zwobw4D1S}`7-*(L%irTJz=Vh{;IHWJMtm3@(D*k!bn=kH(z2nZH3oQE-=_u<~yL z3CZ642T1rosDrsSHfFHnexF&UDib{+z{N+gpE_xDZXrwokhQUMt&v{AMM_`Kl%ElEPmU;(W)`y zkX}yTq<{E`hZud=d)bxO9q98O!l$@Vwhr#gu4=I5M?4;vaVe?BBI%$^nRTu2CL=^> zN`F`k#H?7J!4fmZO!t_*5mLPRB_6%z0oCm8Q#T$0*^ozs01{efc8?BVzmYJeJH6s& z{sId~j7Nh+zD{7?>))iU4OpeSJ*R-UFzOU-Q0+NfAc5q|9Jt`APC8C$7~R(^Pplas zr=rC<{ZUwgNb4AUY;1&D5e!FZBD6+B6(o$~vbYFjfc6QOfoG*5!CSydSUG!r+3izB z{kv8i=pP4Aeg=5|?NcbyBw8ftkBen|xC^I*TbViqW%f>UM4*UHRdj&f)15Ee_nd** zj0rP33XFlSB(E4;h3DDwY@PRihEF@#9o>me#{navqQV2lcI=X1%-NT> zXUR}h^oOSsnUVjrqM%FoC)qxTu;9z*k)nbzudVK&LQzzV>)%J|k^i)!e_9Lu=M;nU zKTk2Pq}4wn`KUFlVBmM{kh2?hC^X`YPh5R=B>M*?E_tj)(~LkteY#AIL?k;!ODOwe zJX=#xo`Hx;#XNJxN2pt;jKvjHL)ZeSN?Q!>^H+ds+M3H}X^jE{;_=EJ6E>%dCSlMp zRKpS1c-g-9c%#$n+j`{{w_Q84Ku^f~LPki}d3(4n6M3o)h~8E4(%paXqJ;lM(BKA8 ze*1oaDyC>}+wqVu{pO6(Ps&OE61naNr$R{3K{(!$lLr|3od8L^^gt{4u!non!>Igp zcU9vH6VBwqanQ^=&r3OupE>AUopMD>3GeR(>JJl`I$y(1r>_Etq!Y_PL$)aCaiS%$ zMj(u?*KwX;Qq9pS7?75E?@Elhj{DwQfLXwrO*gkbzVH3?ylqUtN2~mtCZD{`#|UA%{A!mDL9LRz>*5b znP{l#Je`GLG*qKKx4>FKLUrsu;W??7yr&f;`lKF{oRg9g1i!QKY&@j6d>Z-uAj<&m zv9fgf*65t7>3hU>o7#ztW$%OKj;I&&`PJ19G%rbsx}jH-?X{I*(NH>H1(n++Oc1Mv zu)N4@Z7j2w+sPw1k1GyZKe{WjA4A6HIwDfw@WZ+T%|*1e5aDP2y!)2x#t?TYbR3D$ z@@uZ72BH2^Lb{Fz^5X0GW>HC^9hXGng zy|KQt1U}qH5BGhsHkrtIid6Tk&`&1X+R9bZ(a`ryvEx@5Gi%4+@s<-vVL}pu;-{rB zij=9jxpX+br!9=6r%v~sqs0lGl@$gbr@`+W1tq4_kJY+z=7gisZ3Yp+4L+;P#16)T zs;Gp}ketLOdCjc%Q6e@b4f`zj-trBtWDmwUo+Rtj z(t|-wb3&9TXw%r5y%#q9i)Gt&Ouc)r2X0NqUfPOoXK3G1 zf#0EbFLxTr&ydLc()PI!yfTD>BC}{utlW@KZaQI_y`j$RV4&T?B|g*RX1q$mYJKLN zp?g2?6Lq^@9k0{AgZtR#zJq#vtDj@Ue(z@;RgUNJdty3p9>ZPmp>*~7aZ$MYj} z*A~@j@?a5}n!Xw0Rt_nL+gC$_!?g~sa=WEbV=a&;$Et<3PH1k+I|;=a-Rxn0CCAb& zlcO%8n}R>OX|n5jKXBlVz1L}OP2?sb;u2&-lX4|F(|1LLyT96;nDA(Oe%ozMK-%j` z$6a*$*%f~j+ZM1DXChfTn9a+-A@YWvo?hihEwBO*Xm_i^sfplL9F9fIT;Se4E6H@?Uk*TMeLlKM&tKGG44>0`O;pW{ML{7Lu$_M0u>95hi=Ze29fg zGnnmydTs#C6-`WN@#xzLVinAR&wI_C9n5#&hyaFY9(DI^& ze0@!Hv>rmAP<(hsR?gCCZDkj`Tt(|i!0oxXZ+cSA;{^Vx>D)NR#5qWM=h!=b9L$cJ z%U+0B9bG!;Equ0LR^njhrK%^TE@5cgde>*?956Oo(a>4T*L|(@!1Cza(QrQtXR)wL zNeM&^z9Ej2I(1Hn9LW4%zBUg&x-vYmg4Z}Zkljlb=!&>U?z(mk@zE3Bg+Shsc+EDH zj~09#C-3W;rt}ezHP_K_j!ExM>3(K0 zRj2Dz`_8{CFl4`0kv#VdSLD=%7%O&P74V)c%vj_V&5SHoot8%=&>6@_ffbQE|0j0X zJrUY%#WYZnoQ$ycD(a~mvR=BjzgWu7SF5cW;CUau=&2!-8z-LSTV`Gxyf#H%3LU;L z;^;%>upaR>Mv){K_LddY@`RW`S=g7?`8lDLRzEY9I7& zYAp3cwE*70e#!9R$}1AR#TTS_c4A;RRhNCv(r!PY!ZPfV2x9Ft530dxPHBIyY3I^M zfg`d4o1+EaZ^kE!Y}?RzYuT?!cMU`K)lG84n?2_sUb{Ig@w-IVJ>w?$!Fs)(;^ zsN2?>F5zM1mu-a|}CVzBQK=y-M9 zNRU2tZV2oaO~fSz@Ue&KyE4YO!6{-#tmOi% zMc*(mth4WEvrF)1si=p;U~nYYx6RiF@mbVCP!+_m!!^5)&(V|oNNaSns_3Y17U~=B zs6cBoAkYMNcr;4G_s;ry&0+vksf1J=8qQ*r2)k8b1}n+q8|QKcFE+F2F9Hz)91}*x zgE|<0}JII zPBg(ci!z+c-R{!!XpB4NgAPdHEmz?7JD`i$?Xi1s0WS5dUUrQo4DKYG#k*XSAS$U; z#Vys{NsL{gI_WY+OuCBaJQc@>Vs=ey;2gTjG`P#-qpLd6ywk6Sd9glV|FK>g3*p+C z5*S@%H4*a;wTxU-3mS00dsW+S{+`V+3zn}edAvY>G7?vxF<_2xHbb})?WByA@~=nkPRQRPloZLPt5%)Z~4~mR~H)3`=qjE+ITF95oCmW9)^C(}oP2%>{t( zE-j-ugx~VY9vVDd`2>MBtLe7n`ZPPb_1vjJ?JE7xNifIPzxhtsAGmzNYk@_@QW$uO z(UQ+YOhDgdUSO5>@CFKT?K;?UTXbOYwiQD1s>r?1a_Ut-qf~N8M)Aur!xtT2XDPu1 zv@fyd*a+Att83*e4FV`(& z)Gk4CPHFDiTXeiMwe@ab&!D$vv#^}UeCNDiWqA1Il>3KuwDGQT*A{r2P@#cLXOMF+OTf z7abDdG+_PeaFpzO7`)PGUiV^cD*8GneNe1p8EuWp3!)fl27Gdra+>#s1! zUFi5SqBTSH)dF_F5Up{fGkvz}{X1Een@>8R;+a`D*LK2x`|0P(d8?ht*EH9e$jON8 z71>d%b#uV;Jzi<0>xvp@-D@}IJrS#)v<-cEWk*ca>MFu8khyp-27_vl;Z=_vSPsWRU{|pM=fyo~ zyR7;yQN(yEN-)2;$QSm(B`=b7vr*6|!8MQ9SklnFFUd!eg7HcgWCu;M?`FpHBZ3qC zcuo0Bz>|K{3nXP`unynjnav)R(WGWb$&(s$YMlGo8bgjsPXGnJUtDQ)c%m_pSn&W> zBH9f7Q8b4hTRkz-_D%cwZBeWKf%ZJ21nl)Z+wpag*G)RT%DHWtl8*x6m>yXL*b{il zt^}AtUMsIK0$~fPOS#j54)-QpHYpd>5x<1Kr#*FsjtopDUA-Cj`V_%iIZ*MX^0`YE zJe?aOj_km;Weg7&br5vja7&srToaa|VXAtR@uOn~3Zep=v633=O#eqWs=vyj9$|=E z7lPJTmp({1vOdhnf7I)AAeRhr?;RZ_lwihmwGHPykPZEopuD);KSS+``RQBpf~s2i zo$ka=EVFaN0zNRAs4#^Ys;$ZB&DWP|exZ*VNC;a5Z_9S?2;tVvLZ$uO2+9tpxLbun8@RTO=fBJ+u96AQE@%vVCP`+a^49s$k?@z+B=>Vdv>$Qd5M@ zB+QGh_x5;8gu!S`nfmP%6n6fP(^U9kwO%UD{AYkZ@WP)ZMN z*Xc}>hB+PGidY>VyZ3rc*kv#{zSkA<<*S3RD+qkxpHy;EhIltC-wE3r2qZ0puYU-q z;xpsF&TcHRBvyG*3X`1v@?@J1*V=m_Jwz5{2SsApYZqeGyT zFY3lK&nddi{}d+?+7qi?gZ_9mVErw+t4=X;a!CZT)_UWB&p2h=bxYGhs8bc!P>5OUhV8uwzU$KP|4x zsfFX(}nlha}S@Ue$ie_+QyL;Lp zxu?Bs(AzD=)jh4tsM-8RPB_g=DDg4T`h)#aJ_%UCq>Gw9Q4=%AJC@AijY=wAc-~GT zJOfuj{wjdRNixRq=ZN+_@J5GAajdUFx$jG)EE6M$&7VGd|LHQE#ejZ)`z$<<#!&0R zY0^jP;Ngq2ho3tQ(1Vv;@TBO8BzNk`^_ueFV1iK3TCoU6i9TDEat$Zf{T}@e1bI^K zW7wH*3l5K3RO_p0;?Y%jpnoOQ4l$r5S>*2$icD3>b#^(b& zVj;qNar|4L`Vm3=Flcz~ZD}FnlbQ+YPfxrbRUkPs6f;$C=7-14>lC~9j6&Y7B}QAR z-^uCZh>0(idWz4Zd0c;F?^>&_#P>803Z}iwZ>YFqM_0s`0arYvtBAMGY2P}kw~T0-O88u z%cc=bs#HMRP&4&ov5Xy!cz_hB=$_NNdFgi^Jl+5PKEcaVR%c+0$j~b7s-vWyxO=d~ zBCSSx{&4H>+lkan$lq?7D9q*!Vw5O%;1w$Ck|e*2kbG%DS`P@Cs7SPDyn+lEJGHDtY`+yv8rg~zS;{c*0~67`)=L%spbft66}Fy!7LH+>pouZm(R;k;7b^L66U`z^hCQaKTbQy3HP5p5bqbt@%ntFui&fz!7le-n4 zKvHLDI&lEr=G;fZ3xc9Oa9HL#$Pz%Pw1p4J11jHxI|g}M)r*yjm#S}Ue#p1{pmO?q z*y2>VkM&%IaF?Nt!_}K=oQ(@zURTH6PJSp!xJxG+>$$@+9LGWpn9XOr5-4nWSH`%J zS@yPr@Roz?Q@jG{?~BGO&bK1YohCouN5RX;`$l4DIqOq3E0?Va)pq-Ur+m%ZdkX4so{-}HyT_1GvXrHueEsLUWM9R>`4<+Cz5 z2clSn!o)%enCas5DQ|RCJ-iYLIRJ#S_u@RRHGp`Yep0m)xEGyBsQdC_W#~2F>h9s&`%~zLpbnE7p(DXPr_}ILQC{;_A z0R@Xkfza9~7q4?@aL}@k8rpTcYLLfrE$M!)Ivowo&Fc6X3!Zr#$Nj650Vs0r>porp zd<$a#`05nF8{i7K_)KmRpP_wn2dsqSjsauzqBSkE&)dVzZBqos6tU$DyXCf`&Q-JK zFcU=kCc&a1c+J+<0OM9X=jqHyXMHunmznci=q$-6GuWgKS9UUux$nuwy`QIu)9Pb4 z*!9M&9vSNN<&A%sP1wD|rXLvCXTa`|&zU~X4cSq?ZVZ7MW&Jd7TV(Z}vI1^nIbqy` zN~a$sO&y=v&G=GpkY~QZ{fDa(s5ISsJ?0frpq*JB&u5bo9zrcL8 za1QN;`>G%6sHf)HU=micC6~0TRcf^$1=JUoFPc!rs!!SVC+h)2(c1N}%Yhb`9j>Z5 z7!6G+drJ1p8bm|W{NLXuc=^%+{7&OHn;5B}U0-Dycc0;6y%u*ut+`Jlt{yW7w?DjLMM5wr9NLTe2^U2lpi-CuB zgH)rXkMmNZ?lQg#86VE{Q3+$dqT9ZElQ*hMr+~>IUgB5bkXb>(sZ!d?6l@G~_A9In z{-`(}0aaBXc=R<;5d@QIAe3^t5b6v*)`Q={$`xv#DW8lipN#T^-C@Ua%y)VUQja$) z*U9F&^h~x`JqozB?be;-@l+qAOA|FC&~*NWA+{1!C*yj&o4lu)KyijK_Go?RBp(vj zWiq7?!>3#)Ce3p4eusUl2M0=%Gec#@j)Ry>ZciH)QhjGBZjDVHRoQ2FV>RKgLYjATgK+0vlm z>Jep&^4qG%^kcCDjgE`ZsKc@_3Cc}QV+F|qHZFIK-T6ecMq<&pRs_Y<=ydFKldrHU z<&>iK|HIyUKsDKIYomgwfFMmoKt$<9ilFo=9i@W^h!GJG5fEttp;#zN@4bnF^xh#T zNC_QT5vTo3Rl48BJ>Jx}dJ3>!V?hVv@5t&& zbkZ{iXC}lq-rExE)p9w3iBj4CZuv~<8;OA!^7qyXNT*-XSqk3zn%5d||EBsnY+~J_ z^6SCl!`3p(!?T5Y9^I6wpo|E2+h__^i_9(6sYT;a?y}XHZEK_m5}^&1sHm9Pm~0A- zjpjEQESkram`u8Sym#$MMI=l@u^NkV&MkeusA_!O{@oa2SZy3Y6i!=T1n}*L%I#sq z42>SQjffi?Ubu;wX@V4BPk!fo`S3aJgBJ)TSjHyxyb;)GDFB-M-$aynGXD0eUA1U%5AMtxYcn1$67KZ5})t6 zejWnr#?(Sm1DZYr|PTH`-_>D~nBhFIjaC;Q_Me|I_ zm{qBXM@fIea>K`E@ePU_S+t-QChSPR!p3|mfXnPO=!aDV7$G&l(nCkwQoRIrAhD==GrX{RfAby_Zc$@+d>0(Hjio4) z_`P1#g5O|-uLIRcL@`g|4OrXIC{bR$B-A@U8Ju)qg}d>4D2?4CKBMz=;e7N!C#l?A z349y}_&5#Vz}G=I|D6`lM2~TaAX_TCxi3+POI{lt4#;noSCRl|8}tDGdDI{1M=%=S zJm-fheV7*}HyA>TV}XEU&bLJHB`DqRMOr5X8+aoS01cw0ZW}FTfuL+?2nQ#@ZvDMw z`ctvv+Vu~Motn*%#uk7h38z$t^Rhr#tr;86VXyz`4Uh@z94G)`BIaz%NHQ?i30Z;* zTNLnCf_H4}pw1v6wSDQ+48o(4)cq$EL{}6)#iC5YE8bQdm9T!}=cO%FB{ydPKb1Q8_8a{fXhi|!<P5I`aXlKh@V2wZTVMgWst6DG}4Krxj*?d@eM*F$=j$vC}84@j=ct38uB~I*(y8ku-YNG09keJY1gvO z2)C8KX5rOl_2%9MVJjwCt-A@zI8w!>K}k|Z01qIZ&nXuKK!e}g?i;ra{pk%5#1S^E zWnoJ-);(|K3S6hl5z*6OS-dNahyvEh##Kf_c#ad~MQc9A(v(G%z#8ojE3?ZMzF8KU zDi@^|fjLL63PliD#>Q>P*iwCEF34283~c+0_X{s9C%NO{3Ovo+>EG$5!Pp?7QP0&S zi$%XBSOmtTqhi$X$9A$>nyC_Ab(|WY#m*|}c1pAvcHVvN>}rc}%?E<3ujs|89%l zn)n#-PxBh)xp7W548EXl2N?_a+NW0i`9NQV`QnKYjCfc1Pk-8K3>3$FMaBCN7i)L$ z0|o)L53!HjeM0gv_G97A#7~UY=nuSPCW>>vGsbHB#+95KORpOP#et}#lid{%(aqgV zo?Ta!x!JPls22v^f-^{-AP5J}@J-SgchFXN%Kpg&J6l!BHUaZw4ExJfg9~@M1a{%^ zuY&4eo`irx&lcd6h}#ZJrAA;BvFhy|Fp%*@zlVl+I27!R1EJOsdAZw!b?lH|Se21O znmy>#|N5d#0RQOD8u+czr)sET!JDcP)qZ-m)N4Av6I>tDE!@OK5&<_EJa; zLi2qP*%k$V@60TZiASl(_yn9)wy(V5sFn%j5OiP^J5itk{|N+K5~7^D}85& zKw+r$!6A5dQ~d;li@=z&XI|?7_BnP0JwpJuO3f?Qqof2RlGL_^G*PhfhWIrUdktg^ z(>V|Cs>=L`II1t7&cBhR!@W(_5o2OO{&)j2fS`Y2>yg2&;CG@mcP|Jca?JVyS*A^fOvLe{I{S+crcrf0CaBZgpvyG8UXF02*0iF~In2GtiX|sxn?zlnMco>3(CLf@oPD z_>PO>=CPOVp8z(A+_%@W?*H6%Zwv&ReVr$8F?ZgI>Y?+hGQ3cpeGCxF9L3%E_l)Wv zgdmw8A&8AYAmYKGJnX?-V=gbMBk+BkGuQ#0zRthJ8~Ky%<~$*dIEFJ$^eK1s#}tOKp#YY^{(kF&X{*bg zsW1!6%Lf)^%+HLpau=HvO~T7K26%6ELpjBaGkp5 zh0SHiakIptv)JndH7{=7z|s>-e)#}~Y>E7)D)5NZ*gVemLPRL@&0b!4?3)zI-Qxrv zcmzwOdzhX0&I?ckRkGO9+8$Y`eEM#{b1$5s<=QI4ak!PQmhTbZ1{g^~R0&ZwwxGab zf;9|w<4d*Qa)}@wowm9f&(19Q*$LMuUgNZKZ6cbL69627vu)02@^k(aFmD^~r zL=vuxafVV_t%I3>xY-wpxJ}0=VQ?@k4e<^eMsrnrGmh3e2b;Ii(B|j>`bW+Kp1wZ>9Um3@P? z6F?f?r|7FJ|EUky%M~U8kyC^*EP!<9`imwSP2=+Ur7e|gpTi5;uR|1)u=G%BW3GN+ zGVC91r7%#pq5cY9Pb)YZ$#b;B?6f$Zm%}7mCBTKMicRb@sh4^GM91|66K~%!Ni-vu ze-|XPCo98)R{?0d9eu?f9;V28ChdxN@uONif;7@gq9Md?DfBd#aGhxdb^w5IP7)*q z7q&`IoOleMb$#FlJtqzQM`wwyXaE=8xAwsC(tU11muTqnR=mR4SFD?Hmg-vHM(c<5 z4fJf_J)=r*f%u%d9lu4yrx(&D)=ylw*TSGVYt|RrBq5WV*X(bh-;}6cnsh7bmo=aC zO!gXS7z-AZ1k$VNrxups8BPbOxptW5L7Q8k1+b7rjKsQhiBc+YB<_mlPm2&mgksR$ zr3@I1KyGTqEN`oIr#1vkNmDmHhv2We`WS&T`2N+Q@#8V@eR+Ki*pk-ZFgI}k;K^vA zoO&1mT*ymMs}37sZt0JqU8g;}{J?9;>rDu9J~XIvdIyI=_gl+jDR=FfNGmD2;*@IV z59j>}y~2jqKa21u-{hXz^9TlWk4KU}?q`ED&Gv*58zn~S92>=lLb_W9X~h`VF#?+T zwP(>xvgVbJ{dQ67g{`eE8esahE74AaW~9Ge#N2St8;SW$qTe zTB$Dd(dtg2V_Ukma7&|xd^3?ml3vJ0U#n10zMBMuE6*e*UpI6|Z3?WGqOgXHIsokK z!+Oo+$5P8D?Kb+X_8CWeSMV#xjz< zHpFbHUd-x~NQhOInx{!0%+`j<`-(nb9GBm(^-2`X7Gg_P`(|+EG(3xt+Rx`$3fv!Y zi~?*ADXw;%;}KdRNj<;=;?bG`nwCSkPZC7ST)3?#ch#61hVGFRYcwe47&lZSwMXE| zDTPw`N#D42DyTP8>pE?uBx$jmFRjZSwFmexQ-WfoLAGvv)>I2Q;csYqlH`kIsOQ*_ zu5M}!X_aVAoyw-M)8^@CkH$vveq{DwWDMbZYU0xf=yWqrX2^P>#hTGeC*WB~|A_7F zp%Lrt*i?PHV$<1X(d+{cn$5A-YSEW*w+IU#fU<`+Opxfu?)e7tnb1>8;*SB9Eu5x? zh#(iwb8>GSW+uMl51hK|_m=Aqj!w8+gtlvnXFPK!=f2GoqM0hbwBt72cdgyz4z>m51Cq9bp?N+C06HWt^4z$IeH_kGsd?%G)V@ZeDwq5qT61FTdVZ?>3*2N zj>gZ}M{S!dGime?o#MXP*EaEZ&qs2q^TTC&ytUr$#09|O4%2U#eW}yXuNt25GEWft z$ohS9^#k@0RT!!rGVZ1rVzAH^g4-pX8%28eemZBc04Rrt`44n4hz`2>uf50HQ$4)P zwBOz`O)Rs4E#ss_NwH0`tRd!5W?ZB9u^O{41Rs+TRi%8^g zkBYwG6?-lXinx-bi@bU|4H|L!fW^YL$`j*@(jy>IkUPc+Jn4UB3q!2zDSevDFrqojqS-JxgN+O z$z-%RD~J_S1(pV?GS{O7PZ9+@1<~#0Ka6f*a#%|9`gLf>A6x*$=>R}jO$R(d0WJyN z{W1@li7Kz)_U1tc$q{g*b5e0U>sN3)D!*=rmYuRtYS=m*b!Z!9I(Uzb6F4DP!}Vi%YX*ZrXq+^0{3M_hEbLryz=a;C8zL`$kL4t8Tuv1eEir- z#Q~@G2v7NqmF8vO@+>*Od3*j2J_ks7L4)h>1R0Zm@%#qjL8+~5u_Jd8S0mC@o6;+n z$MK>OK67JNO(Lq|6@Rj%#-2-_p55$xC(3tdOlLiX`8<=(ouv_RWRRpQ*Hswz!TyBh zDFqG>74J0P7JBmKnVVy-fYg5cF-91+lK}0GV>YR#5 z!>PT3w)E^If7Y*6<81x@+`@fC9D>2&kE8v7hhe*)C{GFx-^3jN`XzS*aTYw>wPj91=I z`z*C?LB3vKMNGsTct?qFt?iZcOyC4h&rpZM%}<%8AH9|?>b{Ywo%7fomS+Gt3VXY; zeOZ>Jv~raa7$F%%N=z>KVGpFeo-&i$K!);(Ju)9OxzsOAwRY$ZKG6x;_LAI`O%Sf2 zVA}Ue92>qzbFK%U5?ZYjNZ==Qe!J1Wx1DPE=t0Av6P^fXzm>dkLi%38vo<5J@IkkR zNPYyTp{`Thb|*-_@P=;QwCwFo-O~PiDzFz4E6|tjDLJmWDHKKf_-mG?0*4;xIClnV zY>v8gZsW(c9*r5RyZFA9Qpb4jk^?gFR10~8)@G~f(2I00Z|~tXat)?FBk}C*Tbr@j zGhw0JCSOhOd6t+|cu8c6+8SMjxM3y{FLT(&{0B4z-?Ezry5D{TkO9 zc!*tvPd$$+nC)Mw|t#oP8#9&Tyol|U!*}Pu~rC1(OH?M6-|K^>q0Lekt zWFf?W^E`-R*gUYPy({I+mY*Q1GxD_RsD)AKOay|fFTD`BKMCQ^B2Ij#b*|fa2y|K< zNKMHiwC11pTp%9y!^0=*NOuhGWIFX zPKEiv*q3AML7wBZ_aPXtJ6Any24+jCqx(&lF^6=C4H>U>K=!k+Qn$Upc*cLxrNfZ7 zgvsVyZ_w04)Pe@mGxf3P%KP#pv-L|jG6g2=4KR$z+C-Fyuj{N=r7Fqct&y3-yT(h;js#QM2!WZ(CN{bjt&8@`jT&%OHE8%e!$7bYz9jT{}To zfw!#Mwqi5Nuo#~Sb|j$6mOUKhs@%UQ0Ew^h5)6Dc3-4eDN8<3GzfL=aU+4sp4>N2d zYy_cFpnjH4mUfHc&pcY(=^k<~RnTtiu;T11(Q4tD8GeJz;*n-{N*Ou690OVj!-)8q zwF3=Vht~yn_k#~>p1lD)I6NEYl~)(2K@=QYEe;=ORqB*xLkH;a>_cD_BP2TzW>Eb9 z(~PL)E4X2`3ob0Jo3ti{SIZZLW58t5bPK>D7~8{0@uhuzn234L{dhJId{0*9$Y+II z)F#$KK949-nxGBa0d%hUT5{f~t241HA3RP=-PcwhjV(5THH{8^02T2^!ns}g`!pbT z$nRVddLkjcs_kIl+YP$Q?OTCer&a^%qA4ZyQz67+MM+3ofFkji7O{QgO1 zV`$VhVF?49e_ul`If66GaRRjiBDEHU>zmyHTJ!N}$W|i!gJV{Cny;UJVZ$S~*T4M&Uw|hh{!Q`1vviqMLec$WSX~_3jRi^xXBV+5{-XCuXZq_*JI89SWGz90U8boORL zaynofE}$_Yyt3w5KFR|7F<_(w;(u)?7L4#d=DvZ=W~ZGLn?xeL3lB5^HsMlhfd=e5 z?Zm^G3A@j3xP7ApiL%p7IJ2x00uG{9Kp1~lf+n`tJJ|(BQi#j^EH2@$$u*rd!8k*6 z1G@QWX&%Yhp!S99@9$)bNqElX&7j}h?B{~Kc$6+!Y~pmrj`mQ=O;zTdyfEw?aG^#L z5MdbsOF~*x!iD(O&zQ>Iua}b-Kq>f2`IF|c@>}N>t`02z0X$tf1qNJRH+n-57|f(C zq<>=@xkkMSs|oRXPvm^`^bGCu)$mnOi~A>3WsaNqSDj^p1bh-4uK>046GrKVUo%CmC7!XxHq`%`oqOU~c=g>)83cO!QP)%K zkP4T|id0T!s<>C&q>YXS{g^ZA$^y!#)NLp*h(%onpOs`S9yg!9)XN?=>2?>)OyAm_pWd*o3_xE4)M&HyE3|%oPBV(_; zg=Ju^0jU3b4bnh<+!N|oc4KEvspl&kLK`dXCjhg*K_X=xPm*nid?c1$?J>uXnBXTh z^cly8x7#YfAFku-dsz!pcX^LQ>dK?&dZLDbC|h| z3o;LmMuD*kPFJrRP84uXMKZr@plwKOd4-Vh66I@r{>1Vle*)wIcJ8aYfY`u#s=mgD zttT+=ymNTrdGYyy6W9U#8_-{G`)o)DP=J4W^7zk`j`qw;Bw;_v1v`hv0ObC6m{F+G z|6R-|5bpo`h$cVt;M#)%dMYaUs}!+WYhA5w!T$k{s+`mreh#)_i?HakW)}zFh3@3B zokIqaiBIKWg)fRU?vUaVj!k@Gzl5!nAXP5$PA3xTaf8GL(7D1ZxatnlRBpByV%8QMc25n}}?O8=e*ghUjos7rlF z9%g*n^$S=8)ZhrJQa~4tt6p6sjqssUySj7<@+ZO-P^6h_toco}<}Vaf1CbMIKG1xP zPouR>+e4s3|GUhnzcJo_ix@i@A<$aSz5oOrL%e>C#Qo$eJgskgw7+<#SC#OUB~+6gqP~prA0Av>*oq#!z7zA+h-du{o&_p1x zROYXefU|TUv_=bo-=@Y4#$b)ivo2i{0v8qDFK-XRaRJdr6!a1p_c`{**C)WA0*U$5 zZE#p}?4`LefPAEyMhrTnhCwA7Y4d=d%>5T>Oq503;6oBmhT6XJc?~izHZqN{7zare z`Ip~@{g%ETip3~#|72%{fS-yrqO?gPLXRs(6`jB@$d^0}2Uw60J*X>5a(vZi9e$cFf z$Xbb2WtKek@3N=PxYRC-lwGky94*-|rH8l<>X;xM>qd!_HU~l~=f$=fv@n3J6`$QU zGvzJLxp&|2zqI*IRLnu(Ia=|pav1)Q^QfP;&_ z74TB<^MFox_urUq~SpF@>?%f|!HD||8_ybLjqW2`2{UXQ3)0}cL}c*bg$ zpKmtZ%j>YW#*diB)hW(-ecItDdZz_eW#_(+j|BL>C}yUA48Kaa=YP}^iuio%nZ!If zK&Ya@rO=(ZtP1)3NR< zRsgOz^nV0b{J$?F`WueTrT-Be8;~K@y38;X9}(74?|+o#mxf$wDMK?@KvY zGtBZ*=9^%+o=Aax`6pZa8^e^x(j)GEe;WpUtY(%p0k{Yw2Q;6|j>4c9E~)DhMrcK( z^>7g1NdgPme7t2#?$ZtYAjemGx}HphL?}k+Re(^ygPSeACdFMK;n^_KE~UMzcw0ej z@@kvmmSdH<0S>q?cx?PrCIu`B-5Wb{a1a`p zkYn0wi(wSvVfJv;R9y_xK@atnb5!7MO2qIq*`A^fpd0n|ESaFK*91uXV zN*5#_CIz2)Y~%}jSmB#h@;eMThCjWDKpbmhhy`Xqe5U`23iwQy@O7jh8?$%jyW61N zxlX@vf^ZA}L4|e2CX``#GG*riJyP=jUgrPQ)%+8UYr+Cxfz48PQ(8os(bqHoPu#=$ z(L}XlB0oe-CAK>`v-TP4J)tji=7s8&Gr32gPd5*s9F+;^iPQa_Co;eY@nF&81aYIe zz&3Z3jpaG_JU&O5`)ZM~2Fj*fabo)w^k0|_`b#VVe6Q=j22Y*`hl8VH{#Jd@Mi$>} zut>>O3zpc-0&J@6_NnggCS{;WzH7a0DY3P_{ewxf-magcW?y%3QF`YQx51_&1KXI= z!;83X#-Y$N%Oec8$@2Hub>UgOz6MPJ@RC^@A{yTO7ts8u%x|{(VjBQ8 zvR(}9c3R!DmA@(SBvr1kY=PD(N%i={nt)DTblqtvTQTw}kjer=1=gaw3 zWGjJ|?fL_g_s^|ceIXpnQjqV+o0&WUuqokCJr#GfF2l+Sek&9 z)+)|^t-rXOso~B#s&v5Ml{)A)07A%R&jz>hp(KCxnDjr?8%?#|zrfGgO--KuoqqGM z0k-Qz??73t${3^%^!-)EIp9i)%IR<+3-Z z!7yHvnb^ilOLLRYBQ*1d60>tqz00PSv7q(Gv6^MH8n4%TDcs5spLvR}MRh=@FjAuf zt+Ttc8+n4DK^#J+Jc#~LpF$HdQ(IXFZo5MiIfg<(fk8bm@4)*&< zRfLRJVLi3ZuU>LPcyVB`G{nugdj8J4C+v%|Zt(z~(#YYW3yZ(@FzaBY+anc`kV3uq zQ|56&7y1zB^yR6*9ygjzTi@}ovC9%i;_h=u>m9IP@Ao~=ssJN9i-=6fyZA#Glzd$1 z(SHyIeclqgzWJcEa-qxhYsDUBIxgj!nZz_%l>Z;;^lJ=s}r3 z2gS;KOko1LC}+y<>v(=SIc5e?8}!6Ztq1({6j9w%-@$0l~jizILO}HpF`-! zaqw$oKu^3oE@YW?=8RW^2EDN368&sN3TNp4{Bv09`ldVj#YUgW1mU20#7on-hb*h< z)D}QnMC6B70??jwL+8dY*V2B&xAK}&e`tvW-Ig>Ba1cV}wyJe~ee=rtd*iL%Nfxgt z1O|ocZ_T!!K}Y4q^`!n37Cp<-a9?BiD#XQP2rn#}zVJg>Gy>;ZvEKE!(xO}#&l9iG zX{SBuvGqW{VNrj^O!S`lx$l{K8G-Is$7{daBn($ZW7mbEAL}B%AnIE962-wR4@1^| z63K2&*W?z1>wUy+hB4{&uTma!knLVa-!@GR8jv7!#S5Q|KwMb4V;}=cHLL~4-PSN@ zqtZJQ1w5Hw$&B*Y3rdjG`W`#20Mgi7?DthbFKCV#XU17}Lcxbns~!^x9NsN_BdpDiJ9oD^6=3d3 zUms@e3|q@=J*Xtxf7Bf6b)@;LSIIHEJrRO)KRmdwToEd}GA=jcaUC2hJm@Jl;NHI{ z688A;o}haW8E$0^789{srr%T7J1db0|mJ>s^eBw2#{k6RIK#F z19E|l0C)fgRH9B3obv%*kk&B{n3w`P-4*2h&6V+L4OR)5 zVk^apo}bGF>{V&QTAiZo#(IGt%eSnXTg5^$EW87>NtQ$t`FPR&%Q|cAdxv(Hx$age zKKzrqX!;zIeP~RebWEWn$l^IX4D(i;xtFeAR{v>8T;&^5I*)OIN$yt?eJkYgN0TLy zrQ*-fIxR;SIR=`;vm* zevWJZ^0>(A1bi!EisjkITd!sr^CkzQv#$Y~uf-j}`%+gmKD*)csHn!W?&oqjMT4cR zw>g)BaAg#8K0)i9^OD}dw=R=u-C3HXMRQow$n_dm<$oeX+HrmY<{arI={dfJ>^FFP zYQMH`6KN>oIymT5IW|)O5gJ=tmOuQ|}dMVuyI>Dbs1Z2zO2n@8PAn*5&Z2qbV z>6|!2vItn+AAe~=)>!1K;6+YyCu$R*#|Iu{9UJjAM4vDhahYFs-q3!N^|j*CSFzmL zj&C{Q+2Y?1Dy`9@0#R2a8f2q+U_Cv&i48})uTEaepN?_GE*_8COM%{)oiC&ZvSIG( zdigzTQU{zJd~x|Pm|o_F3<`-MLz!+E)=^pwaZ=O7I|<)|-toM(I2N&G+aZ-NX)|WT z?Zpk)b>j^Har~WWSA?vwi@f*4*j>B`l~6!(AE5UYW6_{;`DlB$42uRbtX07j=PpRD z8SKVa-EVsBhE%5uA3!mZ?Qa#H>*Zvv;cj|ZQ=VE_4^HqyAAU{xx=}aFEV(vdcb^&` z7*F&!mu5YhYg=*Rvh~N7E*91)k^dB_AKw^GNDK+IM;R|@5bimj-9ZaW;mr2Bz?4o->FsHXFC3qqB zpf;Ldx$)?i`d&NYuzpL_*=tHCRVi-OquRpNz32{ijoXaG0p`;^Vz46kK zT*L6=>oS*Dcui(mN=Bi_9jso)CS#?*n26AF{X*`0$>fC^m0Z-)xE>eh^$}Y+p#JO< zSf?GXlKp3~P&<<0y$oMGBjX)Z`qdWD&XZ1E5ZO#LcQOpQ>tzIrI&GvVZ!eI%Za;72 zMXxXirMKtk5%@_L&4~2huC>@`T>Ade4M9=&%&|V1b5FBm%=x58enRZ6z%0wbh}riX z;3V{iT^;}!mfE^w#1A6Yvsw0sm0{2uE$?oEdET1)N;>$6g&#j5CTqe+EWl$z2OnZ& z3n?Yx4u7U3mH;spTZWN!PbtXqYpv3YtXzOBXlD6-gQPE;or_r$iCo22k!e)0jlo*| z27_&%60S#7G$%ovM_6$U+D}B@Tk07SIKYVHrNLiGRMjJ$QMcY^$GGM6zn<6U<938C z$)w7!N1nuuTg|wh)cekn%q1igIJCD`7y~AamfZR#nXe(r{klfvfWG+c=HX5tH*eWT zQ77^{1q&cCX4Hor@&ZiPwpzpvmAF>fHW~s8BI}ka-wrO)SN3eDG7nrzF-MX8gafL^(G~E zy+&1McUfr;3XJTsi;XEnz~wE>uAd*&8WaM(=-tldUrEl(xiagKyMVYFDrDCaG@zg7 zgiBlRya>Q57sA6KpoH`rBwX07GR`xYERy;JD#>K@E|zm=IBu-cPIvFI(s)b5-c*)8 zKfm5Ij?})-r()w^ZlK+ye~>wLIwn~NqBs~)*dQ<|Yj~9Ay7;i&l*5zb3Q{H9sAO@= z^!mG^PMiepk$CpP5)pGplxOH^T5e@S{^TqL57*}tH;T+^iQGTf3b4MLD)BR2Pd+e{ z0wh&r@a0Hlz+%!o5h$gOm*Y{e9((g96R@;4ALP$rocIZRjTGmg_)1L0dsN{B^A%-p zQ<$p%*7sF+m@Qz=W%q}{qu6ake9ua%VfIB$zN^7N!PuL0l|-G~$gY$OPB%xV*t&r4 zk0@E+#1jDZljffMNCPeNz|iqY=R6X?dHqp^ko@Aj9>jhH<5>>l%Ke+o{Mc5`%svJ7 zRMPWSi9EjJlgza{lI=%6wL9xqil+NXHkyNa+e7`kjI@;A%3j`YRUQm%n?n6rjxtX0 z(YSNKD(-}hHc;By>wBgfNiue?u%!x~{FvNW4BHvLWOstz|62kGX|8 z0ljg}@#5{VjhpXtHWPUjOxaUMO*n+p%$5@8z5cfIE;%AW|>j5V?uZzi4zdqH_7m`$(E z3wcg{RHf_F;+gq7qLr-k#+DReYb?*IaJvsy+^8LYWfh4`TEj~nafOck##)aHk`fhf zW(MmH7x#NCqhcCj(ZXZHrFE032jutxk1hy?5KQ;LH-KomKScT6%d{;y*c(oh{%9JKSnjL}q0Ory4D}J+ASN^RI>DNAv<%6ybh`Kx*K=p7|2Z_m?L8t)tKtodjN6YnU^jtjPEI9#y)g0pxA1!%+LcF{}j#wpUs7-@7&{Z|>};5yDba_~atU00;}B!@u(89a+R zilhmwhffIm2fTWv#ghj>zZRTA;O;WRfg9b-G#s@&9E?p_dMARHt-y=jRh{3m4y$+Sh>Z8p$nQXOn3@k?|4u84=m`wz8MiE$AuH~(pOs6^PwQO{=xb(7Y z#pHEdEkq?@X~6vKdo-xT50fpuQzapuoSyQbvAN4Ttddj+%@#?BaAit5sAC8^+*7{X z7FN+nbq@t}%EY2AAV@VjVwPEtXT|W^o4SCBxYM^U2Trr4h83rAQx-9SzRs9Z2>C0Y zGNNT3+%VsmvJ(`}SNV(I>IY@vJ@`V!Ff6rB=#5t%wZ}fdv*pp~X%OSJG0F%ognz^W zTig#!afyDq&eCnBWB9csxxP3s4u+p1Y1QA_b~}Q1lto>IhLGV(8WQ90*2f*@D&!5~InJ7Y zD+n?dC2arD09W{uj$4q48JwT~^wdQ8q^ zQ_fhg3Xs5CQ@IO453f%3UC01P=!~Szj&GfUTOE5S9|Ock*oeyr2u1LEKI;_6Dkuo4 zsfsdC>(EK+z(5jM-UuP&kyA^1O8xD8dB^sM>v}{NCinO}hc^u>QFys;Fl?GxyFQ8D z!3BF4>OJneG+G8t5#k4em|s%1@p_oL(8oV9vx2Fr6s(zS?CkB_$1?YYP#s&J*TI_Q zJML-S(`0YIy%88F&HALXHh4T`1}IOj`An;ld$6_uF6>C}qN> zT(7GWZFW*JzBL72^$5G#iD-&*)2~p^FB)Og0VM!&`{*EO;MF5@c#uXsn~RW@eV z_l@Vi2>%@^X~5^j9|f?eUH@n}USoGLY)m-m&S&<1WvW`wH1txj5JER3$+bWHpf>7s zboW(&f69Y#9Ew`z4AKBEDkB6mIS6QDu57<7)e)aB4*`OQp6fE%)wY{A5^(Z!YQ`=% z9)0^7jb~@dT(vh6PqhqvYdGog@nQUtIp*Fc5ymChTB@*hklNe#Pt0$mbAiDL=*FCR-b}U>0VzRTZM1lP;*WTA52(n#|j) z88jYpOIdPc_MeeH9LjS}X;@!3U56pOVThWQr(YIgZ_}z}FNs`54i2$;qq+pXyFy1S z3s3J|+zuABTpkt~8ytI(U)gf*C`kW&G=nBd;D<0wpZ#R{A&kjwD@o7{hb|+z;=a2e zxb$sds;0yH>yed7opn}GYnCN>mCHh9uD^St{h@TFKVoHV?(k@@wtU+G4G~5e+Qd?- zW#8n&sn1?@GQUb!Lg4GWRGdvVw0qK zp_sCxeY>K;O@FWN5v$X|9L~0oRzi)=%31eL739@#T^*v|OALq2LXS69S+s5!S5B6| z7CGr&_sdoye9kMo=azi&&G*lpOC|i~emYv>2MS&M5@xYOWfROz(Slg537YGQWNv`Fi zOm=g}BzzCBq2A0fCSJbs_K>F-rm1ln3JTHvIjdKPINq$fY>exw#`vmi1Kr1d@kN-x zmbYSo1hPSPKX$da((Wtba5q_oaGLV?yU56=Hg}d7K&W-ZWintHh zG|4hU&aYIhB~*wyR3^f_hrPBBcsnwdeG?Pxm&gkgGSBSS$Mfb@J$nx4J>r$x+EzJv zRxPlr^5_$RS?$CYujEVhp+IS5v;R^5zDJW8W-RkNMlW~S#BJbu(@PI~3RmPa&%^Q*5Hzi&uw_h%)G2N`(SW8aQ0(4qIWl^_!N4A=J4sB zhNu18yp5LwjX5^;Vp6WNbb~TCjTkerp=}n)mDWpNQtYGcA0HmN8v5#m?%#PtOR3~# z+~Bxw+b<*X&DHT-bz$t#HF0FFw~Lt=u1NayT4l+EoxAY~$kP2{bS>K<-_T(WCVyMy z;_iAtd>QG8H82ehTU^ZLLfeCaPUbEdap>NAL{Qaa!jj$jGZG6P3WoxVK~-|0{My(} zV%Vn&br_*4|7FS>+E_QTnseG%XN|Z{LN88;kPdy6+#sPkEN{kHjL?XL>^8Owh8-6~ z4~oA^b<`m?(;<$82(d3qR3GGWL_>|FFWUO6Eii|#Yrae9Z%(lvuzy_9zQgRb{(d#X zK4p9>ruV^G@V#;iEe*L}cm-VY2_pf_?qj2@JPu^H~dQf=si0N%%+`b>Ia(mEg zfrB5Z)ld>q2<XT8}hdSFO%ap@q&D{gkrK74R!rQ(_A z>S3+IdErcfp+Sthr{`Ab?dNuomi(iZV2EAWO(ncel%J6v4P`2>eska7UbGWML8E&utDoi^1teLVrv=Ac(55Muy@#@r zX1vrvnGifyyvc)OAvMp|iGr?KnwM08|KKnij=H7}V>4fR^oSjz^2ClsF6|)!I|QB< z!z!011YTK`8SyVOhnEJGA8BKw?I#J{QoE7Jx^)9^S%nb-lTU7)i}i^RD3FRsQijJq ze8NPYk6H@o(jX#^x{`+CrT_Yjh*+N^4V4=t(&2l+`YfiH2Zs$YfMIO|Y0iFy12BMws+$KX@m}gL|u9U9bSIFH@F)JqZFgBz(XEA}=*b zdmNDvoi^6uV$lp$o9g|d2+VYK>}v?ptGOs^96Hg}xD{DpfVJ4Ta#sAUw<7<$sFmwj z4bfVb$d1YY-I}2`(TU}^w+c|a@4hD5L_zBvDs9}sZ`lwEw0pn~aSGQL2?Gmje46Hx zASnSmlB?*hx{sXJiIcpz8v3?G=)`=pccHgph-`)(N0hy&`x=rLxg&gCcfR@$F2JJY z4V+5`-BCSwlES*bY4kTTr-Zlndbi=fVisGWZNRut9si#h*Ku{& z#5Tp42C^Z;85r5I7j|GnFLNA( zMCu@FM3X%hCnVyb(Z(7mC(bq=Qi*rmiH5qh@<4DRikC3v_>J?=e+uxxCOcmmdv@)s zQrBi${7Ggw%1nhEUU5$yx}DyYNp<)o{dsvS$X>d!H zY_HV(zPqi9|ACVVJ4A*KI2V%s4<0o zhKd+_@;2%58!+iFg=RMF5b`!AJ8>c6fg4HAZ;|Xn8Tc(unR<$ z>%8_^>|ww`3e0XS0&!%Pp?_hy*&!_Q^pSFNz$0`Xcab8bC($>;T6{rq5ixKI{>q$e z#;YA=im18JP6}baZFpR@C=<6}LqC;^I1h_;XeCsOw`ito&yaku#)Txq=;-(E~RM6hiGW{i3b-5$!Wn~Uwhul5tb%5!jAkx zp80wY&8Mo}*p@-N8XA;A8+hdI?LC8NdksEx_2{r=lMEkwny#&pg6~}%@$e`pTybDC zO&wN1!YAh=M*=RCnvzf6M~;v1RW$Ter;OlTXY_!=imYX4oS^l0jixodP@7syctjdz z>6jEFl>-|#9y@vVC_nyvk5$&R zxRs6KRp@0PB6i|U`>`5C#9!G#KJB_TezURAzTv?zl?cSOwCLcy?O1_P2`cf|1|M)f zyZ7}{5%NspZnE5Uv9Xsw7gtpQ7`L)!=5jrVcH8u9vM(Y8aKxvX<=}>K?Y&v1VbHr&Z8)8$ z;s^4NDBj?{8{j^{ZeJoq)ZUIQshQOaa4b5Z}*R(9Q>oXAH?jv!;{yM+ zOXl~WEhikC)N}Jv=i98AdcXw4s@=+|fcYcD2i!UPz#kF1_;X_Gz z$x(8h;BVvluQC!4{y*)#c|6qZ_dj0Joh2b<2~k2qqg1kMFC~W95S87~V#yX^l4Y2&jrDt8W~uJG_xt^MzyJC@9>4qk!^}O+>vf&$T<1Km zbDrm1uQ|-|Q)07ck35GBdFpscYIxEn>YDMLaBzn|=j%$E=1all)Wlh@LG*V`IoDng z{M&UcH-@}j+Cf^9Cj*wU_d@mlOT`C&LU%oS`tTSYIvf@bfBaQ9)AWD0o3fl|<1VmS zmJ=VM00oHb@aNZCUBVF*>z=|)jw?^_(u7dv@C-+l8N@$@APT+u8JGESm{M^Bwx;6q zA@(;AQHLmtX7Gw5cfSCp*KbhkL$|q5n6t=nx5t~npbekwaMS_uSteAeG2xlvD}zm8 zZ+BHWEup1VK@|FC0K4VWxM>8(z>-A3MU)?ZKVk?fex~Qt6az5GI;8VgEJUnU1@O#m z6H$YPf#L^1$k3-M$5+GHn+$}rKn=?Nvj6Llsof4o8;)v=pN6|u_eI}W1Qu#{_c;7t z()l3?np>X--0q2frf3Krn7ljF#$gjRhA#((HC2`IkA{hCNjkX$w&P9I712J{Ip1rO zgZ^Bjc=|~6tMSL3lTXVsEfxP3XDlWhT-Z%RcP}6Z+XY|?`eW)?xDZU=@NH8VVEQqo z)XWG-mu%Ls=pdNB-HRx7Gog4OyhylKW2D9BirCCm?jVU3ZPFHM6S(32mNda}h1j%w z(ai5qRnF~z>_WildrVYC`*}hL2G}TN8~DqmHPK@>`|g7cw%il zUE0+<>kq8uKsc2S^Wd-2avfX;<_qU7CQWiEj~%AxiW<#Gwm{T9H>u2XL*4IAHgEN% z8wlMg%#R?>@&4cieo6HG*KP_~xU?nPvU$Yy^1u);9OGOmu6Il8!(seBT}@G6N;rwO zuex-^J5?gtdwNaihtevmLPKMA?AJf{l;{#PQ!qCQH7K8kzdL31+ZS53fy5C7KI}B( z5=7SI@rlQ@S6tH(c-l2f{|O68w1B6xq}JVSu)Jq)^F!Ww!e1&1%RNu^cj=Pu5F5hx zOz}n}6*xtu_xmPt6GXBHf*Uvcye6eio}KJkOd6awsZg@ttwe`S*85->`-DR7tw&x3 zwe2NjsK*XJr+7tR<6CmW7x|OHEqYANAZ4~+8e>LQr>04lyb$xY%b5*KUbZM3|Cr*1 zvXGF=nFt-8JQ=oPGNS_1tUTabUai0MJOzW|Mo&&2T{5IkzHzPYElr_|{LjGL>i3^a zFNb^IT8iu1sf}NCyx*}G@}HsUpuxwaj^^D%>tWXx0F`eZZiS2}g+)i6S{v`ScXA{b z-IwhaU^K#f)Fu5x=@iFEJ!k9N&)SuA&3g-};ggIW&vD z^!rSpW*>A5-y#4>O4INL82!+jQ{`;%%KPWdDK>5VM?N`?B3xQdge*JHlcRt^@hK7P zJR6PgUX|?No5!jRS%J0;QTwOa=6cZaN7s>ri| zi9F&ruOZ=gi8Oy;gJ0?FbulH4BZ5lz262DOr{z_yg%GArOb#__bZ9o-vLmo3>tg$E zx+cs#3>h+2rB~fGMTKhBDrd40haE?c-g=M}#%M$f?Jtqt*6#HBOrEUfmh0n7$b zbtxpS?ApThGd=#(rDMi38(O_vxRz|4BzX+2eLUp)@rf(ILQFLm!%qK|Lv~acw^l|# z{3;E5S8YQj#IF!9Plpk8@jlvsFuCEjxBA zQ;-&^Xo;@@huWquxZym!b}xRUkbpx4+^)Q%Utt{jz>tmS8z4wyd}2OeiLO?~gjj!~ z4{_9`Y<_M>f%T26oh0sA<<^U{t8M1mio|%{C1?41H&KRBL3VPK>XLObRF31hH)&Cu z_|4l8MLo?~-m(Wq;vR8rHmis_jFU)&F0z-mWMPS|V2KM~h_dl)Jb*0m*ZWC{D~fWy z#kn*HXl(g%zfD6~qF{XRIOj5;7LWAoW?0TKnD4``t&ZG?<-8%-LhifyP$+gqQ8?z5 zgfZ-GNG5Udq$<1%MSN><2KRE~Hm|Xp!r(hnN6frGxXgU~`Z@JZ&geN1Y}2ow12DQ-%fExMnx24h5E+YR9l&?U{ib0Ik?jV4ZT z6487{R&F)BxdDLx(`Zj2fQo?t$`Z7j*L){V>jOf1tud00SVM!Z>z63J zu+>zhJcmB4&X(L@#_Ghu>g1L$Z{rrp*K(cK_eT zftrv?t4CSQtiInMaU#$z)ZD%4ThYu@^^)yTmaTp@BLWrzMi#c8c87)hPx^^5A=l8| z0CZYYZ?gSAAsE(#Lt&2@AR@unX;C$G3lD18)VyPe(%`D`$&b%V?tmiyH+Mo7Eqwms zdJ(8kmK0{U)rIBENyP^BEfW>9O7DF$N0=j*d39bV3<^8v^$N@;X0~^!_L>_9t~R>p z+qvjA4Pt19a!?z>Wdn=A!F%9UL6fi1Rsg<9)V;x_ zNlqkDCjv{`vvuZ;T{bs3M)=64<()QhAq{p8nWtj*w0pc$Dy>rLxM(m5MZ0sk(F}Wp zBoB4x$@0%95qn%xZsRS9ULnJ;dIgsj9i9wmVFYNAlJ*HU1l|8*CTY~3E}3?rDzk_K ztvs`*^H$KDT300!Oh>yHzp!~9i4hw4{0OD@e}VIP2yiB5OWi-r9>sTEOx>V$R}RE9 z#Wk$cx@+(M8XhukoFLrWs$%yoMIb=IYgWdlvEq13>LGAau6fw80vDo>*UIQMQgV;f_HzDxn268C1GYA|3ZLOmGh>S#jMyT%{rwbU)A<*% z=xIr`oioIsbh^q2=mHf)xI#j+QgtCQ&qk^3RLYIg=Vui`G^iY7FC`_+7*BoxuUCFj%5M|{BshZ=jd<7<4JDfcD<0EQUe+$yaNpj#BGUz2@7qNE zQNB&o`(aOBEsnrG(l-lU7qLbAtXxQ7dPsI8LWT&=q0dr zwxSsIpyT9EUqwnc<5Lv_S5<3WLb7a#!8gGkVSu}K@~9+QL^+yHH3b9Jgh&#MhF8D+ z^}0{GLp1Kc|6^2&o|JIHn$i*#BY2n6T7iLRL1Xtay6D>A_7TBnpFRh{Ov}s(owu=L zJIm5TaH#)zXsBcEKf8{FH4n7n=1(+&Fa)&wsWN`%v^Kt5;=&@@rgwKQ$a6J1vJ#@A zD7S(!k)svHVXn*bC*!eO9$Npw0y;~8{rwm(B#^4sX_lpR67F>4>gQegFjY79Z{s|- zu{B;C|L0xJsG>2w>=67Z7LB6$wIH}Haq&96BQQ*Lg<&bM4lxu@`131KVf_%7CcF+q zcB9pVCKb1?qs1$Fi2yqq4yuokqP}X;#>d9weY|_*_iUuv+hnmrQ6FxowjOCAQD}m~ zQ*|mN(5N|YsF>KDqDKsOH9&bj4er|Jpgeb9{Ud4JA?pLp0zuCuj(iN=ZH4g9cI7FZ zGWeL)NtmX0adxEbz*@?xDtI9BPa;gpk%P>)fNw!upJFSPJcyWdw;2jUmoW zd)!^6ZbPIK82f>b6TT7Y5DQ$6!}%%6c*_dom8V#$phU-35zM*<(ijJ=^3a3gd~koM zT@4qOOQLn^vewZY4F^Uc$s4{-0$%s=?EWsXk$OxweR4({-(`eAkP2M%|AoO3hzZb@ zTb?o>I4z{st=~CY|goEviQzga#AmskyezeZH_3*TL|W?6(2|P$%&V++OAX32ybt zV;jq3ps@MhtRR4yor5h4Yvt;ndj;Gd&~u&R6~@jY^{}iV8ta$P$;0hH9@@XjGJKCj zqF+N!dLk(EaZ5m2|MNY~YeCu#uLI49JH@uJDvp{k)=xVUFPhTCQMgd6r z?3yTifS_x4IW2*^KFH!-%z}7{8rkDlwczBUP0kJzo6 z{S&HF5}TZ~ARAxvC;%e(-4MYqO)!TD-VnS_)`J`?L&b&FEv8@pFEZ;c9lYQ%g(Ztw zv^5B@I#1c4sNZA*HOXl)B~7R4lVURf zX_No_bYBRxvvqmq5Rfw8`AKcQ+W7Y7mOG35=aE@K-t=(2o^3_cHGgGQpvByJdpFa4 zO$^Lg!&K!^CTP{(NBBh^E{%7~gy^x~gw${&?synRCBjvVh7>MMrIje_#)H`BcJX zg05)1baS2*4BaoTFWrx^Yypb>#L+TGSRI2Og?RSuzf@8lk7I{Y=!Oc)dGrJ9*q;*;Vub0#C>BLJ59rxO4xED`p<5skG{yi z04&Rk*ngo1;@!#&#{q3^{JZ_6FZAexac7zfZ-f~bt2|w*2sg1^;}~BCmD;CHD8{{h z`Eoaac$F$TNme)7A&ryK3jo(A*q$^#M&7>T;xcKPNQ^v-u8%&L!}~+>(LkT0K3Mmw zMdH$qI(gGs^k2mi$AAH}Q&lXz;2N@=a6S%~_M%>OCqt0pjZ678UMRep6CSdg$r-UJ z$zgwB(cJUnCS-8Vp72NE29T~NH)QiB%Tm|yX6&K6K7_gA3e%OYrE%uO<%REmur=)Y zrt~la)U18cPXM9Ve$9ks+wVcsw6g$fWtz2=Lub5eKJ7DGEABx&Dx z0;pgd@N7HN7SDtC^+Z0Q7(kn0vUb+4ekhWx%6h<@=LPt@{~Do&sTmqWex_^yu()yi zJs2N}IT$NNca43FDb_iH92pdksV-0f>{lW%KCzx2Nx?;oh=6&68i?q&tNJCOAbdo2 zS11QC?ga`phP$LrwjN=8R0kC;^q$M^)u|wdC7&7aBEL$*tOyi%R!48KdH*>!@N3dq zW5->_Kg_0F4@^#KCVJbdf?l z439WZX7OJr-F4qx1w-kUt`J>p>*~vw)o$;)la?q8&Xu{!LCB zzxFl!mLc8J#g+Y;$rQ1jo=kl5iJja+47LC(}TKI*k24^YVM0kK^QAUNi>`#w8EvLUMj z6CeBsU?*zc_g#xN(c+aXha<7qG{W{Pf%=_CTU$1O{Ne9YzdhH+U$d5HS~NVZ8;8n2 zC`xC61Rr<9U~2S2AO0~iSfuAtA`EuP{t#1(ae@4oBN2b*V?o0aM~A&744;}wF+ zGv?KaR1DZi+EZ`^=Y>Rt2|MMqjAg}v1w&YaQUIF&vi|7yxe>wDw4#A?I$Zg>Gs^7( zJ)x$XOgU^UQQjrBv@2ue6)Y-}zB!C%Z29~_mbzcIcj__hbTKj5t3)FtE0bYL`+OuB5Gc2z@W5K?0^v0QJR$7jPD*q#2TV(i3ta>9`t;cfqIjC1ex>in2di&A^1 zA4EdzpHkA8pRNoGj`Xp#!P@0fTs_!!RS>uQHw? z?{Kl)kSuSJqac^vdVUX!%#@nyVVIg4AERil4aMc(4wU03Ez;GmLL?-L8Cjg$w=Xd% z30Y$x(u8T9xq9zL3M-5Q?{EJVLl&Iup}fM!SS+~Fy`Z%me9}h^MrYA)bk#=EhMP(P zI&%r8>jvv&W2`ESq4Jc0)Kvw}&Mrf`!<-NNM%6Famm8DiP141$i)mWb3+$e(*Y)Mq zz<2Q4#5NX6VZC~9y?875Rt;$vQP|4@s$|`L&1bJ9dVu{-7}MwZ{r{TX$VtRrq5rd< z36i^J>V=2kJ34%gC*QnIW|Cl1WlKL!^e-yo+&SFyhW<5F8?3AfLadi6u>Jpe*n?4P$R zfS=Z$rTdSQw?F!(`kexgbPiiP|<#Mbe4Sr*pAnoC|@Y4_dV2?-}!Ogh5ysdz`t)p z!A90|GgO<`mzVCTjiQjg9Vfeun{1-0xVjosK+>A3f>A!`7?i$Yze^Hra859gITQMu zn{m+Zf$rDJ<@eFX*RC~Qpws?3K9EM6R+RqDTX1|EVT(2xZFBi5-&vX5JKi&zLMamS zb$h%0%IR@(RN*$P%hcvsza8!aO$tumLv5OE%Os=DH7}8+R`O?BrGo8Qgr|&HXyezs z@J@$n!?JH#jLeET*r^SAuez&1#a-omCG}+qAN4PLw2p?PH^d}+T)-SvlA)g0{$TnH z$Q#3_EjOWu!D|`NwGeUtZGShASBSx(g6`{9G|uup>h5o^uTTNgSYZf6ri zWFaZ_rzed}AOaGc^LkeI;X*{a^EsQSc}lN#D6tHOnqu3E z=Ck|Wbzbt(XqvY>Sr*EL@uBRnxnrA9|E<702$H5b-bH#4G#c|%+U@~wAp5G&G9{)x zGhA*J>oM6W{@Ar+RMXB62M8j!JtrneB1bhwo~)X5NQ7dWYAzQa*uLZ%iz6pxsVB4_ z%$P#YLaf=XzZ#{M^(c!BVvzO3*1SCxjdx=6W1yz!VAcC5(F9Vjvj&_%p+0NyRLMc#xde~S{eOU7aG@87B!f!y{w>rq??|?t2D;|I>VKcIX)isti zfa%*&l=r&Oyc)ml+0qEC{#zSC29*7MlX0 zp*uc)b75utfj#d~!tX*wTU;qt+>lecv5f1|x*>U?fi`4k+{*nFDKvX<ZFUOB2ifR;0Bn7HXcgtI?o^#01DRnE#mzf!Ti4G9<^FHfYF~z6)va(MKiP}{T zM}Vboaua@;g+r0<0ty0`@i*@~K*Bt=!`_B=`8(GqH4)z!{$aum0p}Z)(P8`{V#aW6R7aiKbZk0w_Gx zk}evT47!}Ql$@kFzx}z!$ftnBP({4EMfB9rAm_grM3zdfZS13O+(R&Yo3I%@QYbe^ zVz|vqJGu-yUQm{mPWXA6-n*&kJvrA|JgZ^w?Qp^s37*fL}%%_^+CKD7>KiMn(8`HhN6{4sk^yXCd#btE>ZaA3~@8-L6teQgBc=&;ll>R7UAmZQUW!ZNaFT#m`yST$+ekQ$t)Td;1 zQLcM=wXxS6rDWYQ-)#e9uP1hNW(~FZ7UqOxpD4PIs z-+2P{^#dew=C(g_^Rwe3+j=rdyB5>0k=57?rp4rb)o!S~p(I~zU4-6bX zYL1rM3^%gy+Ms*`hpK2=uEX$LJsbo)8xB^{cl`=vzdb%IDduIn3^|eE&{+)-bZ6=3 zkgq@+-*eVwV57eYeEwpC%?jEF&?yXyy9!%)RCZ_|a?oNVmcrED{k|4!q<2)ERL&YIW7=Ch#$+aB*)hD2JfN z2AP_vLz=ztb>S&x`q`7nMx-#|e!#*Vq=1yy?&Q;gb8c&?! zX^kjdB1RPgcAFnFbJHi#c48csVM%wk$)$m^R3u?pik{)!uXqnRk+*zhE{H=A>J70(t9 zi80*dr;_=C#jRn1c&Su}B&+hFJZnfM;@?ptdih`*?&pQ+48kpr1>4xS<`nBm8R~kr z*KCJ?2*Cx*46R#Oappbs6a{I5}wnkentFqRHN>M8r! z(P`btRS+c0OsXmF1IyWW+hUJ6t7Kx*U1V7s|Kw+w2hTy|yts?!e)?6~tC4JdIHEFs z?8L$m)*M<1V~3jz_-43=Gt(d=q5SL0*gPh)i(QJsbzc)@gl+)Uu+1#h2{{>9(HC{W z9SImBnHj{vV=&$+$3S|!?j|0=?>gYBOPlVXo_1HMS(nKo?A{t%R{~C%jfJmdJy@Ek4u@?N4aLe(Vyab(iZT?S0(FV9IYVVOi zdM;tl0~C|YqhWfT0{L?cImhNh;E<)Q!zz2T_RwuX0y7 z9m4y+)GwN;s_G1!M0kEU%2>e|5{++G5n{}$xEw~fb;Dv818lXdx4|d%XL7SD;8iLK z{k}N@E4j;znH!290^`KJ5Q?FB4#3ap!_CyH8}TB`8p0&(|UvnQhK2vvdDpC zrVH_yK=qd*$FmX0KH1*xXEvaT<-+&RtEWV! z0km~EI;h?OhS^@22~T^$ZbKASNiaXyfYjarzZ;RGIJ~NjDRAmqs^5aS2n=Ut>hvy< z*GNBdEL`Z|KKCdN=o*d(`gE0w6!D+FmFIR?T~qzc5Wht3XSP8ubr4- zHxKZ%za&F9{o#WvJfipbVUgvly)gHGO``>&cag}>1Q2n(RG6`aZgLM4jjEJdJ|BJD zoqXf-e&+bRu$JqWzdgwO;1I}<3ndKGO3AfuvXbc33Kapy1hsf!Twck>A4as1Ru-u| z4Hw45`^h8$y;yRFmuW_O0pPFxVdu#F;I}y*ZUE5iDrl1Qk$%d)?$s6sf{pM2_v9+`*vjH;j|7-|%m@kgL?lX>IEz(0NYCiIg2CC6;+ z9*w7OqfF({-q1_0W_VB_HX~*56jBLsVix@v`x5eH{{dL8<^uzlB*C(^FFTUABH;6r z!Ty*Uw8w&=#;Hl4$lxVCu})6X(DSt4NrJJ`j0IT$s8bN&S81GBCU*#~S$QfvO8@8m z_t!c8Cm{Gq4_e~hV36ays@8{KayaqWg6qgm`okI+=(vCZ^n!9G7A8qOZQcUs8Z-4< z7@em->tQiVGuZRXu%^X_p_4X(wYl5&Hdg0oM>7)|eir#ZdD+iOD+dpa@_DnuWFeX^ zmqI;2I&f;~y#3EpgVi_>g7#cAlSjiO@y!kHTant>4rZYE^Ezvdg8~L5+>3F5wvxMc zcnLwF<%5U&Z&w3p`Toa*VOjp22_F`#`@-x6VD2HbH~&92Xa9#w`Q^=4%}stuEG_hl zZuAJvM8Dea`FfMAhuh-=b8}~MhzZjrNPX*YgWoxXZmEAh@$tBBKt3rKCFl7?*JgIC zp>OJWyvTFM#D?WznsWq~x0qB>g<2_Lj9^0cdskJVa|RRL?g{#$?aWh7e*hjKZ2&*< zL5W1>qFC?rM_ZoL**Vn|UZRVdHf*saSk0N*d@#n#iMS89+f+JO8M2YnxYb2gOjnO| z5{q)Sw)&Q}Eg7Qv#X5^*c~1+M_=;*+?ms_=EH-^9_DK%p{`H3>yz?LC*+G&7CY3GX}oMJz;A`7C#-L#q9uJYWq~o?*N8u-7cI5eJuB0 ztL~uxv$deVbRu?><8*BsrvNr2%09wXz=!w>&d)SueKN6A#Rfm^5`- zdjE;G@HK1^GuB6BJGMKEjXwC@bs$q9>tHm!%0p|UrNesqt76^*33U^z49R#G7IIHsm?Rq$)_!tK2?^VIA<<=6 zT0`yobR_=X07b6Qx4)*)V_@d>E>3(yrfn29H$JKV5}bIa4Rs$sRh4!`P zKsrb?Wv;Qwu`gGp=T=9ey{_!iAD=1yIQ+N~Bb0z8SGWvO1%gNx!=+jMW<^OP-3heP z7bkp!TYdrQfm0)X#umkOyra;^6WjTM}t zk-h_HeSpnG$B!kh=m|{Y$?bmn(+3z^PUNZ_^0QoY&ZF57ga(Q)bcy7K z?6zS_dhKV2aX| zfj?l3{DiToTDho4J0E`X(TCE;qtm^?8MNu}F1BPWhPVQ9irD{Om5UE^VCQneOKlSG zY+3Zk@S?g4DYNqq!Qyl=(Ju#MW#aKbzGV?%zPG}rCA$z$qLi&Q7X;Lb%C>Z!d@aTu zggyVI#mBtRhPVJtE`tcWyLq~PV2)5}MRHOd;NLvk+_Pld(Pyb$fAY?#nX^7UJ)iqA zyk_%6oMuWkZ(3p3w5aA@+r*^p<+_`v4L$qEmlSPv%UN6{M?J5qTlPASQBSF}2H(3< z>M^Ph2)F-jPuFYMzY{na+qS>t(iQ4mIWFxUD52r8(3G@q1>aPcbhtaTQ3!q-ya}^2 zTrG==&b_Nn{}759oSw<+Xj`=-_N8UZcFls}=jk>5R*GLJ55lO&c6L2MWm!&7mKKrb zrwA__&A+~l&GgavhLh~Um0~Y+l$ILn&hBr)`XzYym~=K*d5nrj_#`y&bj-GgmE$_WZI@SZrU2y2b!G)k*b) z97Qc;nMq#v^v7LdoXdu?jgkG*rj?^VtH$(nMO2HI3dbiMdidxc2qEd3xkWpDZNOw~ zHVLeNOormf@L#qI>lG%Z=DA{v+^4h5%`wH9?SGojP_AAHTY;WA=FL0#M#AG&oA)fq zc|}^nd#gx!>ewCIG$q?ukHF;AT;F0j#ZcvK*b8an9cL$A%!aa|m3!i{a&_ZI`iAeR zKXxw>tklqT2qsyMejUE$vx0Nk!1TRg{KOErP2B1!yX3t8Qm^}ET}Wa>+zW_NRF#@p zOYp-wd)D~P7R^b{6Tb!p9w+SdDCSS;;F^e{7CE$xN1Bt^Vhg5poDvhgDz6VG#Lv%q z&sJJt(!xYn9#y4iK04(P=GI`P*t%^ZX-WIGnf^W8L1Q~UpRemU>+vy(n{C-Z2JwuL ztg@~{eNv*YirU!653`u#o0UyINurynqf`HlQS6s+ArwvoH5T@@*|Zld`|R8@rcPo@ z^66<>oT%I4)8R7YCBQZ57(vuTwfYeaohCZmCd5h(D>dg0Qcwi7tyt@9j|m^E@rwYZ zXPeG`pI?44(zMJ^6374{P}{a zCA_9wnIPc~G(*y5tu0^|TcnM-pu3;q$+U}At8>2AdY2esn!M_iCe1L!whL{#rJU&D znZ;Fw9#id|Ekr-RVmoMXz+Wh_D_Q2@r`xf{Hm%W&!T`D}TOEhryY1=cD(!^l22xVT z(EY>;=|qmA{E=SXLuIIZN5pq!l?g7fVOfYBNc{1Yg()1p6Oz7>GdkHA<*1us#ZE;< zvW}Eq%RiuGmo(EEU>D$IH4&4wghah{rBnz#KYodDVA?I-4m+5mFxECdH|w_`U2Xml zmvF&}*wE6cRy*$H(>~ZR{B+P%W&I-ow4>fzuRzuEGl%i5wI9xvJ2N{LHUj4bhc-*- zzI6K>RuV0At$~46-SP^@UQeWC6s>Y^N}cuS%QP6DePdS|g+Fg3T*nvosz}s>=0Go< zS~5E{h!4|evoHhG1p{OJ823RprND^-(#g&t?;+u>)t`+0I`5*7=XJ*hK9GO=OogBq z^dzr4WLC{a=5pJ|6qd5rzM z`o)kS4MvZJd!XOk;J5-biZ4z?LzyhSr@!Nq`ydX(9$DCqi&ZC294NhaKtupPT`0D~ z^W=-MhPIEE?rfws+aLw6e$kfR>32ekrAJlqnIuYmB-Sv&E`3?jcN}V*GCGP1uVG}m z|E|5({~oKa-?XJozY^E%>Uhn(Z~Zp?DOrO2!=o8^QH|01D=n$H6Irq)&yrKOexV={ zP_!Uk9M{MEu)e^PyRcK4mxoLUQ*?i~PM;#pI zjv8l+i785RW|+(JvrLf3hgDf&-Yk(j_nH`{sw^^oN60CAW-x4C7-~G&4kCEacMV;D zlL4ZTPJ1f-SQ;FBfX24JwMRT%$`HJQ6CN1#p))=oyAeAd6qOnO$Z&XeASk0$c1Lr1 zowC^E*TfRD{#oOe%tTFcxJ^fxk99GT)I`1F<6hjFWR&b>KaD$PARb2lT~SF`;f zx;}T5^tt6kvd5ntwS_jt7yDE4k4AgIS^cL)=ljx$%O1N&xI*G^_9nDJg7fEco)b+?mrk@R56Z)dw%fNa2GrSt4MPyEYzlU^7A>H@A=uJ zqWK(@ue=Dh>4j5&;b4tpgL;c~rB46KMFjYLFx_qx$ieoNww3fkp@N+V4l);*)OQlxMV1p9uylTDME}bR(c0JWCArspZUlN{;_bq5s(BOer_6 zt?)oCvr!~XFK~_GzA?EU-9ItrGtrus<)cG?!o#t zQm3%vgRKOzstT&H z35kU>86{a5Z=Y!8ufgdh8BwTJNAC1z))pbzi}6hF=Uo$Xq!j6(QtoC|$MI*zs}{%s zHG=gt_jqqhHbGg&v1m|ORmkIw_jy%9@G|$s3~OA-W$)evorvGA3WSoRmkKlPytr<- zJ6tya>D|nk3ydv2|22wBxCna%$-@F)oGf$ScGR?A-t5&zYr$H}&W z&wi9wmzgNE}R$`E3lzx7rr9f!gnIr4iB*#IKyr&4x9w03&CoJ1U|R8REk zFH|f;2-<@R+Oud6E|A0f!wE^-6YCkl14B~_*FIc0pP=m82zHZfu2wn==X?Xd|EOL3 zOtXHw1zJ5YoTUSgjfk>1HNpk;V-z1BvkLI@wNihVu z2o-{U-U6p^C^bn6m|~&-va9ON-k;~hl;a@nKt*pVoE$aI_6(esfrD+IZZUNF%Qbbh z=O{SE(w?i(C2Ue5fR>W_{p6m(qzu9pJ!l6k6kDOW0FI&jewEikzIu@r)6xwJso2Ng zQ4I0R)-7;K@R#9f>H0CK5Il;B%k2jn|0JK#31{z??{VAkyRWj1B4-jLJX@roneg!D zmy>@Ok>5sLz53h9-K3oW`NxF+F=4tbY5iluG*9vW2Zia-`VR`zH;#W$m`)!56G(iS z&$e5p#Kj6f#waSXmxX=j%9Y}LxIJo~>(`lQD-{~gjMRRrPNStN{?9Iz8@I*f8gm9? zliw;~Nt(AXrG)WAzuD&jdR%bDEmgDQa~C!H+z2b9*NKyBn)Y1exP>bGsnL=V^dL|r zE-g;(F*^Na3F}_`61}wX@bw*^b_{tl)1;p_ewWls(b7PS)wrv(~tUO5Y&C zJP`S_z}6CrZ($zTPTdY#x)&eRH}f^^QaO?TXEB*I3N7nLm&&t`wU{4=uRe`suaP7N zMA{nsHtum07biQV?@b(jb*X$}+dVNJYU^(!W>&W6t9YnJ_TCP#E=q2Sy}u3R=$8%$ zfo$7;FB`3@^#YFadzt+dX;`m2jDi<)1KKEO@p-(Cp>&%?gZ{BDf(drJ7ug>|6J?$zS ze7a~6d(@%5y9^jIv+mVU`i-`jHx`!?C%@%CZSmXHrW@4yL}6xB|C={Fd_2?>oQlj2 z#@`vlEdtli*2x^6{w5gw;6;EC{joQ{83Ue81P3ITllsk@e@uj__kUv|OvU|U7EBxb z2RVLV$v + +# Iceberg AWS Integrations + +Iceberg provides integration with different AWS services through the `iceberg-aws` module. +This section describes how to use Iceberg with AWS. + +## Enabling AWS Integration + +The `iceberg-aws` module is bundled with Spark and Flink engine runtimes for all versions from `0.11.0` onwards. +However, the AWS clients are not bundled so that you can use the same client version as your application. +You will need to provide the AWS v2 SDK because that is what Iceberg depends on. +You can choose to use the [AWS SDK bundle](https://mvnrepository.com/artifact/software.amazon.awssdk/bundle), +or individual AWS client packages (Glue, S3, DynamoDB, KMS, STS) if you would like to have a minimal dependency footprint. + +All the default AWS clients use the [URL Connection HTTP Client](https://mvnrepository.com/artifact/software.amazon.awssdk/url-connection-client) +for HTTP connection management. +This dependency is not part of the AWS SDK bundle and needs to be added separately. +To choose a different HTTP client library such as [Apache HTTP Client](https://mvnrepository.com/artifact/software.amazon.awssdk/apache-client), +see the section [client customization](#aws-client-customization) for more details. + +All the AWS module features can be loaded through custom catalog properties, +you can go to the documentations of each engine to see how to load a custom catalog. +Here are some examples. + +### Spark + +For example, to use AWS features with Spark 3.3 (with scala 2.12) and AWS clients version 2.20.18, you can start the Spark SQL shell with: + +```sh +# add Iceberg dependency +ICEBERG_VERSION={{ icebergVersion }} +DEPENDENCIES="org.apache.iceberg:iceberg-spark-runtime-3.3_2.12:$ICEBERG_VERSION" + +# add AWS dependency +AWS_SDK_VERSION=2.20.18 +AWS_MAVEN_GROUP=software.amazon.awssdk +AWS_PACKAGES=( + "bundle" +) +for pkg in "${AWS_PACKAGES[@]}"; do + DEPENDENCIES+=",$AWS_MAVEN_GROUP:$pkg:$AWS_SDK_VERSION" +done + +# start Spark SQL client shell +spark-sql --packages $DEPENDENCIES \ + --conf spark.sql.defaultCatalog=my_catalog \ + --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket/my/key/prefix \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO +``` + +As you can see, In the shell command, we use `--packages` to specify the additional AWS bundle and HTTP client dependencies with their version as `2.20.18`. + +### Flink + +To use AWS module with Flink, you can download the necessary dependencies and specify them when starting the Flink SQL client: + +```sh +# download Iceberg dependency +ICEBERG_VERSION={{ icebergVersion }} +MAVEN_URL=https://repo1.maven.org/maven2 +ICEBERG_MAVEN_URL=$MAVEN_URL/org/apache/iceberg +wget $ICEBERG_MAVEN_URL/iceberg-flink-runtime/$ICEBERG_VERSION/iceberg-flink-runtime-$ICEBERG_VERSION.jar + +# download AWS dependency +AWS_SDK_VERSION=2.20.18 +AWS_MAVEN_URL=$MAVEN_URL/software/amazon/awssdk +AWS_PACKAGES=( + "bundle" +) +for pkg in "${AWS_PACKAGES[@]}"; do + wget $AWS_MAVEN_URL/$pkg/$AWS_SDK_VERSION/$pkg-$AWS_SDK_VERSION.jar +done + +# start Flink SQL client shell +/path/to/bin/sql-client.sh embedded \ + -j iceberg-flink-runtime-$ICEBERG_VERSION.jar \ + -j bundle-$AWS_SDK_VERSION.jar \ + shell +``` + +With those dependencies, you can create a Flink catalog like the following: + +```sql +CREATE CATALOG my_catalog WITH ( + 'type'='iceberg', + 'warehouse'='s3://my-bucket/my/key/prefix', + 'catalog-impl'='org.apache.iceberg.aws.glue.GlueCatalog', + 'io-impl'='org.apache.iceberg.aws.s3.S3FileIO' +); +``` + +You can also specify the catalog configurations in `sql-client-defaults.yaml` to preload it: + +```yaml +catalogs: + - name: my_catalog + type: iceberg + warehouse: s3://my-bucket/my/key/prefix + catalog-impl: org.apache.iceberg.aws.glue.GlueCatalog + io-impl: org.apache.iceberg.aws.s3.S3FileIO +``` + +### Hive + +To use AWS module with Hive, you can download the necessary dependencies similar to the Flink example, +and then add them to the Hive classpath or add the jars at runtime in CLI: + +``` +add jar /my/path/to/iceberg-hive-runtime.jar; +add jar /my/path/to/aws/bundle.jar; +``` + +With those dependencies, you can register a Glue catalog and create external tables in Hive at runtime in CLI by: + +```sql +SET iceberg.engine.hive.enabled=true; +SET hive.vectorized.execution.enabled=false; +SET iceberg.catalog.glue.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog; +SET iceberg.catalog.glue.warehouse=s3://my-bucket/my/key/prefix; + +-- suppose you have an Iceberg table database_a.table_a created by GlueCatalog +CREATE EXTERNAL TABLE database_a.table_a +STORED BY 'org.apache.iceberg.mr.hive.HiveIcebergStorageHandler' +TBLPROPERTIES ('iceberg.catalog'='glue'); +``` + +You can also preload the catalog by setting the configurations above in `hive-site.xml`. + +## Catalogs + +There are multiple different options that users can choose to build an Iceberg catalog with AWS. + +### Glue Catalog + +Iceberg enables the use of [AWS Glue](https://aws.amazon.com/glue) as the `Catalog` implementation. +When used, an Iceberg namespace is stored as a [Glue Database](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-databases.html), +an Iceberg table is stored as a [Glue Table](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-tables.html), +and every Iceberg table version is stored as a [Glue TableVersion](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-catalog-tables.html#aws-glue-api-catalog-tables-TableVersion). +You can start using Glue catalog by specifying the `catalog-impl` as `org.apache.iceberg.aws.glue.GlueCatalog`, +just like what is shown in the [enabling AWS integration](#enabling-aws-integration) section above. +More details about loading the catalog can be found in individual engine pages, such as [Spark](spark-configuration.md#loading-a-custom-catalog) and [Flink](flink.md#adding-catalogs). + +#### Glue Catalog ID + +There is a unique Glue metastore in each AWS account and each AWS region. +By default, `GlueCatalog` chooses the Glue metastore to use based on the user's default AWS client credential and region setup. +You can specify the Glue catalog ID through `glue.id` catalog property to point to a Glue catalog in a different AWS account. +The Glue catalog ID is your numeric AWS account ID. +If the Glue catalog is in a different region, you should configure your AWS client to point to the correct region, +see more details in [AWS client customization](#aws-client-customization). + +#### Skip Archive + +AWS Glue has the ability to archive older table versions and a user can roll back the table to any historical version if needed. +By default, the Iceberg Glue Catalog will skip the archival of older table versions. +If a user wishes to archive older table versions, they can set `glue.skip-archive` to false. +Do note for streaming ingestion into Iceberg tables, setting `glue.skip-archive` to false will quickly create a lot of Glue table versions. +For more details, please read [Glue Quotas](https://docs.aws.amazon.com/general/latest/gr/glue.html) and the [UpdateTable API](https://docs.aws.amazon.com/glue/latest/webapi/API_UpdateTable.html). + +#### Skip Name Validation + +Allow user to skip name validation for table name and namespaces. +It is recommended to stick to Glue best practice in +https://docs.aws.amazon.com/athena/latest/ug/glue-best-practices.html to make sure operations are Hive compatible. +This is only added for users that have existing conventions using non-standard characters. When database name +and table name validation are skipped, there is no guarantee that downstream systems would all support the names. + +#### Optimistic Locking + +By default, Iceberg uses Glue's optimistic locking for concurrent updates to a table. +With optimistic locking, each table has a version id. +If users retrieve the table metadata, Iceberg records the version id of that table. +Users can update the table as long as the version ID on the server side remains unchanged. +Version mismatch occurs if someone else modified the table before you did, causing an update failure. +Iceberg then refreshes metadata and checks if there is a conflict. +If there is no commit conflict, the operation will be retried. +Optimistic locking guarantees atomic transaction of Iceberg tables in Glue. +It also prevents others from accidentally overwriting your changes. + +!!! info + Please use AWS SDK version >= 2.17.131 to leverage Glue's Optimistic Locking. + If the AWS SDK version is below 2.17.131, only in-memory lock is used. To ensure atomic transaction, you need to set up a [DynamoDb Lock Manager](#dynamodb-lock-manager). + + +#### Warehouse Location + +Similar to all other catalog implementations, `warehouse` is a required catalog property to determine the root path of the data warehouse in storage. +By default, Glue only allows a warehouse location in S3 because of the use of `S3FileIO`. +To store data in a different local or cloud store, Glue catalog can switch to use `HadoopFileIO` or any custom FileIO by setting the `io-impl` catalog property. +Details about this feature can be found in the [custom FileIO](custom-catalog.md#custom-file-io-implementation) section. + +#### Table Location + +By default, the root location for a table `my_table` of namespace `my_ns` is at `my-warehouse-location/my-ns.db/my-table`. +This default root location can be changed at both namespace and table level. + +To use a different path prefix for all tables under a namespace, use AWS console or any AWS Glue client SDK you like to update the `locationUri` attribute of the corresponding Glue database. +For example, you can update the `locationUri` of `my_ns` to `s3://my-ns-bucket`, +then any newly created table will have a default root location under the new prefix. +For instance, a new table `my_table_2` will have its root location at `s3://my-ns-bucket/my_table_2`. + +To use a completely different root path for a specific table, set the `location` table property to the desired root path value you want. +For example, in Spark SQL you can do: + +```sql +CREATE TABLE my_catalog.my_ns.my_table ( + id bigint, + data string, + category string) +USING iceberg +OPTIONS ('location'='s3://my-special-table-bucket') +PARTITIONED BY (category); +``` + +For engines like Spark that support the `LOCATION` keyword, the above SQL statement is equivalent to: + +```sql +CREATE TABLE my_catalog.my_ns.my_table ( + id bigint, + data string, + category string) +USING iceberg +LOCATION 's3://my-special-table-bucket' +PARTITIONED BY (category); +``` + +### DynamoDB Catalog + +Iceberg supports using a [DynamoDB](https://aws.amazon.com/dynamodb) table to record and manage database and table information. + +#### Configurations + +The DynamoDB catalog supports the following configurations: + +| Property | Default | Description | +| --------------------------------- | -------------------------------------------------- | ------------------------------------------------------ | +| dynamodb.table-name | iceberg | name of the DynamoDB table used by DynamoDbCatalog | + + +#### Internal Table Design + +The DynamoDB table is designed with the following columns: + +| Column | Key | Type | Description | +| ----------------- | --------------- | ----------- |--------------------------------------------------------------------- | +| identifier | partition key | string | table identifier such as `db1.table1`, or string `NAMESPACE` for namespaces | +| namespace | sort key | string | namespace name. A [global secondary index (GSI)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html) is created with namespace as partition key, identifier as sort key, no other projected columns | +| v | | string | row version, used for optimistic locking | +| updated_at | | number | timestamp (millis) of the last update | +| created_at | | number | timestamp (millis) of the table creation | +| p. | | string | Iceberg-defined table properties including `table_type`, `metadata_location` and `previous_metadata_location` or namespace properties + +This design has the following benefits: + +1. it avoids potential [hot partition issue](https://aws.amazon.com/premiumsupport/knowledge-center/dynamodb-table-throttled/) if there are heavy write traffic to the tables within the same namespace because the partition key is at the table level +2. namespace operations are clustered in a single partition to avoid affecting table commit operations +3. a sort key to partition key reverse GSI is used for list table operation, and all other operations are single row ops or single partition query. No full table scan is needed for any operation in the catalog. +4. a string UUID version field `v` is used instead of `updated_at` to avoid 2 processes committing at the same millisecond +5. multi-row transaction is used for `catalog.renameTable` to ensure idempotency +6. properties are flattened as top level columns so that user can add custom GSI on any property field to customize the catalog. For example, users can store owner information as table property `owner`, and search tables by owner by adding a GSI on the `p.owner` column. + +### RDS JDBC Catalog + +Iceberg also supports the JDBC catalog which uses a table in a relational database to manage Iceberg tables. +You can configure to use the JDBC catalog with relational database services like [AWS RDS](https://aws.amazon.com/rds). +Read [the JDBC integration page](jdbc.md#jdbc-catalog) for guides and examples about using the JDBC catalog. +Read [this AWS documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.Java.html) for more details about configuring the JDBC catalog with IAM authentication. + +### Which catalog to choose? + +With all the available options, we offer the following guidelines when choosing the right catalog to use for your application: + +1. if your organization has an existing Glue metastore or plans to use the AWS analytics ecosystem including Glue, [Athena](https://aws.amazon.com/athena), [EMR](https://aws.amazon.com/emr), [Redshift](https://aws.amazon.com/redshift) and [LakeFormation](https://aws.amazon.com/lake-formation), Glue catalog provides the easiest integration. +2. if your application requires frequent updates to table or high read and write throughput (e.g. streaming write), Glue and DynamoDB catalog provides the best performance through optimistic locking. +3. if you would like to enforce access control for tables in a catalog, Glue tables can be managed as an [IAM resource](https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsglue.html), whereas DynamoDB catalog tables can only be managed through [item-level permission](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html) which is much more complicated. +4. if you would like to query tables based on table property information without the need to scan the entire catalog, DynamoDB catalog allows you to build secondary indexes for any arbitrary property field and provide efficient query performance. +5. if you would like to have the benefit of DynamoDB catalog while also connect to Glue, you can enable [DynamoDB stream with Lambda trigger](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.Tutorial.html) to asynchronously update your Glue metastore with table information in the DynamoDB catalog. +6. if your organization already maintains an existing relational database in RDS or uses [serverless Aurora](https://aws.amazon.com/rds/aurora/serverless/) to manage tables, the JDBC catalog provides the easiest integration. + +## DynamoDb Lock Manager + +[Amazon DynamoDB](https://aws.amazon.com/dynamodb) can be used by `HadoopCatalog` or `HadoopTables` so that for every commit, +the catalog first obtains a lock using a helper DynamoDB table and then try to safely modify the Iceberg table. +This is necessary for a file system-based catalog to ensure atomic transaction in storages like S3 that do not provide file write mutual exclusion. + +This feature requires the following lock related catalog properties: + +1. Set `lock-impl` as `org.apache.iceberg.aws.dynamodb.DynamoDbLockManager`. +2. Set `lock.table` as the DynamoDB table name you would like to use. If the lock table with the given name does not exist in DynamoDB, a new table is created with billing mode set as [pay-per-request](https://aws.amazon.com/blogs/aws/amazon-dynamodb-on-demand-no-capacity-planning-and-pay-per-request-pricing). + +Other lock related catalog properties can also be used to adjust locking behaviors such as heartbeat interval. +For more details, please refer to [Lock catalog properties](configuration.md#lock-catalog-properties). + + +## S3 FileIO + +Iceberg allows users to write data to S3 through `S3FileIO`. +`GlueCatalog` by default uses this `FileIO`, and other catalogs can load this `FileIO` using the `io-impl` catalog property. + +### Progressive Multipart Upload + +`S3FileIO` implements a customized progressive multipart upload algorithm to upload data. +Data files are uploaded by parts in parallel as soon as each part is ready, +and each file part is deleted as soon as its upload process completes. +This provides maximized upload speed and minimized local disk usage during uploads. +Here are the configurations that users can tune related to this feature: + +| Property | Default | Description | +| --------------------------------- | -------------------------------------------------- | ------------------------------------------------------ | +| s3.multipart.num-threads | the available number of processors in the system | number of threads to use for uploading parts to S3 (shared across all output streams) | +| s3.multipart.part-size-bytes | 32MB | the size of a single part for multipart upload requests | +| s3.multipart.threshold | 1.5 | the threshold expressed as a factor times the multipart size at which to switch from uploading using a single put object request to uploading using multipart upload | +| s3.staging-dir | `java.io.tmpdir` property value | the directory to hold temporary files | + +### S3 Server Side Encryption + +`S3FileIO` supports all 3 S3 server side encryption modes: + +* [SSE-S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html): When you use Server-Side Encryption with Amazon S3-Managed Keys (SSE-S3), each object is encrypted with a unique key. As an additional safeguard, it encrypts the key itself with a master key that it regularly rotates. Amazon S3 server-side encryption uses one of the strongest block ciphers available, 256-bit Advanced Encryption Standard (AES-256), to encrypt your data. +* [SSE-KMS](https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html): Server-Side Encryption with Customer Master Keys (CMKs) Stored in AWS Key Management Service (SSE-KMS) is similar to SSE-S3, but with some additional benefits and charges for using this service. There are separate permissions for the use of a CMK that provides added protection against unauthorized access of your objects in Amazon S3. SSE-KMS also provides you with an audit trail that shows when your CMK was used and by whom. Additionally, you can create and manage customer managed CMKs or use AWS managed CMKs that are unique to you, your service, and your Region. +* [SSE-C](https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html): With Server-Side Encryption with Customer-Provided Keys (SSE-C), you manage the encryption keys and Amazon S3 manages the encryption, as it writes to disks, and decryption when you access your objects. + +To enable server side encryption, use the following configuration properties: + +| Property | Default | Description | +| --------------------------------- | ---------------------------------------- | ------------------------------------------------------ | +| s3.sse.type | `none` | `none`, `s3`, `kms` or `custom` | +| s3.sse.key | `aws/s3` for `kms` type, null otherwise | A KMS Key ID or ARN for `kms` type, or a custom base-64 AES256 symmetric key for `custom` type. | +| s3.sse.md5 | null | If SSE type is `custom`, this value must be set as the base-64 MD5 digest of the symmetric key to ensure integrity. | + +### S3 Access Control List + +`S3FileIO` supports S3 access control list (ACL) for detailed access control. +User can choose the ACL level by setting the `s3.acl` property. +For more details, please read [S3 ACL Documentation](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html). + +### Object Store File Layout + +S3 and many other cloud storage services [throttle requests based on object prefix](https://aws.amazon.com/premiumsupport/knowledge-center/s3-request-limit-avoid-throttling/). +Data stored in S3 with a traditional Hive storage layout can face S3 request throttling as objects are stored under the same file path prefix. + +Iceberg by default uses the Hive storage layout but can be switched to use the `ObjectStoreLocationProvider`. +With `ObjectStoreLocationProvider`, a deterministic hash is generated for each stored file, with the hash appended +directly after the `write.data.path`. This ensures files written to s3 are equally distributed across multiple [prefixes](https://aws.amazon.com/premiumsupport/knowledge-center/s3-object-key-naming-pattern/) in the S3 bucket. Resulting in minimized throttling and maximized throughput for S3-related IO operations. When using `ObjectStoreLocationProvider` having a shared and short `write.data.path` across your Iceberg tables will improve performance. + +For more information on how S3 scales API QPS, check out the 2018 re:Invent session on [Best Practices for Amazon S3 and Amazon S3 Glacier](https://youtu.be/rHeTn9pHNKo?t=3219). At [53:39](https://youtu.be/rHeTn9pHNKo?t=3219) it covers how S3 scales/partitions & at [54:50](https://youtu.be/rHeTn9pHNKo?t=3290) it discusses the 30-60 minute wait time before new partitions are created. + +To use the `ObjectStorageLocationProvider` add `'write.object-storage.enabled'=true` in the table's properties. +Below is an example Spark SQL command to create a table using the `ObjectStorageLocationProvider`: +```sql +CREATE TABLE my_catalog.my_ns.my_table ( + id bigint, + data string, + category string) +USING iceberg +OPTIONS ( + 'write.object-storage.enabled'=true, + 'write.data.path'='s3://my-table-data-bucket') +PARTITIONED BY (category); +``` + +We can then insert a single row into this new table +```SQL +INSERT INTO my_catalog.my_ns.my_table VALUES (1, "Pizza", "orders"); +``` + +Which will write the data to S3 with a hash (`2d3905f8`) appended directly after the `write.object-storage.path`, ensuring reads to the table are spread evenly across [S3 bucket prefixes](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance.html), and improving performance. +``` +s3://my-table-data-bucket/2d3905f8/my_ns.db/my_table/category=orders/00000-0-5affc076-96a4-48f2-9cd2-d5efbc9f0c94-00001.parquet +``` + +Note, the path resolution logic for `ObjectStoreLocationProvider` is `write.data.path` then `/data`. +However, for the older versions up to 0.12.0, the logic is as follows: +- before 0.12.0, `write.object-storage.path` must be set. +- at 0.12.0, `write.object-storage.path` then `write.folder-storage.path` then `/data`. + +For more details, please refer to the [LocationProvider Configuration](custom-catalog.md#custom-location-provider-implementation) section. + +### S3 Strong Consistency + +In November 2020, S3 announced [strong consistency](https://aws.amazon.com/s3/consistency/) for all read operations, and Iceberg is updated to fully leverage this feature. +There is no redundant consistency wait and check which might negatively impact performance during IO operations. + +### Hadoop S3A FileSystem + +Before `S3FileIO` was introduced, many Iceberg users choose to use `HadoopFileIO` to write data to S3 through the [S3A FileSystem](https://github.com/apache/hadoop/blob/trunk/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java). +As introduced in the previous sections, `S3FileIO` adopts the latest AWS clients and S3 features for optimized security and performance + and is thus recommended for S3 use cases rather than the S3A FileSystem. + +`S3FileIO` writes data with `s3://` URI scheme, but it is also compatible with schemes written by the S3A FileSystem. +This means for any table manifests containing `s3a://` or `s3n://` file paths, `S3FileIO` is still able to read them. +This feature allows people to easily switch from S3A to `S3FileIO`. + +If for any reason you have to use S3A, here are the instructions: + +1. To store data using S3A, specify the `warehouse` catalog property to be an S3A path, e.g. `s3a://my-bucket/my-warehouse` +2. For `HiveCatalog`, to also store metadata using S3A, specify the Hadoop config property `hive.metastore.warehouse.dir` to be an S3A path. +3. Add [hadoop-aws](https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-aws) as a runtime dependency of your compute engine. +4. Configure AWS settings based on [hadoop-aws documentation](https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html) (make sure you check the version, S3A configuration varies a lot based on the version you use). + +### S3 Write Checksum Verification + +To ensure integrity of uploaded objects, checksum validations for S3 writes can be turned on by setting catalog property `s3.checksum-enabled` to `true`. +This is turned off by default. + +### S3 Tags + +Custom [tags](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-tagging.html) can be added to S3 objects while writing and deleting. +For example, to write S3 tags with Spark 3.3, you can start the Spark SQL shell with: +``` +spark-sql --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket/my/key/prefix \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \ + --conf spark.sql.catalog.my_catalog.s3.write.tags.my_key1=my_val1 \ + --conf spark.sql.catalog.my_catalog.s3.write.tags.my_key2=my_val2 +``` +For the above example, the objects in S3 will be saved with tags: `my_key1=my_val1` and `my_key2=my_val2`. Do note that the specified write tags will be saved only while object creation. + +When the catalog property `s3.delete-enabled` is set to `false`, the objects are not hard-deleted from S3. +This is expected to be used in combination with S3 delete tagging, so objects are tagged and removed using [S3 lifecycle policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html). +The property is set to `true` by default. + +With the `s3.delete.tags` config, objects are tagged with the configured key-value pairs before deletion. +Users can configure tag-based object lifecycle policy at bucket level to transition objects to different tiers. +For example, to add S3 delete tags with Spark 3.3, you can start the Spark SQL shell with: + +``` +sh spark-sql --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://iceberg-warehouse/s3-tagging \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \ + --conf spark.sql.catalog.my_catalog.s3.delete.tags.my_key3=my_val3 \ + --conf spark.sql.catalog.my_catalog.s3.delete-enabled=false +``` + +For the above example, the objects in S3 will be saved with tags: `my_key3=my_val3` before deletion. +Users can also use the catalog property `s3.delete.num-threads` to mention the number of threads to be used for adding delete tags to the S3 objects. + +When the catalog property `s3.write.table-tag-enabled` and `s3.write.namespace-tag-enabled` is set to `true` then the objects in S3 will be saved with tags: `iceberg.table=` and `iceberg.namespace=`. +Users can define access and data retention policy per namespace or table based on these tags. +For example, to write table and namespace name as S3 tags with Spark 3.3, you can start the Spark SQL shell with: +``` +sh spark-sql --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://iceberg-warehouse/s3-tagging \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \ + --conf spark.sql.catalog.my_catalog.s3.write.table-tag-enabled=true \ + --conf spark.sql.catalog.my_catalog.s3.write.namespace-tag-enabled=true +``` +For more details on tag restrictions, please refer [User-Defined Tag Restrictions](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/allocation-tag-restrictions.html). + +### S3 Access Points + +[Access Points](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-access-points.html) can be used to perform +S3 operations by specifying a mapping of bucket to access points. This is useful for multi-region access, cross-region access, +disaster recovery, etc. + +For using cross-region access points, we need to additionally set `use-arn-region-enabled` catalog property to +`true` to enable `S3FileIO` to make cross-region calls, it's not required for same / multi-region access points. + +For example, to use S3 access-point with Spark 3.3, you can start the Spark SQL shell with: +``` +spark-sql --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket2/my/key/prefix \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \ + --conf spark.sql.catalog.my_catalog.s3.use-arn-region-enabled=false \ + --conf spark.sql.catalog.test.s3.access-points.my-bucket1=arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap \ + --conf spark.sql.catalog.test.s3.access-points.my-bucket2=arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap +``` +For the above example, the objects in S3 on `my-bucket1` and `my-bucket2` buckets will use `arn:aws:s3::123456789012:accesspoint:mfzwi23gnjvgw.mrap` +access-point for all S3 operations. + +For more details on using access-points, please refer [Using access points with compatible Amazon S3 operations](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-points-usage-examples.html). + +### S3 Acceleration + +[S3 Acceleration](https://aws.amazon.com/s3/transfer-acceleration/) can be used to speed up transfers to and from Amazon S3 by as much as 50-500% for long-distance transfer of larger objects. + +To use S3 Acceleration, we need to set `s3.acceleration-enabled` catalog property to `true` to enable `S3FileIO` to make accelerated S3 calls. + +For example, to use S3 Acceleration with Spark 3.3, you can start the Spark SQL shell with: +``` +spark-sql --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket2/my/key/prefix \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \ + --conf spark.sql.catalog.my_catalog.s3.acceleration-enabled=true +``` + +For more details on using S3 Acceleration, please refer to [Configuring fast, secure file transfers using Amazon S3 Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html). + +### S3 Dual-stack + +[S3 Dual-stack](https://docs.aws.amazon.com/AmazonS3/latest/userguide/dual-stack-endpoints.html) allows a client to access an S3 bucket through a dual-stack endpoint. +When clients request a dual-stack endpoint, the bucket URL resolves to an IPv6 address if possible, otherwise fallback to IPv4. + +To use S3 Dual-stack, we need to set `s3.dualstack-enabled` catalog property to `true` to enable `S3FileIO` to make dual-stack S3 calls. + +For example, to use S3 Dual-stack with Spark 3.3, you can start the Spark SQL shell with: +``` +spark-sql --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket2/my/key/prefix \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \ + --conf spark.sql.catalog.my_catalog.s3.dualstack-enabled=true +``` + +For more details on using S3 Dual-stack, please refer [Using dual-stack endpoints from the AWS CLI and the AWS SDKs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/dual-stack-endpoints.html#dual-stack-endpoints-cli) + +## AWS Client Customization + +Many organizations have customized their way of configuring AWS clients with their own credential provider, access proxy, retry strategy, etc. +Iceberg allows users to plug in their own implementation of `org.apache.iceberg.aws.AwsClientFactory` by setting the `client.factory` catalog property. + +### Cross-Account and Cross-Region Access + +It is a common use case for organizations to have a centralized AWS account for Glue metastore and S3 buckets, and use different AWS accounts and regions for different teams to access those resources. +In this case, a [cross-account IAM role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) is needed to access those centralized resources. +Iceberg provides an AWS client factory `AssumeRoleAwsClientFactory` to support this common use case. +This also serves as an example for users who would like to implement their own AWS client factory. + +This client factory has the following configurable catalog properties: + +| Property | Default | Description | +| --------------------------------- | ---------------------------------------- | ------------------------------------------------------ | +| client.assume-role.arn | null, requires user input | ARN of the role to assume, e.g. arn:aws:iam::123456789:role/myRoleToAssume | +| client.assume-role.region | null, requires user input | All AWS clients except the STS client will use the given region instead of the default region chain | +| client.assume-role.external-id | null | An optional [external ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html) | +| client.assume-role.timeout-sec | 1 hour | Timeout of each assume role session. At the end of the timeout, a new set of role session credentials will be fetched through an STS client. | + +By using this client factory, an STS client is initialized with the default credential and region to assume the specified role. +The Glue, S3 and DynamoDB clients are then initialized with the assume-role credential and region to access resources. +Here is an example to start Spark shell with this client factory: + +```shell +spark-sql --packages org.apache.iceberg:iceberg-spark-runtime:{{ icebergVersion }},software.amazon.awssdk:bundle:2.20.18 \ + --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket/my/key/prefix \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \ + --conf spark.sql.catalog.my_catalog.client.factory=org.apache.iceberg.aws.AssumeRoleAwsClientFactory \ + --conf spark.sql.catalog.my_catalog.client.assume-role.arn=arn:aws:iam::123456789:role/myRoleToAssume \ + --conf spark.sql.catalog.my_catalog.client.assume-role.region=ap-northeast-1 +``` + +### HTTP Client Configurations +AWS clients support two types of HTTP Client, [URL Connection HTTP Client](https://mvnrepository.com/artifact/software.amazon.awssdk/url-connection-client) +and [Apache HTTP Client](https://mvnrepository.com/artifact/software.amazon.awssdk/apache-client). +By default, AWS clients use **URL Connection** HTTP Client to communicate with the service. +This HTTP client optimizes for minimum dependencies and startup latency but supports less functionality than other implementations. +In contrast, Apache HTTP Client supports more functionalities and more customized settings, such as expect-continue handshake and TCP KeepAlive, at the cost of extra dependency and additional startup latency. + +For more details of configuration, see sections [URL Connection HTTP Client Configurations](#url-connection-http-client-configurations) and [Apache HTTP Client Configurations](#apache-http-client-configurations). + +Configure the following property to set the type of HTTP client: + +| Property | Default | Description | +|------------------|---------|------------------------------------------------------------------------------------------------------------| +| http-client.type | apache | Types of HTTP Client.
`urlconnection`: URL Connection HTTP Client
`apache`: Apache HTTP Client | + +#### URL Connection HTTP Client Configurations + +URL Connection HTTP Client has the following configurable properties: + +| Property | Default | Description | +|-------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| http-client.urlconnection.socket-timeout-ms | null | An optional [socket timeout](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.Builder.html#socketTimeout(java.time.Duration)) in milliseconds | +| http-client.urlconnection.connection-timeout-ms | null | An optional [connection timeout](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.Builder.html#connectionTimeout(java.time.Duration)) in milliseconds | + +Users can use catalog properties to override the defaults. For example, to configure the socket timeout for URL Connection HTTP Client when starting a spark shell, one can add: +```shell +--conf spark.sql.catalog.my_catalog.http-client.urlconnection.socket-timeout-ms=80 +``` + +#### Apache HTTP Client Configurations + +Apache HTTP Client has the following configurable properties: + +| Property | Default | Description | +|-------------------------------------------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| http-client.apache.socket-timeout-ms | null | An optional [socket timeout](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#socketTimeout(java.time.Duration)) in milliseconds | +| http-client.apache.connection-timeout-ms | null | An optional [connection timeout](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#connectionTimeout(java.time.Duration)) in milliseconds | +| http-client.apache.connection-acquisition-timeout-ms | null | An optional [connection acquisition timeout](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#connectionAcquisitionTimeout(java.time.Duration)) in milliseconds | +| http-client.apache.connection-max-idle-time-ms | null | An optional [connection max idle timeout](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#connectionMaxIdleTime(java.time.Duration)) in milliseconds | +| http-client.apache.connection-time-to-live-ms | null | An optional [connection time to live](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#connectionTimeToLive(java.time.Duration)) in milliseconds | +| http-client.apache.expect-continue-enabled | null, disabled by default | An optional `true/false` setting that controls whether [expect continue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#expectContinueEnabled(java.lang.Boolean)) is enabled | +| http-client.apache.max-connections | null | An optional [max connections](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#maxConnections(java.lang.Integer)) in integer | +| http-client.apache.tcp-keep-alive-enabled | null, disabled by default | An optional `true/false` setting that controls whether [tcp keep alive](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#tcpKeepAlive(java.lang.Boolean)) is enabled | +| http-client.apache.use-idle-connection-reaper-enabled | null, enabled by default | An optional `true/false` setting that controls whether [use idle connection reaper](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html#useIdleConnectionReaper(java.lang.Boolean)) is used | + +Users can use catalog properties to override the defaults. For example, to configure the max connections for Apache HTTP Client when starting a spark shell, one can add: +```shell +--conf spark.sql.catalog.my_catalog.http-client.apache.max-connections=5 +``` + +## Run Iceberg on AWS + +### Amazon Athena + +[Amazon Athena](https://aws.amazon.com/athena/) provides a serverless query engine that could be used to perform read, write, update and optimization tasks against Iceberg tables. +More details could be found [here](https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg.html). + +### Amazon EMR + +[Amazon EMR](https://aws.amazon.com/emr/) can provision clusters with [Spark](https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-spark.html) (EMR 6 for Spark 3, EMR 5 for Spark 2), +[Hive](https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-hive.html), [Flink](https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-flink.html), +[Trino](https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-presto.html) that can run Iceberg. + +Starting with EMR version 6.5.0, EMR clusters can be configured to have the necessary Apache Iceberg dependencies installed without requiring bootstrap actions. +Please refer to the [official documentation](https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-iceberg-use-cluster.html) on how to create a cluster with Iceberg installed. + +For versions before 6.5.0, you can use a [bootstrap action](https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-plan-bootstrap.html) similar to the following to pre-install all necessary dependencies: +```sh +#!/bin/bash + +AWS_SDK_VERSION=2.20.18 +ICEBERG_VERSION={{ icebergVersion }} +MAVEN_URL=https://repo1.maven.org/maven2 +ICEBERG_MAVEN_URL=$MAVEN_URL/org/apache/iceberg +AWS_MAVEN_URL=$MAVEN_URL/software/amazon/awssdk +# NOTE: this is just an example shared class path between Spark and Flink, +# please choose a proper class path for production. +LIB_PATH=/usr/share/aws/aws-java-sdk/ + +AWS_PACKAGES=( + "bundle" +) + +ICEBERG_PACKAGES=( + "iceberg-spark-runtime-3.3_2.12" + "iceberg-flink-runtime" +) + +install_dependencies () { + install_path=$1 + download_url=$2 + version=$3 + shift + pkgs=("$@") + for pkg in "${pkgs[@]}"; do + sudo wget -P $install_path $download_url/$pkg/$version/$pkg-$version.jar + done +} + +install_dependencies $LIB_PATH $ICEBERG_MAVEN_URL $ICEBERG_VERSION "${ICEBERG_PACKAGES[@]}" +install_dependencies $LIB_PATH $AWS_MAVEN_URL $AWS_SDK_VERSION "${AWS_PACKAGES[@]}" +``` + +### AWS Glue + +[AWS Glue](https://aws.amazon.com/glue/) provides a serverless data integration service +that could be used to perform read, write and update tasks against Iceberg tables. +More details could be found [here](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-programming-etl-format-iceberg.html). + + +### AWS EKS + +[AWS Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/) can be used to start any Spark, Flink, Hive, Presto or Trino clusters to work with Iceberg. +Search the [Iceberg blogs](../../blogs.md) page for tutorials around running Iceberg with Docker and Kubernetes. + +### Amazon Kinesis + +[Amazon Kinesis Data Analytics](https://aws.amazon.com/about-aws/whats-new/2019/11/you-can-now-run-fully-managed-apache-flink-applications-with-apache-kafka/) provides a platform +to run fully managed Apache Flink applications. You can include Iceberg in your application Jar and run it in the platform. diff --git a/site/docs/docs/nightly/docs/branching.md b/site/docs/docs/nightly/docs/branching.md new file mode 100644 index 000000000000..3ec28e628507 --- /dev/null +++ b/site/docs/docs/nightly/docs/branching.md @@ -0,0 +1,115 @@ +--- +title: "Branching and Tagging" +--- + + + +# Branching and Tagging + +## Overview + +Iceberg table metadata maintains a log of snapshots which represent the changes applied to a table. +Snapshots are fundamental in Iceberg as they are the basis for reader isolation and time travel queries. +For controlling metadata size and storage costs, Iceberg provides snapshot lifecycle management procedures such as [`expire_snapshots`](spark-procedures.md#expire-snapshots) for removing unused snapshots and no longer neccessary data files based on table snapshot retention properties. + +**For more sophisticated snapshot lifecycle management, Iceberg supports branches and tags which are named references to snapshots with their own independent lifecycles. This lifecycle is controlled by branch and tag level retention policies.** +Branches are independent lineages of snapshots and point to the head of the lineage. +Branches and tags have a maximum reference age property which control when the reference to the snapshot itself should be expired. +Branches have retention properties which define the minimum number of snapshots to retain on a branch as well as the maximum age of individual snapshots to retain on the branch. +These properties are used when the expireSnapshots procedure is run. +For details on the algorithm for expireSnapshots, refer to the [spec](../../spec.md#snapshot-retention-policy). + +## Use Cases + +Branching and tagging can be used for handling GDPR requirements and retaining important historical snapshots for auditing. +Branches can also be used as part of data engineering workflows, for enabling experimental branches for testing and validating new jobs. +See below for some examples of how branching and tagging can facilitate these use cases. + +### Historical Tags + +Tags can be used for retaining important historical snapshots for auditing purposes. + +![Historical Tags](assets/images/historical-snapshot-tag.png) + +The above diagram demonstrates retaininig important historical snapshot with the following retention policy, defined +via Spark SQL. + +1. Retain 1 snapshot per week for 1 month. This can be achieved by tagging the weekly snapshot and setting the tag retention to be a month. +snapshots will be kept, and the branch reference itself will be retained for 1 week. +```sql +-- Create a tag for the first end of week snapshot. Retain the snapshot for a week +ALTER TABLE prod.db.table CREATE TAG 'EOW-01' AS OF VERSION 7 RETAIN 7 DAYS +``` + +2. Retain 1 snapshot per month for 6 months. This can be achieved by tagging the monthly snapshot and setting the tag retention to be 6 months. +```sql +-- Create a tag for the first end of month snapshot. Retain the snapshot for 6 months +ALTER TABLE prod.db.table CREATE TAG 'EOM-01' AS OF VERSION 30 RETAIN 180 DAYS +``` + +3. Retain 1 snapshot per year forever. This can be achieved by tagging the annual snapshot. The default retention for branches and tags is forever. +```sql +-- Create a tag for the end of the year and retain it forever. +ALTER TABLE prod.db.table CREATE TAG 'EOY-2023' AS OF VERSION 365 +``` + +4. Create a temporary "test-branch" which is retained for 7 days and the latest 2 snapshots on the branch are retained. +```sql +-- Create a branch "test-branch" which will be retained for 7 days along with the latest 2 snapshots +ALTER TABLE prod.db.table CREATE BRANCH test-branch RETAIN 7 DAYS WITH RETENTION 2 SNAPSHOTS +``` + +### Audit Branch + +![Audit Branch](assets/images/audit-branch.png) + +The above diagram shows an example of using an audit branch for validating a write workflow. + +1. First ensure `write.wap.enabled` is set. +```sql +ALTER TABLE db.table SET TBLPROPERTIES ( + 'write.wap.enabled''true' +) +``` +2. Create `audit-branch` starting from snapshot 3, which will be written to and retained for 1 week. +```sql +ALTER TABLE db.table CREATE BRANCH `audit-branch` AS OF VERSION 3 RETAIN 7 DAYS +``` +3. Writes are performed on a separate `audit-branch` independent from the main table history. +```sql +-- WAP Branch write +SET spark.wap.branch = 'audit-branch' +INSERT INTO prod.db.table VALUES (3, 'c') +``` +4. A validation workflow can validate (e.g. data quality) the state of `audit-branch`. +5. After validation, the main branch can be `fastForward` to the head of `audit-branch` to update the main table state. +```java +table.manageSnapshots().fastForward("main", "audit-branch").commit() +``` +6. The branch reference will be removed when `expireSnapshots` is run 1 week later. + +## Usage + +Creating, querying and writing to branches and tags are supported in the Iceberg Java library, and in Spark and Flink engine integrations. + +- [Iceberg Java Library](java-api-quickstart.md#branching-and-tagging) +- [Spark DDLs](spark-ddl.md#branching-and-tagging-ddl) +- [Spark Reads](spark-queries.md#time-travel) +- [Spark Branch Writes](spark-writes.md#writing-to-branches) +- [Flink Reads](flink-queries.md#reading-branches-and-tags-with-SQL) +- [Flink Branch Writes](flink-writes.md#branch-writes) diff --git a/site/docs/docs/nightly/docs/configuration.md b/site/docs/docs/nightly/docs/configuration.md new file mode 100644 index 000000000000..bc919150a3e1 --- /dev/null +++ b/site/docs/docs/nightly/docs/configuration.md @@ -0,0 +1,191 @@ +--- +title: "Configuration" +--- + + +# Configuration + +## Table properties + +Iceberg tables support table properties to configure table behavior, like the default split size for readers. + +### Read properties + +| Property | Default | Description | +| --------------------------------- | ------------------ | ------------------------------------------------------ | +| read.split.target-size | 134217728 (128 MB) | Target size when combining data input splits | +| read.split.metadata-target-size | 33554432 (32 MB) | Target size when combining metadata input splits | +| read.split.planning-lookback | 10 | Number of bins to consider when combining input splits | +| read.split.open-file-cost | 4194304 (4 MB) | The estimated cost to open a file, used as a minimum weight when combining splits. | +| read.parquet.vectorization.enabled| true | Controls whether Parquet vectorized reads are used | +| read.parquet.vectorization.batch-size| 5000 | The batch size for parquet vectorized reads | +| read.orc.vectorization.enabled | false | Controls whether orc vectorized reads are used | +| read.orc.vectorization.batch-size | 5000 | The batch size for orc vectorized reads | + +### Write properties + +| Property | Default | Description | +|-----------------------------------------------------|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| write.format.default | parquet | Default file format for the table; parquet, avro, or orc | +| write.delete.format.default | data file format | Default delete file format for the table; parquet, avro, or orc | +| write.parquet.row-group-size-bytes | 134217728 (128 MB) | Parquet row group size | +| write.parquet.page-size-bytes | 1048576 (1 MB) | Parquet page size | +| write.parquet.page-row-limit | 20000 | Parquet page row limit | +| write.parquet.dict-size-bytes | 2097152 (2 MB) | Parquet dictionary page size | +| write.parquet.compression-codec | gzip | Parquet compression codec: zstd, brotli, lz4, gzip, snappy, uncompressed | +| write.parquet.compression-level | null | Parquet compression level | +| write.parquet.bloom-filter-enabled.column.col1 | (not set) | Enables writing a bloom filter for the column: col1 | +| write.parquet.bloom-filter-max-bytes | 1048576 (1 MB) | The maximum number of bytes for a bloom filter bitset | +| write.avro.compression-codec | gzip | Avro compression codec: gzip(deflate with 9 level), zstd, snappy, uncompressed | +| write.avro.compression-level | null | Avro compression level | +| write.orc.stripe-size-bytes | 67108864 (64 MB) | Define the default ORC stripe size, in bytes | +| write.orc.block-size-bytes | 268435456 (256 MB) | Define the default file system block size for ORC files | +| write.orc.compression-codec | zlib | ORC compression codec: zstd, lz4, lzo, zlib, snappy, none | +| write.orc.compression-strategy | speed | ORC compression strategy: speed, compression | +| write.orc.bloom.filter.columns | (not set) | Comma separated list of column names for which a Bloom filter must be created | +| write.orc.bloom.filter.fpp | 0.05 | False positive probability for Bloom filter (must > 0.0 and < 1.0) | +| write.location-provider.impl | null | Optional custom implementation for LocationProvider | +| write.metadata.compression-codec | none | Metadata compression codec; none or gzip | +| write.metadata.metrics.max-inferred-column-defaults | 100 | Defines the maximum number of columns for which metrics are collected | +| write.metadata.metrics.default | truncate(16) | Default metrics mode for all columns in the table; none, counts, truncate(length), or full | +| write.metadata.metrics.column.col1 | (not set) | Metrics mode for column 'col1' to allow per-column tuning; none, counts, truncate(length), or full | +| write.target-file-size-bytes | 536870912 (512 MB) | Controls the size of files generated to target about this many bytes | +| write.delete.target-file-size-bytes | 67108864 (64 MB) | Controls the size of delete files generated to target about this many bytes | +| write.distribution-mode | none | Defines distribution of write data: __none__: don't shuffle rows; __hash__: hash distribute by partition key ; __range__: range distribute by partition key or sort key if table has an SortOrder | +| write.delete.distribution-mode | hash | Defines distribution of write delete data | +| write.update.distribution-mode | hash | Defines distribution of write update data | +| write.merge.distribution-mode | none | Defines distribution of write merge data | +| write.wap.enabled | false | Enables write-audit-publish writes | +| write.summary.partition-limit | 0 | Includes partition-level summary stats in snapshot summaries if the changed partition count is less than this limit | +| write.metadata.delete-after-commit.enabled | false | Controls whether to delete the oldest **tracked** version metadata files after commit | +| write.metadata.previous-versions-max | 100 | The max number of previous version metadata files to keep before deleting after commit | +| write.spark.fanout.enabled | false | Enables the fanout writer in Spark that does not require data to be clustered; uses more memory | +| write.object-storage.enabled | false | Enables the object storage location provider that adds a hash component to file paths | +| write.data.path | table location + /data | Base location for data files | +| write.metadata.path | table location + /metadata | Base location for metadata files | +| write.delete.mode | copy-on-write | Mode used for delete commands: copy-on-write or merge-on-read (v2 only) | +| write.delete.isolation-level | serializable | Isolation level for delete commands: serializable or snapshot | +| write.update.mode | copy-on-write | Mode used for update commands: copy-on-write or merge-on-read (v2 only) | +| write.update.isolation-level | serializable | Isolation level for update commands: serializable or snapshot | +| write.merge.mode | copy-on-write | Mode used for merge commands: copy-on-write or merge-on-read (v2 only) | +| write.merge.isolation-level | serializable | Isolation level for merge commands: serializable or snapshot | + +### Table behavior properties + +| Property | Default | Description | +| ---------------------------------- | ---------------- | ------------------------------------------------------------- | +| commit.retry.num-retries | 4 | Number of times to retry a commit before failing | +| commit.retry.min-wait-ms | 100 | Minimum time in milliseconds to wait before retrying a commit | +| commit.retry.max-wait-ms | 60000 (1 min) | Maximum time in milliseconds to wait before retrying a commit | +| commit.retry.total-timeout-ms | 1800000 (30 min) | Total retry timeout period in milliseconds for a commit | +| commit.status-check.num-retries | 3 | Number of times to check whether a commit succeeded after a connection is lost before failing due to an unknown commit state | +| commit.status-check.min-wait-ms | 1000 (1s) | Minimum time in milliseconds to wait before retrying a status-check | +| commit.status-check.max-wait-ms | 60000 (1 min) | Maximum time in milliseconds to wait before retrying a status-check | +| commit.status-check.total-timeout-ms| 1800000 (30 min) | Total timeout period in which the commit status-check must succeed, in milliseconds | +| commit.manifest.target-size-bytes | 8388608 (8 MB) | Target size when merging manifest files | +| commit.manifest.min-count-to-merge | 100 | Minimum number of manifests to accumulate before merging | +| commit.manifest-merge.enabled | true | Controls whether to automatically merge manifests on writes | +| history.expire.max-snapshot-age-ms | 432000000 (5 days) | Default max age of snapshots to keep on the table and all of its branches while expiring snapshots | +| history.expire.min-snapshots-to-keep | 1 | Default min number of snapshots to keep on the table and all of its branches while expiring snapshots | +| history.expire.max-ref-age-ms | `Long.MAX_VALUE` (forever) | For snapshot references except the `main` branch, default max age of snapshot references to keep while expiring snapshots. The `main` branch never expires. | + +### Reserved table properties +Reserved table properties are only used to control behaviors when creating or updating a table. +The value of these properties are not persisted as a part of the table metadata. + +| Property | Default | Description | +| -------------- | -------- | ------------------------------------------------------------- | +| format-version | 1 | Table's format version (can be 1 or 2) as defined in the [Spec](../../spec.md#format-versioning). | + +### Compatibility flags + +| Property | Default | Description | +| --------------------------------------------- | -------- | ------------------------------------------------------------- | +| compatibility.snapshot-id-inheritance.enabled | false | Enables committing snapshots without explicit snapshot IDs | + +## Catalog properties + +Iceberg catalogs support using catalog properties to configure catalog behaviors. Here is a list of commonly used catalog properties: + +| Property | Default | Description | +| --------------------------------- | ------------------ | ------------------------------------------------------ | +| catalog-impl | null | a custom `Catalog` implementation to use by an engine | +| io-impl | null | a custom `FileIO` implementation to use in a catalog | +| warehouse | null | the root path of the data warehouse | +| uri | null | a URI string, such as Hive metastore URI | +| clients | 2 | client pool size | +| cache-enabled | true | Whether to cache catalog entries | +| cache.expiration-interval-ms | 30000 | How long catalog entries are locally cached, in milliseconds; 0 disables caching, negative values disable expiration | + +`HadoopCatalog` and `HiveCatalog` can access the properties in their constructors. +Any other custom catalog can access the properties by implementing `Catalog.initialize(catalogName, catalogProperties)`. +The properties can be manually constructed or passed in from a compute engine like Spark or Flink. +Spark uses its session properties as catalog properties, see more details in the [Spark configuration](spark-configuration.md#catalog-configuration) section. +Flink passes in catalog properties through `CREATE CATALOG` statement, see more details in the [Flink](flink.md#adding-catalogs) section. + +### Lock catalog properties + +Here are the catalog properties related to locking. They are used by some catalog implementations to control the locking behavior during commits. + +| Property | Default | Description | +| --------------------------------- | ------------------ | ------------------------------------------------------ | +| lock-impl | null | a custom implementation of the lock manager, the actual interface depends on the catalog used | +| lock.table | null | an auxiliary table for locking, such as in [AWS DynamoDB lock manager](aws.md#dynamodb-lock-manager) | +| lock.acquire-interval-ms | 5000 (5 s) | the interval to wait between each attempt to acquire a lock | +| lock.acquire-timeout-ms | 180000 (3 min) | the maximum time to try acquiring a lock | +| lock.heartbeat-interval-ms | 3000 (3 s) | the interval to wait between each heartbeat after acquiring a lock | +| lock.heartbeat-timeout-ms | 15000 (15 s) | the maximum time without a heartbeat to consider a lock expired | + + +## Hadoop configuration + +The following properties from the Hadoop configuration are used by the Hive Metastore connector. +The HMS table locking is a 2-step process: +1. Lock Creation: Create lock in HMS and queue for acquisition +2. Lock Check: Check if lock successfully acquired + +| Property | Default | Description | +|-------------------------------------------|-----------------|------------------------------------------------------------------------------| +| iceberg.hive.client-pool-size | 5 | The size of the Hive client pool when tracking tables in HMS | +| iceberg.hive.lock-creation-timeout-ms | 180000 (3 min) | Maximum time in milliseconds to create a lock in the HMS | +| iceberg.hive.lock-creation-min-wait-ms | 50 | Minimum time in milliseconds between retries of creating the lock in the HMS | +| iceberg.hive.lock-creation-max-wait-ms | 5000 | Maximum time in milliseconds between retries of creating the lock in the HMS | +| iceberg.hive.lock-timeout-ms | 180000 (3 min) | Maximum time in milliseconds to acquire a lock | +| iceberg.hive.lock-check-min-wait-ms | 50 | Minimum time in milliseconds between checking the acquisition of the lock | +| iceberg.hive.lock-check-max-wait-ms | 5000 | Maximum time in milliseconds between checking the acquisition of the lock | +| iceberg.hive.lock-heartbeat-interval-ms | 240000 (4 min) | The heartbeat interval for the HMS locks. | +| iceberg.hive.metadata-refresh-max-retries | 2 | Maximum number of retries when the metadata file is missing | +| iceberg.hive.table-level-lock-evict-ms | 600000 (10 min) | The timeout for the JVM table lock is | +| iceberg.engine.hive.lock-enabled | true | Use HMS locks to ensure atomicity of commits | + +Note: `iceberg.hive.lock-check-max-wait-ms` and `iceberg.hive.lock-heartbeat-interval-ms` should be less than the [transaction timeout](https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties#ConfigurationProperties-hive.txn.timeout) +of the Hive Metastore (`hive.txn.timeout` or `metastore.txn.timeout` in the newer versions). Otherwise, the heartbeats on the lock (which happens during the lock checks) would end up expiring in the +Hive Metastore before the lock is retried from Iceberg. + +Warn: Setting `iceberg.engine.hive.lock-enabled`=`false` will cause HiveCatalog to commit to tables without using Hive locks. +This should only be set to `false` if all following conditions are met: + - [HIVE-26882](https://issues.apache.org/jira/browse/HIVE-26882) +is available on the Hive Metastore server + - All other HiveCatalogs committing to tables that this HiveCatalog commits to are also on Iceberg 1.3 or later + - All other HiveCatalogs committing to tables that this HiveCatalog commits to have also disabled Hive locks on commit. + +**Failing to ensure these conditions risks corrupting the table.** + +Even with `iceberg.engine.hive.lock-enabled` set to `false`, a HiveCatalog can still use locks for individual tables by setting the table property `engine.hive.lock-enabled`=`true`. +This is useful in the case where other HiveCatalogs cannot be upgraded and set to commit without using Hive locks. + diff --git a/site/docs/docs/nightly/docs/custom-catalog.md b/site/docs/docs/nightly/docs/custom-catalog.md new file mode 100644 index 000000000000..bfcb786cb449 --- /dev/null +++ b/site/docs/docs/nightly/docs/custom-catalog.md @@ -0,0 +1,268 @@ +--- +title: "Java Custom Catalog" +--- + + +# Custom Catalog + +It's possible to read an iceberg table either from an hdfs path or from a hive table. It's also possible to use a custom metastore in place of hive. The steps to do that are as follows. + +- [Custom TableOperations](#custom-table-operations-implementation) +- [Custom Catalog](#custom-catalog-implementation) +- [Custom FileIO](#custom-file-io-implementation) +- [Custom LocationProvider](#custom-location-provider-implementation) +- [Custom IcebergSource](#custom-icebergsource) + +### Custom table operations implementation +Extend `BaseMetastoreTableOperations` to provide implementation on how to read and write metadata + +Example: +```java +class CustomTableOperations extends BaseMetastoreTableOperations { + private String dbName; + private String tableName; + private Configuration conf; + private FileIO fileIO; + + protected CustomTableOperations(Configuration conf, String dbName, String tableName) { + this.conf = conf; + this.dbName = dbName; + this.tableName = tableName; + } + + // The doRefresh method should provide implementation on how to get the metadata location + @Override + public void doRefresh() { + + // Example custom service which returns the metadata location given a dbName and tableName + String metadataLocation = CustomService.getMetadataForTable(conf, dbName, tableName); + + // When updating from a metadata file location, call the helper method + refreshFromMetadataLocation(metadataLocation); + + } + + // The doCommit method should provide implementation on how to update with metadata location atomically + @Override + public void doCommit(TableMetadata base, TableMetadata metadata) { + String oldMetadataLocation = base.location(); + + // Write new metadata using helper method + String newMetadataLocation = writeNewMetadata(metadata, currentVersion() + 1); + + // Example custom service which updates the metadata location for the given db and table atomically + CustomService.updateMetadataLocation(dbName, tableName, oldMetadataLocation, newMetadataLocation); + + } + + // The io method provides a FileIO which is used to read and write the table metadata files + @Override + public FileIO io() { + if (fileIO == null) { + fileIO = new HadoopFileIO(conf); + } + return fileIO; + } +} +``` + +A `TableOperations` instance is usually obtained by calling `Catalog.newTableOps(TableIdentifier)`. +See the next section about implementing and loading a custom catalog. + +### Custom catalog implementation +Extend `BaseMetastoreCatalog` to provide default warehouse locations and instantiate `CustomTableOperations` + +Example: +```java +public class CustomCatalog extends BaseMetastoreCatalog { + + private Configuration configuration; + + // must have a no-arg constructor to be dynamically loaded + // initialize(String name, Map properties) will be called to complete initialization + public CustomCatalog() { + } + + public CustomCatalog(Configuration configuration) { + this.configuration = configuration; + } + + @Override + protected TableOperations newTableOps(TableIdentifier tableIdentifier) { + String dbName = tableIdentifier.namespace().level(0); + String tableName = tableIdentifier.name(); + // instantiate the CustomTableOperations + return new CustomTableOperations(configuration, dbName, tableName); + } + + @Override + protected String defaultWarehouseLocation(TableIdentifier tableIdentifier) { + + // Can choose to use any other configuration name + String tableLocation = configuration.get("custom.iceberg.warehouse.location"); + + // Can be an s3 or hdfs path + if (tableLocation == null) { + throw new RuntimeException("custom.iceberg.warehouse.location configuration not set!"); + } + + return String.format( + "%s/%s.db/%s", tableLocation, + tableIdentifier.namespace().levels()[0], + tableIdentifier.name()); + } + + @Override + public boolean dropTable(TableIdentifier identifier, boolean purge) { + // Example service to delete table + CustomService.deleteTable(identifier.namepsace().level(0), identifier.name()); + } + + @Override + public void renameTable(TableIdentifier from, TableIdentifier to) { + Preconditions.checkArgument(from.namespace().level(0).equals(to.namespace().level(0)), + "Cannot move table between databases"); + // Example service to rename table + CustomService.renameTable(from.namepsace().level(0), from.name(), to.name()); + } + + // implement this method to read catalog name and properties during initialization + public void initialize(String name, Map properties) { + } +} +``` + +Catalog implementations can be dynamically loaded in most compute engines. +For Spark and Flink, you can specify the `catalog-impl` catalog property to load it. +Read the [Configuration](configuration.md#catalog-properties) section for more details. +For MapReduce, implement `org.apache.iceberg.mr.CatalogLoader` and set Hadoop property `iceberg.mr.catalog.loader.class` to load it. +If your catalog must read Hadoop configuration to access certain environment properties, make your catalog implement `org.apache.hadoop.conf.Configurable`. + +### Custom file IO implementation + +Extend `FileIO` and provide implementation to read and write data files + +Example: +```java +public class CustomFileIO implements FileIO { + + // must have a no-arg constructor to be dynamically loaded + // initialize(Map properties) will be called to complete initialization + public CustomFileIO() { + } + + @Override + public InputFile newInputFile(String s) { + // you also need to implement the InputFile interface for a custom input file + return new CustomInputFile(s); + } + + @Override + public OutputFile newOutputFile(String s) { + // you also need to implement the OutputFile interface for a custom output file + return new CustomOutputFile(s); + } + + @Override + public void deleteFile(String path) { + Path toDelete = new Path(path); + FileSystem fs = Util.getFs(toDelete); + try { + fs.delete(toDelete, false /* not recursive */); + } catch (IOException e) { + throw new RuntimeIOException(e, "Failed to delete file: %s", path); + } + } + + // implement this method to read catalog properties during initialization + public void initialize(Map properties) { + } +} +``` + +If you are already implementing your own catalog, you can implement `TableOperations.io()` to use your custom `FileIO`. +In addition, custom `FileIO` implementations can also be dynamically loaded in `HadoopCatalog` and `HiveCatalog` by specifying the `io-impl` catalog property. +Read the [Configuration](configuration.md#catalog-properties) section for more details. +If your `FileIO` must read Hadoop configuration to access certain environment properties, make your `FileIO` implement `org.apache.hadoop.conf.Configurable`. + +### Custom location provider implementation + +Extend `LocationProvider` and provide implementation to determine the file path to write data + +Example: +```java +public class CustomLocationProvider implements LocationProvider { + + private String tableLocation; + + // must have a 2-arg constructor like this, or a no-arg constructor + public CustomLocationProvider(String tableLocation, Map properties) { + this.tableLocation = tableLocation; + } + + @Override + public String newDataLocation(String filename) { + // can use any custom method to generate a file path given a file name + return String.format("%s/%s/%s", tableLocation, UUID.randomUUID().toString(), filename); + } + + @Override + public String newDataLocation(PartitionSpec spec, StructLike partitionData, String filename) { + // can use any custom method to generate a file path given a partition info and file name + return newDataLocation(filename); + } +} +``` + +If you are already implementing your own catalog, you can override `TableOperations.locationProvider()` to use your custom default `LocationProvider`. +To use a different custom location provider for a specific table, specify the implementation when creating the table using table property `write.location-provider.impl` + +Example: +```sql +CREATE TABLE hive.default.my_table ( + id bigint, + data string, + category string) +USING iceberg +OPTIONS ( + 'write.location-provider.impl'='com.my.CustomLocationProvider' +) +PARTITIONED BY (category); +``` + +### Custom IcebergSource +Extend `IcebergSource` and provide implementation to read from `CustomCatalog` + +Example: +```java +public class CustomIcebergSource extends IcebergSource { + + @Override + protected Table findTable(DataSourceOptions options, Configuration conf) { + Optional path = options.get("path"); + Preconditions.checkArgument(path.isPresent(), "Cannot open table: path is not set"); + + // Read table from CustomCatalog + CustomCatalog catalog = new CustomCatalog(conf); + TableIdentifier tableIdentifier = TableIdentifier.parse(path.get()); + return catalog.loadTable(tableIdentifier); + } +} +``` + +Register the `CustomIcebergSource` by updating `META-INF/services/org.apache.spark.sql.sources.DataSourceRegister` with its fully qualified name diff --git a/site/docs/docs/nightly/docs/dell.md b/site/docs/docs/nightly/docs/dell.md new file mode 100644 index 000000000000..ddeebeca657d --- /dev/null +++ b/site/docs/docs/nightly/docs/dell.md @@ -0,0 +1,130 @@ +--- +title: "Dell" +--- + + + +# Iceberg Dell Integration + +## Dell ECS Integration + +Iceberg can be used with Dell's Enterprise Object Storage (ECS) by using the ECS catalog since 0.15.0. + +See [Dell ECS](https://www.dell.com/en-us/dt/storage/ecs/index.htm) for more information on Dell ECS. + +### Parameters + +When using Dell ECS with Iceberg, these configuration parameters are required: + +| Name | Description | +| ------------------------ | --------------------------------- | +| ecs.s3.endpoint | ECS S3 service endpoint | +| ecs.s3.access-key-id | ECS Username | +| ecs.s3.secret-access-key | S3 Secret Key | +| warehouse | The location of data and metadata | + +The warehouse should use the following formats: + +| Example | Description | +| -------------------------- | --------------------------------------------------------------- | +| ecs://bucket-a | Use the whole bucket as the data | +| ecs://bucket-a/ | Use the whole bucket as the data. The last `/` is ignored. | +| ecs://bucket-a/namespace-a | Use a prefix to access the data only in this specific namespace | + +The Iceberg `runtime` jar supports different versions of Spark and Flink. You should pick the correct version. + +Even though the [Dell ECS client](https://github.com/EMCECS/ecs-object-client-java) jar is backward compatible, Dell EMC still recommends using the latest version of the client. + +### Spark + +To use the Dell ECS catalog with Spark 3.2.1, you should create a Spark session like: + +```bash +ICEBERG_VERSION=0.15.0 +SPARK_VERSION=3.2_2.12 +ECS_CLIENT_VERSION=3.3.2 + +DEPENDENCIES="org.apache.iceberg:iceberg-spark-runtime-${SPARK_VERSION}:${ICEBERG_VERSION},\ +org.apache.iceberg:iceberg-dell:${ICEBERG_VERSION},\ +com.emc.ecs:object-client-bundle:${ECS_CLIENT_VERSION}" + +spark-sql --packages ${DEPENDENCIES} \ + --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \ + --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=ecs://bucket-a/namespace-a \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.dell.ecs.EcsCatalog \ + --conf spark.sql.catalog.my_catalog.ecs.s3.endpoint=http://10.x.x.x:9020 \ + --conf spark.sql.catalog.my_catalog.ecs.s3.access-key-id= \ + --conf spark.sql.catalog.my_catalog.ecs.s3.secret-access-key= +``` + +Then, use `my_catalog` to access the data in ECS. You can use `SHOW NAMESPACES IN my_catalog` and `SHOW TABLES IN my_catalog` to fetch the namespaces and tables of the catalog. + +The related problems of catalog usage: + +1. The `SparkSession.catalog` won't access the 3rd-party catalog of Spark in both Python and Scala, so please use DDL SQL to list all tables and namespaces. + + +### Flink + +Use the Dell ECS catalog with Flink, you first must create a Flink environment. + +```bash +# HADOOP_HOME is your hadoop root directory after unpack the binary package. +export HADOOP_CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath` + +# download Iceberg dependency +MAVEN_URL=https://repo1.maven.org/maven2 +ICEBERG_VERSION=0.15.0 +FLINK_VERSION=1.14 +wget ${MAVEN_URL}/org/apache/iceberg/iceberg-flink-runtime-${FLINK_VERSION}/${ICEBERG_VERSION}/iceberg-flink-runtime-${FLINK_VERSION}-${ICEBERG_VERSION}.jar +wget ${MAVEN_URL}/org/apache/iceberg/iceberg-dell/${ICEBERG_VERSION}/iceberg-dell-${ICEBERG_VERSION}.jar + +# download ECS object client +ECS_CLIENT_VERSION=3.3.2 +wget ${MAVEN_URL}/com/emc/ecs/object-client-bundle/${ECS_CLIENT_VERSION}/object-client-bundle-${ECS_CLIENT_VERSION}.jar + +# open the SQL client. +/path/to/bin/sql-client.sh embedded \ + -j iceberg-flink-runtime-${FLINK_VERSION}-${ICEBERG_VERSION}.jar \ + -j iceberg-dell-${ICEBERG_VERSION}.jar \ + -j object-client-bundle-${ECS_CLIENT_VERSION}.jar \ + shell +``` + +Then, use Flink SQL to create a catalog named `my_catalog`: + +```SQL +CREATE CATALOG my_catalog WITH ( + 'type'='iceberg', + 'warehouse' = 'ecs://bucket-a/namespace-a', + 'catalog-impl'='org.apache.iceberg.dell.ecs.EcsCatalog', + 'ecs.s3.endpoint' = 'http://10.x.x.x:9020', + 'ecs.s3.access-key-id' = '', + 'ecs.s3.secret-access-key' = '') +``` + +Then, you can run `USE CATALOG my_catalog`, `SHOW DATABASES`, and `SHOW TABLES` to fetch the namespaces and tables of the catalog. + +### Limitations + +When you use the catalog with Dell ECS only, you should care about these limitations: + +1. `RENAME` statements are supported without other protections. When you try to rename a table, you need to guarantee all commits are finished in the original table. +2. `RENAME` statements only rename the table without moving any data files. This can lead to a table's data being stored in a path outside of the configured warehouse path. +3. The CAS operations used by table commits are based on the checksum of the object. There is a very small probability of a checksum conflict. diff --git a/site/docs/docs/nightly/docs/delta-lake-migration.md b/site/docs/docs/nightly/docs/delta-lake-migration.md new file mode 100644 index 000000000000..45f4f09593ce --- /dev/null +++ b/site/docs/docs/nightly/docs/delta-lake-migration.md @@ -0,0 +1,118 @@ +--- +title: "Delta Lake Migration" +--- + + +# Delta Lake Table Migration +Delta Lake is a table format that supports Parquet file format and provides time travel and versioning features. When migrating data from Delta Lake to Iceberg, +it is common to migrate all snapshots to maintain the history of the data. + +Currently, Iceberg supports the Snapshot Table action for migrating from Delta Lake to Iceberg tables. +Since Delta Lake tables maintain transactions, all available transactions will be committed to the new Iceberg table as transactions in order. +For Delta Lake tables, any additional data files added after the initial migration will be included in their corresponding transactions and subsequently added to the new Iceberg table using the Add Transaction action. +The Add Transaction action, a variant of the Add File action, is still under development. + +## Enabling Migration from Delta Lake to Iceberg +The `iceberg-delta-lake` module is not bundled with Spark and Flink engine runtimes. To enable migration from delta lake features, the minimum required dependencies are: + + - [iceberg-delta-lake](https://repo1.maven.org/maven2/org/apache/iceberg/iceberg-delta-lake/1.2.1/iceberg-delta-lake-1.2.1.jar) + - [delta-standalone-0.6.0](https://repo1.maven.org/maven2/io/delta/delta-standalone_2.13/0.6.0/delta-standalone_2.13-0.6.0.jar) + - [delta-storage-2.2.0](https://repo1.maven.org/maven2/io/delta/delta-storage/2.2.0/delta-storage-2.2.0.jar) + +### Compatibilities +The module is built and tested with `Delta Standalone:0.6.0` and supports Delta Lake tables with the following protocol version: +* `minReaderVersion`: 1 +* `minWriterVersion`: 2 + +Please refer to [Delta Lake Table Protocol Versioning](https://docs.delta.io/latest/versioning.html) for more details about Delta Lake protocol versions. + +### API +The `iceberg-delta-lake` module provides an interface named `DeltaLakeToIcebergMigrationActionsProvider`, which contains actions that helps converting from Delta Lake to Iceberg. +The supported actions are: +* `snapshotDeltaLakeTable`: snapshot an existing Delta Lake table to an Iceberg table + +### Default Implementation +The `iceberg-delta-lake` module also provides a default implementation of the interface which can be accessed by +```java +DeltaLakeToIcebergMigrationActionsProvider defaultActions = DeltaLakeToIcebergMigrationActionsProvider.defaultActions() +``` + +## Snapshot Delta Lake Table to Iceberg +The action `snapshotDeltaLakeTable` reads the Delta Lake table's transactions and converts them to a new Iceberg table with the same schema and partitioning in one iceberg transaction. +The original Delta Lake table remains unchanged. + +The newly created table can be changed or written to without affecting the source table, but the snapshot uses the original table's data files. +Existing data files are added to the Iceberg table's metadata and can be read using a name-to-id mapping created from the original table schema. + +When inserts or overwrites run on the snapshot, new files are placed in the snapshot table's location. The location is default to be the same as that +of the source Delta Lake Table. Users can also specify a different location for the snapshot table. + +!!! info + Because tables created by `snapshotDeltaLakeTable` are not the sole owners of their data files, they are prohibited from + actions like `expire_snapshots` which would physically delete data files. Iceberg deletes, which only effect metadata, + are still allowed. In addition, any operations which affect the original data files will disrupt the Snapshot's + integrity. DELETE statements executed against the original Delta Lake table will remove original data files and the + `snapshotDeltaLakeTable` table will no longer be able to access them. + + +#### Usage +| Required Input | Configured By | Description | +|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------| +| Source Table Location | Argument [`sourceTableLocation`](../../javadoc/latest/org/apache/iceberg/delta/DeltaLakeToIcebergMigrationActionsProvider.html#snapshotDeltaLakeTable(java.lang.String)) | The location of the source Delta Lake table | +| New Iceberg Table Identifier | Configuration API [`as`](../../javadoc/latest/org/apache/iceberg/delta/SnapshotDeltaLakeTable.html#as(org.apache.iceberg.catalog.TableIdentifier)) | The identifier specifies the namespace and table name for the new iceberg table | +| Iceberg Catalog | Configuration API [`icebergCatalog`](../../javadoc/latest/org/apache/iceberg/delta/SnapshotDeltaLakeTable.html#icebergCatalog(org.apache.iceberg.catalog.Catalog)) | The catalog used to create the new iceberg table | +| Hadoop Configuration | Configuration API [`deltaLakeConfiguration`](../../javadoc/latest/org/apache/iceberg/delta/SnapshotDeltaLakeTable.html#deltaLakeConfiguration(org.apache.hadoop.conf.Configuration)) | The Hadoop Configuration used to read the source Delta Lake table. | + +For detailed usage and other optional configurations, please refer to the [SnapshotDeltaLakeTable API](../../javadoc/latest/org/apache/iceberg/delta/SnapshotDeltaLakeTable.html) + +#### Output +| Output Name | Type | Description | +| ------------|------|-------------| +| `imported_files_count` | long | Number of files added to the new table | + +#### Added Table Properties +The following table properties are added to the Iceberg table to be created by default: + +| Property Name | Value | Description | +|-------------------------------|-------------------------------------------|--------------------------------------------------------------------| +| `snapshot_source` | `delta` | Indicates that the table is snapshot from a delta lake table | +| `original_location` | location of the delta lake table | The absolute path to the location of the original delta lake table | +| `schema.name-mapping.default` | JSON name mapping derived from the schema | The name mapping string used to read Delta Lake table's data files | + +#### Examples +```java +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.catalog.Catalog; +import org.apache.hadoop.conf.Configuration; +import org.apache.iceberg.delta.DeltaLakeToIcebergMigrationActionsProvider; + +String sourceDeltaLakeTableLocation = "s3://my-bucket/delta-table"; +String destTableLocation = "s3://my-bucket/iceberg-table"; +TableIdentifier destTableIdentifier = TableIdentifier.of("my_db", "my_table"); +Catalog icebergCatalog = ...; // Iceberg Catalog fetched from engines like Spark or created via CatalogUtil.loadCatalog +Configuration hadoopConf = ...; // Hadoop Configuration fetched from engines like Spark and have proper file system configuration to access the Delta Lake table. + +DeltaLakeToIcebergMigrationActionsProvider.defaultActions() + .snapshotDeltaLakeTable(sourceDeltaLakeTableLocation) + .as(destTableIdentifier) + .icebergCatalog(icebergCatalog) + .tableLocation(destTableLocation) + .deltaLakeConfiguration(hadoopConf) + .tableProperty("my_property", "my_value") + .execute(); +``` diff --git a/site/docs/docs/nightly/docs/evolution.md b/site/docs/docs/nightly/docs/evolution.md new file mode 100644 index 000000000000..98bdbd27c0c5 --- /dev/null +++ b/site/docs/docs/nightly/docs/evolution.md @@ -0,0 +1,101 @@ +--- +title: Evolution +--- + + +# Evolution + +Iceberg supports **in-place table evolution**. You can [evolve a table schema](#schema-evolution) just like SQL -- even in nested structures -- or [change partition layout](#partition-evolution) when data volume changes. Iceberg does not require costly distractions, like rewriting table data or migrating to a new table. + +For example, Hive table partitioning cannot change so moving from a daily partition layout to an hourly partition layout requires a new table. And because queries are dependent on partitions, queries must be rewritten for the new table. In some cases, even changes as simple as renaming a column are either not supported, or can cause [data correctness](#correctness) problems. + +## Schema evolution + +Iceberg supports the following schema evolution changes: + +* **Add** -- add a new column to the table or to a nested struct +* **Drop** -- remove an existing column from the table or a nested struct +* **Rename** -- rename an existing column or field in a nested struct +* **Update** -- widen the type of a column, struct field, map key, map value, or list element +* **Reorder** -- change the order of columns or fields in a nested struct + +Iceberg schema updates are **metadata changes**, so no data files need to be rewritten to perform the update. + +Note that map keys do not support adding or dropping struct fields that would change equality. + +### Correctness + +Iceberg guarantees that **schema evolution changes are independent and free of side-effects**, without rewriting files: + +1. Added columns never read existing values from another column. +2. Dropping a column or field does not change the values in any other column. +3. Updating a column or field does not change values in any other column. +4. Changing the order of columns or fields in a struct does not change the values associated with a column or field name. + +Iceberg uses unique IDs to track each column in a table. When you add a column, it is assigned a new ID so existing data is never used by mistake. + +* Formats that track columns by name can inadvertently un-delete a column if a name is reused, which violates #1. +* Formats that track columns by position cannot delete columns without changing the names that are used for each column, which violates #2. + + +## Partition evolution + +Iceberg table partitioning can be updated in an existing table because queries do not reference partition values directly. + +When you evolve a partition spec, the old data written with an earlier spec remains unchanged. New data is written using the new spec in a new layout. Metadata for each of the partition versions is kept separately. Because of this, when you start writing queries, you get split planning. This is where each partition layout plans files separately using the filter it derives for that specific partition layout. Here's a visual representation of a contrived example: + +![Partition evolution diagram](assets/images/partition-spec-evolution.png) +*The data for 2008 is partitioned by month. Starting from 2009 the table is updated so that the data is instead partitioned by day. Both partitioning layouts are able to coexist in the same table.* + +Iceberg uses [hidden partitioning](partitioning.md), so you don't *need* to write queries for a specific partition layout to be fast. Instead, you can write queries that select the data you need, and Iceberg automatically prunes out files that don't contain matching data. + +Partition evolution is a metadata operation and does not eagerly rewrite files. + +Iceberg's Java table API provides `updateSpec` API to update partition spec. +For example, the following code could be used to update the partition spec to add a new partition field that places `id` column values into 8 buckets and remove an existing partition field `category`: + +```java +Table sampleTable = ...; +sampleTable.updateSpec() + .addField(bucket("id", 8)) + .removeField("category") + .commit(); +``` + +Spark supports updating partition spec through its `ALTER TABLE` SQL statement, see more details in [Spark SQL](spark-ddl.md#alter-table-add-partition-field). + +## Sort order evolution + +Similar to partition spec, Iceberg sort order can also be updated in an existing table. +When you evolve a sort order, the old data written with an earlier order remains unchanged. +Engines can always choose to write data in the latest sort order or unsorted when sorting is prohibitively expensive. + +Iceberg's Java table API provides `replaceSortOrder` API to update sort order. +For example, the following code could be used to create a new sort order +with `id` column sorted in ascending order with nulls last, +and `category` column sorted in descending order with nulls first: + +```java +Table sampleTable = ...; +sampleTable.replaceSortOrder() + .asc("id", NullOrder.NULLS_LAST) + .dec("category", NullOrder.NULL_FIRST) + .commit(); +``` + +Spark supports updating sort order through its `ALTER TABLE` SQL statement, see more details in [Spark SQL](spark-ddl.md#alter-table-write-ordered-by). diff --git a/site/docs/docs/nightly/docs/flink-actions.md b/site/docs/docs/nightly/docs/flink-actions.md new file mode 100644 index 000000000000..ca67ef0e5f8f --- /dev/null +++ b/site/docs/docs/nightly/docs/flink-actions.md @@ -0,0 +1,35 @@ +--- +title: "Flink Actions" +--- + + +## Rewrite files action. + +Iceberg provides API to rewrite small files into large files by submitting Flink batch jobs. The behavior of this Flink action is the same as Spark's [rewriteDataFiles](maintenance.md#compact-data-files). + +```java +import org.apache.iceberg.flink.actions.Actions; + +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path"); +Table table = tableLoader.loadTable(); +RewriteDataFilesActionResult result = Actions.forTable(table) + .rewriteDataFiles() + .execute(); +``` + +For more details of the rewrite files action, please refer to [RewriteDataFilesAction](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/flink/actions/RewriteDataFilesAction.html) diff --git a/site/docs/docs/nightly/docs/flink-configuration.md b/site/docs/docs/nightly/docs/flink-configuration.md new file mode 100644 index 000000000000..d3725947d902 --- /dev/null +++ b/site/docs/docs/nightly/docs/flink-configuration.md @@ -0,0 +1,158 @@ +--- +title: "Flink Configuration" +--- + + +# Flink Configuration + +## Catalog Configuration + +A catalog is created and named by executing the following query (replace `` with your catalog name and +``=`` with catalog implementation config): + +```sql +CREATE CATALOG WITH ( + 'type'='iceberg', + ``=`` +); +``` + +The following properties can be set globally and are not limited to a specific catalog implementation: + +| Property | Required | Values | Description | +| ---------------------------- |----------| -------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| type | ✔️ | iceberg | Must be `iceberg`. | +| catalog-type | | `hive`, `hadoop` or `rest` | `hive`, `hadoop` or `rest` for built-in catalogs, or left unset for custom catalog implementations using catalog-impl. | +| catalog-impl | | | The fully-qualified class name of a custom catalog implementation. Must be set if `catalog-type` is unset. | +| property-version | | | Version number to describe the property version. This property can be used for backwards compatibility in case the property format changes. The current property version is `1`. | +| cache-enabled | | `true` or `false` | Whether to enable catalog cache, default value is `true`. | +| cache.expiration-interval-ms | | | How long catalog entries are locally cached, in milliseconds; negative values like `-1` will disable expiration, value 0 is not allowed to set. default value is `-1`. | + +The following properties can be set if using the Hive catalog: + +| Property | Required | Values | Description | +| --------------- |----------| ------ |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| uri | ✔️ | | The Hive metastore's thrift URI. | +| clients | | | The Hive metastore client pool size, default value is 2. | +| warehouse | | | The Hive warehouse location, users should specify this path if neither set the `hive-conf-dir` to specify a location containing a `hive-site.xml` configuration file nor add a correct `hive-site.xml` to classpath. | +| hive-conf-dir | | | Path to a directory containing a `hive-site.xml` configuration file which will be used to provide custom Hive configuration values. The value of `hive.metastore.warehouse.dir` from `/hive-site.xml` (or hive configure file from classpath) will be overwritten with the `warehouse` value if setting both `hive-conf-dir` and `warehouse` when creating iceberg catalog. | +| hadoop-conf-dir | | | Path to a directory containing `core-site.xml` and `hdfs-site.xml` configuration files which will be used to provide custom Hadoop configuration values. | + +The following properties can be set if using the Hadoop catalog: + +| Property | Required | Values | Description | +| --------- |-------------| ------ | ---------------------------------------------------------- | +| warehouse | ✔️ | | The HDFS directory to store metadata files and data files. | + +The following properties can be set if using the REST catalog: + +| Property | Required | Values | Description | +| ---------- |----------| ------ |-----------------------------------------------------------------------------| +| uri | ✔️ | | The URL to the REST Catalog. | +| credential | | | A credential to exchange for a token in the OAuth2 client credentials flow. | +| token | | | A token which will be used to interact with the server. | + + +## Runtime configuration + +### Read options + +Flink read options are passed when configuring the Flink IcebergSource: + +``` +IcebergSource.forRowData() + .tableLoader(TableLoader.fromCatalog(...)) + .assignerFactory(new SimpleSplitAssignerFactory()) + .streaming(true) + .streamingStartingStrategy(StreamingStartingStrategy.INCREMENTAL_FROM_LATEST_SNAPSHOT) + .startSnapshotId(3821550127947089987L) + .monitorInterval(Duration.ofMillis(10L)) // or .set("monitor-interval", "10s") \ set(FlinkReadOptions.MONITOR_INTERVAL, "10s") + .build() +``` + +For Flink SQL, read options can be passed in via SQL hints like this: + +``` +SELECT * FROM tableName /*+ OPTIONS('monitor-interval'='10s') */ +... +``` + +Options can be passed in via Flink configuration, which will be applied to current session. Note that not all options support this mode. + +``` +env.getConfig() + .getConfiguration() + .set(FlinkReadOptions.SPLIT_FILE_OPEN_COST_OPTION, 1000L); +... +``` + +`Read option` has the highest priority, followed by `Flink configuration` and then `Table property`. + +| Read option | Flink configuration | Table property | Default | Description | +| --------------------------- | --------------------------------------------- | ---------------------------- | -------------------------------- | ------------------------------------------------------------ | +| snapshot-id | N/A | N/A | null | For time travel in batch mode. Read data from the specified snapshot-id. | +| case-sensitive | connector.iceberg.case-sensitive | N/A | false | If true, match column name in a case sensitive way. | +| as-of-timestamp | N/A | N/A | null | For time travel in batch mode. Read data from the most recent snapshot as of the given time in milliseconds. | +| starting-strategy | connector.iceberg.starting-strategy | N/A | INCREMENTAL_FROM_LATEST_SNAPSHOT | Starting strategy for streaming execution. TABLE_SCAN_THEN_INCREMENTAL: Do a regular table scan then switch to the incremental mode. The incremental mode starts from the current snapshot exclusive. INCREMENTAL_FROM_LATEST_SNAPSHOT: Start incremental mode from the latest snapshot inclusive. If it is an empty map, all future append snapshots should be discovered. INCREMENTAL_FROM_EARLIEST_SNAPSHOT: Start incremental mode from the earliest snapshot inclusive. If it is an empty map, all future append snapshots should be discovered. INCREMENTAL_FROM_SNAPSHOT_ID: Start incremental mode from a snapshot with a specific id inclusive. INCREMENTAL_FROM_SNAPSHOT_TIMESTAMP: Start incremental mode from a snapshot with a specific timestamp inclusive. If the timestamp is between two snapshots, it should start from the snapshot after the timestamp. Just for FIP27 Source. | +| start-snapshot-timestamp | N/A | N/A | null | Start to read data from the most recent snapshot as of the given time in milliseconds. | +| start-snapshot-id | N/A | N/A | null | Start to read data from the specified snapshot-id. | +| end-snapshot-id | N/A | N/A | The latest snapshot id | Specifies the end snapshot. +| branch | N/A | N/A | main | Specifies the branch to read from in batch mode +| tag | N/A | N/A | null | Specifies the tag to read from in batch mode +| start-tag | N/A | N/A | null | Specifies the starting tag to read from for incremental reads +| end-tag | N/A | N/A | null | Specifies the ending tag to to read from for incremental reads | +| split-size | connector.iceberg.split-size | read.split.target-size | 128 MB | Target size when combining input splits. | +| split-lookback | connector.iceberg.split-file-open-cost | read.split.planning-lookback | 10 | Number of bins to consider when combining input splits. | +| split-file-open-cost | connector.iceberg.split-file-open-cost | read.split.open-file-cost | 4MB | The estimated cost to open a file, used as a minimum weight when combining splits. | +| streaming | connector.iceberg.streaming | N/A | false | Sets whether the current task runs in streaming or batch mode. | +| monitor-interval | connector.iceberg.monitor-interval | N/A | 60s | Monitor interval to discover splits from new snapshots. Applicable only for streaming read. | +| include-column-stats | connector.iceberg.include-column-stats | N/A | false | Create a new scan from this that loads the column stats with each data file. Column stats include: value count, null value count, lower bounds, and upper bounds. | +| max-planning-snapshot-count | connector.iceberg.max-planning-snapshot-count | N/A | Integer.MAX_VALUE | Max number of snapshots limited per split enumeration. Applicable only to streaming read. | +| limit | connector.iceberg.limit | N/A | -1 | Limited output number of rows. | + + +### Write options + +Flink write options are passed when configuring the FlinkSink, like this: + +``` +FlinkSink.Builder builder = FlinkSink.forRow(dataStream, SimpleDataUtil.FLINK_SCHEMA) + .table(table) + .tableLoader(tableLoader) + .set("write-format", "orc") + .set(FlinkWriteOptions.OVERWRITE_MODE, "true"); +``` + +For Flink SQL, write options can be passed in via SQL hints like this: + +``` +INSERT INTO tableName /*+ OPTIONS('upsert-enabled'='true') */ +... +``` + +| Flink option | Default | Description | +| ---------------------- | ------------------------------------------ | ------------------------------------------------------------ | +| write-format | Table write.format.default | File format to use for this write operation; parquet, avro, or orc | +| target-file-size-bytes | As per table property | Overrides this table's write.target-file-size-bytes | +| upsert-enabled | Table write.upsert.enabled | Overrides this table's write.upsert.enabled | +| overwrite-enabled | false | Overwrite the table's data, overwrite mode shouldn't be enable when configuring to use UPSERT data stream. | +| distribution-mode | Table write.distribution-mode | Overrides this table's write.distribution-mode | +| compression-codec | Table write.(fileformat).compression-codec | Overrides this table's compression codec for this write | +| compression-level | Table write.(fileformat).compression-level | Overrides this table's compression level for Parquet and Avro tables for this write | +| compression-strategy | Table write.orc.compression-strategy | Overrides this table's compression strategy for ORC tables for this write | +| write-parallelism | Upstream operator parallelism | Overrides the writer parallelism | diff --git a/site/docs/docs/nightly/docs/flink-connector.md b/site/docs/docs/nightly/docs/flink-connector.md new file mode 100644 index 000000000000..025e9aee92ea --- /dev/null +++ b/site/docs/docs/nightly/docs/flink-connector.md @@ -0,0 +1,141 @@ +--- +title: "Flink Connector" +--- + + +# Flink Connector +Apache Flink supports creating Iceberg table directly without creating the explicit Flink catalog in Flink SQL. That means we can just create an iceberg table by specifying `'connector'='iceberg'` table option in Flink SQL which is similar to usage in the Flink official [document](https://nightlies.apache.org/flink/flink-docs-release-1.13/docs/connectors/table/overview/). + +In Flink, the SQL `CREATE TABLE test (..) WITH ('connector'='iceberg', ...)` will create a Flink table in current Flink catalog (use [GenericInMemoryCatalog](https://ci.apache.org/projects/flink/flink-docs-release-1.13/docs/dev/table/catalogs/#genericinmemorycatalog) by default), +which is just mapping to the underlying iceberg table instead of maintaining iceberg table directly in current Flink catalog. + +To create the table in Flink SQL by using SQL syntax `CREATE TABLE test (..) WITH ('connector'='iceberg', ...)`, Flink iceberg connector provides the following table properties: + +* `connector`: Use the constant `iceberg`. +* `catalog-name`: User-specified catalog name. It's required because the connector don't have any default value. +* `catalog-type`: `hive` or `hadoop` for built-in catalogs (defaults to `hive`), or left unset for custom catalog implementations using `catalog-impl`. +* `catalog-impl`: The fully-qualified class name of a custom catalog implementation. Must be set if `catalog-type` is unset. See also [custom catalog](flink.md#adding-catalogs) for more details. +* `catalog-database`: The iceberg database name in the backend catalog, use the current flink database name by default. +* `catalog-table`: The iceberg table name in the backend catalog. Default to use the table name in the flink `CREATE TABLE` sentence. + +## Table managed in Hive catalog. + +Before executing the following SQL, please make sure you've configured the Flink SQL client correctly according to the [quick start documentation](flink.md). + +The following SQL will create a Flink table in the current Flink catalog, which maps to the iceberg table `default_database.flink_table` managed in iceberg catalog. + +```sql +CREATE TABLE flink_table ( + id BIGINT, + data STRING +) WITH ( + 'connector'='iceberg', + 'catalog-name'='hive_prod', + 'uri'='thrift://localhost:9083', + 'warehouse'='hdfs://nn:8020/path/to/warehouse' +); +``` + +If you want to create a Flink table mapping to a different iceberg table managed in Hive catalog (such as `hive_db.hive_iceberg_table` in Hive), then you can create Flink table as following: + +```sql +CREATE TABLE flink_table ( + id BIGINT, + data STRING +) WITH ( + 'connector'='iceberg', + 'catalog-name'='hive_prod', + 'catalog-database'='hive_db', + 'catalog-table'='hive_iceberg_table', + 'uri'='thrift://localhost:9083', + 'warehouse'='hdfs://nn:8020/path/to/warehouse' +); +``` + +!!! info + The underlying catalog database (`hive_db` in the above example) will be created automatically if it does not exist when writing records into the Flink table. + + +## Table managed in hadoop catalog + +The following SQL will create a Flink table in current Flink catalog, which maps to the iceberg table `default_database.flink_table` managed in hadoop catalog. + +```sql +CREATE TABLE flink_table ( + id BIGINT, + data STRING +) WITH ( + 'connector'='iceberg', + 'catalog-name'='hadoop_prod', + 'catalog-type'='hadoop', + 'warehouse'='hdfs://nn:8020/path/to/warehouse' +); +``` + +## Table managed in custom catalog + +The following SQL will create a Flink table in current Flink catalog, which maps to the iceberg table `default_database.flink_table` managed in +a custom catalog of type `com.my.custom.CatalogImpl`. + +```sql +CREATE TABLE flink_table ( + id BIGINT, + data STRING +) WITH ( + 'connector'='iceberg', + 'catalog-name'='custom_prod', + 'catalog-impl'='com.my.custom.CatalogImpl', + -- More table properties for the customized catalog + 'my-additional-catalog-config'='my-value', + ... +); +``` + +Please check sections under the Integrations tab for all custom catalogs. + +## A complete example. + +Take the Hive catalog as an example: + +```sql +CREATE TABLE flink_table ( + id BIGINT, + data STRING +) WITH ( + 'connector'='iceberg', + 'catalog-name'='hive_prod', + 'uri'='thrift://localhost:9083', + 'warehouse'='file:///path/to/warehouse' +); + +INSERT INTO flink_table VALUES (1, 'AAA'), (2, 'BBB'), (3, 'CCC'); + +SET execution.result-mode=tableau; +SELECT * FROM flink_table; + ++----+------+ +| id | data | ++----+------+ +| 1 | AAA | +| 2 | BBB | +| 3 | CCC | ++----+------+ +3 rows in set +``` + +For more details, please refer to the Iceberg [Flink documentation](flink.md). diff --git a/site/docs/docs/nightly/docs/flink-ddl.md b/site/docs/docs/nightly/docs/flink-ddl.md new file mode 100644 index 000000000000..fc44b1b7a54d --- /dev/null +++ b/site/docs/docs/nightly/docs/flink-ddl.md @@ -0,0 +1,206 @@ +--- +title: "Flink DDL" +--- + + +## DDL commands + +### `CREATE Catalog` + +#### Hive catalog + +This creates an Iceberg catalog named `hive_catalog` that can be configured using `'catalog-type'='hive'`, which loads tables from Hive metastore: + +```sql +CREATE CATALOG hive_catalog WITH ( + 'type'='iceberg', + 'catalog-type'='hive', + 'uri'='thrift://localhost:9083', + 'clients'='5', + 'property-version'='1', + 'warehouse'='hdfs://nn:8020/warehouse/path' +); +``` + +The following properties can be set if using the Hive catalog: + +* `uri`: The Hive metastore's thrift URI. (Required) +* `clients`: The Hive metastore client pool size, default value is 2. (Optional) +* `warehouse`: The Hive warehouse location, users should specify this path if neither set the `hive-conf-dir` to specify a location containing a `hive-site.xml` configuration file nor add a correct `hive-site.xml` to classpath. +* `hive-conf-dir`: Path to a directory containing a `hive-site.xml` configuration file which will be used to provide custom Hive configuration values. The value of `hive.metastore.warehouse.dir` from `/hive-site.xml` (or hive configure file from classpath) will be overwritten with the `warehouse` value if setting both `hive-conf-dir` and `warehouse` when creating iceberg catalog. +* `hadoop-conf-dir`: Path to a directory containing `core-site.xml` and `hdfs-site.xml` configuration files which will be used to provide custom Hadoop configuration values. + +#### Hadoop catalog + +Iceberg also supports a directory-based catalog in HDFS that can be configured using `'catalog-type'='hadoop'`: + +```sql +CREATE CATALOG hadoop_catalog WITH ( + 'type'='iceberg', + 'catalog-type'='hadoop', + 'warehouse'='hdfs://nn:8020/warehouse/path', + 'property-version'='1' +); +``` + +The following properties can be set if using the Hadoop catalog: + +* `warehouse`: The HDFS directory to store metadata files and data files. (Required) + +Execute the sql command `USE CATALOG hadoop_catalog` to set the current catalog. + +#### REST catalog + +This creates an iceberg catalog named `rest_catalog` that can be configured using `'catalog-type'='rest'`, which loads tables from a REST catalog: + +```sql +CREATE CATALOG rest_catalog WITH ( + 'type'='iceberg', + 'catalog-type'='rest', + 'uri'='https://localhost/' +); +``` + +The following properties can be set if using the REST catalog: + +* `uri`: The URL to the REST Catalog (Required) +* `credential`: A credential to exchange for a token in the OAuth2 client credentials flow (Optional) +* `token`: A token which will be used to interact with the server (Optional) + +#### Custom catalog + +Flink also supports loading a custom Iceberg `Catalog` implementation by specifying the `catalog-impl` property: + +```sql +CREATE CATALOG my_catalog WITH ( + 'type'='iceberg', + 'catalog-impl'='com.my.custom.CatalogImpl', + 'my-additional-catalog-config'='my-value' +); +``` + +#### Create through YAML config + +Catalogs can be registered in `sql-client-defaults.yaml` before starting the SQL client. + +```yaml +catalogs: + - name: my_catalog + type: iceberg + catalog-type: hadoop + warehouse: hdfs://nn:8020/warehouse/path +``` + +#### Create through SQL Files + +The Flink SQL Client supports the `-i` startup option to execute an initialization SQL file to set up environment when starting up the SQL Client. + +```sql +-- define available catalogs +CREATE CATALOG hive_catalog WITH ( + 'type'='iceberg', + 'catalog-type'='hive', + 'uri'='thrift://localhost:9083', + 'warehouse'='hdfs://nn:8020/warehouse/path' +); + +USE CATALOG hive_catalog; +``` + +Using `-i ` option to initialize SQL Client session: + +```bash +/path/to/bin/sql-client.sh -i /path/to/init.sql +``` + +### `CREATE DATABASE` + +By default, Iceberg will use the `default` database in Flink. Using the following example to create a separate database in order to avoid creating tables under the `default` database: + +```sql +CREATE DATABASE iceberg_db; +USE iceberg_db; +``` + +### `CREATE TABLE` + +```sql +CREATE TABLE `hive_catalog`.`default`.`sample` ( + id BIGINT COMMENT 'unique id', + data STRING +); +``` + +Table create commands support the commonly used [Flink create clauses](https://nightlies.apache.org/flink/flink-docs-master/docs/dev/table/sql/create/) including: + +* `PARTITION BY (column1, column2, ...)` to configure partitioning, Flink does not yet support hidden partitioning. +* `COMMENT 'table document'` to set a table description. +* `WITH ('key'='value', ...)` to set [table configuration](configuration.md) which will be stored in Iceberg table properties. + +Currently, it does not support computed column, primary key and watermark definition etc. + +### `PARTITIONED BY` + +To create a partition table, use `PARTITIONED BY`: + +```sql +CREATE TABLE `hive_catalog`.`default`.`sample` ( + id BIGINT COMMENT 'unique id', + data STRING +) PARTITIONED BY (data); +``` + +Iceberg support hidden partition but Flink don't support partitioning by a function on columns, so there is no way to support hidden partition in Flink DDL. + +### `CREATE TABLE LIKE` + +To create a table with the same schema, partitioning, and table properties as another table, use `CREATE TABLE LIKE`. + +```sql +CREATE TABLE `hive_catalog`.`default`.`sample` ( + id BIGINT COMMENT 'unique id', + data STRING +); + +CREATE TABLE `hive_catalog`.`default`.`sample_like` LIKE `hive_catalog`.`default`.`sample`; +``` + +For more details, refer to the [Flink `CREATE TABLE` documentation](https://nightlies.apache.org/flink/flink-docs-release-1.16/docs/dev/table/sql/create/). + + +### `ALTER TABLE` + +Iceberg only support altering table properties: + +```sql +ALTER TABLE `hive_catalog`.`default`.`sample` SET ('write.format.default'='avro') +``` + +### `ALTER TABLE .. RENAME TO` + +```sql +ALTER TABLE `hive_catalog`.`default`.`sample` RENAME TO `hive_catalog`.`default`.`new_sample`; +``` + +### `DROP TABLE` + +To delete a table, run: + +```sql +DROP TABLE `hive_catalog`.`default`.`sample`; +``` diff --git a/site/docs/docs/nightly/docs/flink-queries.md b/site/docs/docs/nightly/docs/flink-queries.md new file mode 100644 index 000000000000..c33d498587d5 --- /dev/null +++ b/site/docs/docs/nightly/docs/flink-queries.md @@ -0,0 +1,489 @@ +--- +title: "Flink Queries" +--- + + +# Flink Queries + +Iceberg support streaming and batch read With [Apache Flink](https://flink.apache.org/)'s DataStream API and Table API. + +## Reading with SQL + +Iceberg support both streaming and batch read in Flink. Execute the following sql command to switch execution mode from `streaming` to `batch`, and vice versa: + +```sql +-- Execute the flink job in streaming mode for current session context +SET execution.runtime-mode = streaming; + +-- Execute the flink job in batch mode for current session context +SET execution.runtime-mode = batch; +``` + +### Flink batch read + +Submit a Flink __batch__ job using the following sentences: + +```sql +-- Execute the flink job in batch mode for current session context +SET execution.runtime-mode = batch; +SELECT * FROM sample; +``` + +### Flink streaming read + +Iceberg supports processing incremental data in Flink streaming jobs which starts from a historical snapshot-id: + +```sql +-- Submit the flink job in streaming mode for current session. +SET execution.runtime-mode = streaming; + +-- Enable this switch because streaming read SQL will provide few job options in flink SQL hint options. +SET table.dynamic-table-options.enabled=true; + +-- Read all the records from the iceberg current snapshot, and then read incremental data starting from that snapshot. +SELECT * FROM sample /*+ OPTIONS('streaming'='true', 'monitor-interval'='1s')*/ ; + +-- Read all incremental data starting from the snapshot-id '3821550127947089987' (records from this snapshot will be excluded). +SELECT * FROM sample /*+ OPTIONS('streaming'='true', 'monitor-interval'='1s', 'start-snapshot-id'='3821550127947089987')*/ ; +``` + +There are some options that could be set in Flink SQL hint options for streaming job, see [read options](#read-options) for details. + +### FLIP-27 source for SQL + +Here are the SQL settings for the [FLIP-27](https://cwiki.apache.org/confluence/display/FLINK/FLIP-27%3A+Refactor+Source+Interface) source. All other SQL settings and options documented above are applicable to the FLIP-27 source. + +```sql +-- Opt in the FLIP-27 source. Default is false. +SET table.exec.iceberg.use-flip27-source = true; +``` + +### Reading branches and tags with SQL +Branch and tags can be read via SQL by specifying options. For more details +refer to [Flink Configuration](flink-configuration.md#read-options) + +```sql +--- Read from branch b1 +SELECT * FROM table /*+ OPTIONS('branch'='b1') */ ; + +--- Read from tag t1 +SELECT * FROM table /*+ OPTIONS('tag'='t1') */; + +--- Incremental scan from tag t1 to tag t2 +SELECT * FROM table /*+ OPTIONS('streaming'='true', 'monitor-interval'='1s', 'start-tag'='t1', 'end-tag'='t2') */; +``` + +## Reading with DataStream + +Iceberg support streaming or batch read in Java API now. + +### Batch Read + +This example will read all records from iceberg table and then print to the stdout console in flink batch job: + +```java +StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path"); +DataStream batch = FlinkSource.forRowData() + .env(env) + .tableLoader(tableLoader) + .streaming(false) + .build(); + +// Print all records to stdout. +batch.print(); + +// Submit and execute this batch read job. +env.execute("Test Iceberg Batch Read"); +``` + +### Streaming read + +This example will read incremental records which start from snapshot-id '3821550127947089987' and print to stdout console in flink streaming job: + +```java +StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path"); +DataStream stream = FlinkSource.forRowData() + .env(env) + .tableLoader(tableLoader) + .streaming(true) + .startSnapshotId(3821550127947089987L) + .build(); + +// Print all records to stdout. +stream.print(); + +// Submit and execute this streaming read job. +env.execute("Test Iceberg Streaming Read"); +``` + +There are other options that can be set, please see the [FlinkSource#Builder](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/flink/source/FlinkSource.html). + +## Reading with DataStream (FLIP-27 source) + +[FLIP-27 source interface](https://cwiki.apache.org/confluence/display/FLINK/FLIP-27%3A+Refactor+Source+Interface) +was introduced in Flink 1.12. It aims to solve several shortcomings of the old `SourceFunction` +streaming source interface. It also unifies the source interfaces for both batch and streaming executions. +Most source connectors (like Kafka, file) in Flink repo have migrated to the FLIP-27 interface. +Flink is planning to deprecate the old `SourceFunction` interface in the near future. + +A FLIP-27 based Flink `IcebergSource` is added in `iceberg-flink` module. The FLIP-27 `IcebergSource` is currently an experimental feature. + +### Batch Read + +This example will read all records from iceberg table and then print to the stdout console in flink batch job: + +```java +StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path"); + +IcebergSource source = IcebergSource.forRowData() + .tableLoader(tableLoader) + .assignerFactory(new SimpleSplitAssignerFactory()) + .build(); + +DataStream batch = env.fromSource( + source, + WatermarkStrategy.noWatermarks(), + "My Iceberg Source", + TypeInformation.of(RowData.class)); + +// Print all records to stdout. +batch.print(); + +// Submit and execute this batch read job. +env.execute("Test Iceberg Batch Read"); +``` + +### Streaming read + +This example will start the streaming read from the latest table snapshot (inclusive). +Every 60s, it polls Iceberg table to discover new append-only snapshots. +CDC read is not supported yet. + +```java +StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path"); + +IcebergSource source = IcebergSource.forRowData() + .tableLoader(tableLoader) + .assignerFactory(new SimpleSplitAssignerFactory()) + .streaming(true) + .streamingStartingStrategy(StreamingStartingStrategy.INCREMENTAL_FROM_LATEST_SNAPSHOT) + .monitorInterval(Duration.ofSeconds(60)) + .build() + +DataStream stream = env.fromSource( + source, + WatermarkStrategy.noWatermarks(), + "My Iceberg Source", + TypeInformation.of(RowData.class)); + +// Print all records to stdout. +stream.print(); + +// Submit and execute this streaming read job. +env.execute("Test Iceberg Streaming Read"); +``` + +There are other options that could be set by Java API, please see the +[IcebergSource#Builder](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/flink/source/IcebergSource.html). + +### Reading branches and tags with DataStream +Branches and tags can also be read via the DataStream API + +```java +StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path"); +// Read from branch +DataStream batch = FlinkSource.forRowData() + .env(env) + .tableLoader(tableLoader) + .branch("test-branch") + .streaming(false) + .build(); + +// Read from tag +DataStream batch = FlinkSource.forRowData() + .env(env) + .tableLoader(tableLoader) + .tag("test-tag") + .streaming(false) + .build(); + +// Streaming read from start-tag +DataStream batch = FlinkSource.forRowData() + .env(env) + .tableLoader(tableLoader) + .streaming(true) + .startTag("test-tag") + .build(); +``` + +### Read as Avro GenericRecord + +FLIP-27 Iceberg source provides `AvroGenericRecordReaderFunction` that converts +Flink `RowData` Avro `GenericRecord`. You can use the convert to read from +Iceberg table as Avro GenericRecord DataStream. + +Please make sure `flink-avro` jar is included in the classpath. +Also `iceberg-flink-runtime` shaded bundle jar can't be used +because the runtime jar shades the avro package. +Please use non-shaded `iceberg-flink` jar instead. + +```java +TableLoader tableLoader = ...; +Table table; +try (TableLoader loader = tableLoader) { + loader.open(); + table = loader.loadTable(); +} + +AvroGenericRecordReaderFunction readerFunction = AvroGenericRecordReaderFunction.fromTable(table); + +IcebergSource source = + IcebergSource.builder() + .tableLoader(tableLoader) + .readerFunction(readerFunction) + .assignerFactory(new SimpleSplitAssignerFactory()) + ... + .build(); + +DataStream stream = env.fromSource(source, WatermarkStrategy.noWatermarks(), + "Iceberg Source as Avro GenericRecord", new GenericRecordAvroTypeInfo(avroSchema)); +``` + +## Options + +### Read options + +Flink read options are passed when configuring the Flink IcebergSource: + +``` +IcebergSource.forRowData() + .tableLoader(TableLoader.fromCatalog(...)) + .assignerFactory(new SimpleSplitAssignerFactory()) + .streaming(true) + .streamingStartingStrategy(StreamingStartingStrategy.INCREMENTAL_FROM_LATEST_SNAPSHOT) + .startSnapshotId(3821550127947089987L) + .monitorInterval(Duration.ofMillis(10L)) // or .set("monitor-interval", "10s") \ set(FlinkReadOptions.MONITOR_INTERVAL, "10s") + .build() +``` + +For Flink SQL, read options can be passed in via SQL hints like this: + +``` +SELECT * FROM tableName /*+ OPTIONS('monitor-interval'='10s') */ +... +``` + +Options can be passed in via Flink configuration, which will be applied to current session. Note that not all options support this mode. + +``` +env.getConfig() + .getConfiguration() + .set(FlinkReadOptions.SPLIT_FILE_OPEN_COST_OPTION, 1000L); +... +``` + +Check out all the options here: [read-options](flink-configuration.md#read-options) + +## Inspecting tables + +To inspect a table's history, snapshots, and other metadata, Iceberg supports metadata tables. + +Metadata tables are identified by adding the metadata table name after the original table name. For example, history for `db.table` is read using `db.table$history`. + +### History + +To show table history: + +```sql +SELECT * FROM prod.db.table$history; +``` + +| made_current_at | snapshot_id | parent_id | is_current_ancestor | +| ----------------------- | ------------------- | ------------------- | ------------------- | +| 2019-02-08 03:29:51.215 | 5781947118336215154 | NULL | true | +| 2019-02-08 03:47:55.948 | 5179299526185056830 | 5781947118336215154 | true | +| 2019-02-09 16:24:30.13 | 296410040247533544 | 5179299526185056830 | false | +| 2019-02-09 16:32:47.336 | 2999875608062437330 | 5179299526185056830 | true | +| 2019-02-09 19:42:03.919 | 8924558786060583479 | 2999875608062437330 | true | +| 2019-02-09 19:49:16.343 | 6536733823181975045 | 8924558786060583479 | true | + +!!! info + **This shows a commit that was rolled back.** In this example, snapshot 296410040247533544 and 2999875608062437330 have the same parent snapshot 5179299526185056830. Snapshot 296410040247533544 was rolled back and is *not* an ancestor of the current table state. + + +### Metadata Log Entries + +To show table metadata log entries: + +```sql +SELECT * from prod.db.table$metadata_log_entries; +``` + +| timestamp | file | latest_snapshot_id | latest_schema_id | latest_sequence_number | +| ----------------------- | ------------------------------------------------------------ | ------------------ | ---------------- | ---------------------- | +| 2022-07-28 10:43:52.93 | s3://.../table/metadata/00000-9441e604-b3c2-498a-a45a-6320e8ab9006.metadata.json | null | null | null | +| 2022-07-28 10:43:57.487 | s3://.../table/metadata/00001-f30823df-b745-4a0a-b293-7532e0c99986.metadata.json | 170260833677645300 | 0 | 1 | +| 2022-07-28 10:43:58.25 | s3://.../table/metadata/00002-2cc2837a-02dc-4687-acc1-b4d86ea486f4.metadata.json | 958906493976709774 | 0 | 2 | + +### Snapshots + +To show the valid snapshots for a table: + +```sql +SELECT * FROM prod.db.table$snapshots; +``` + +| committed_at | snapshot_id | parent_id | operation | manifest_list | summary | +| ----------------------- | -------------- | --------- | --------- | -------------------------------------------------- | ------------------------------------------------------------ | +| 2019-02-08 03:29:51.215 | 57897183625154 | null | append | s3://.../table/metadata/snap-57897183625154-1.avro | { added-records -> 2478404, total-records -> 2478404, added-data-files -> 438, total-data-files -> 438, flink.job-id -> 2e274eecb503d85369fb390e8956c813 } | + +You can also join snapshots to table history. For example, this query will show table history, with the application ID that wrote each snapshot: + +```sql +select + h.made_current_at, + s.operation, + h.snapshot_id, + h.is_current_ancestor, + s.summary['flink.job-id'] +from prod.db.table$history h +join prod.db.table$snapshots s + on h.snapshot_id = s.snapshot_id +order by made_current_at +``` + +| made_current_at | operation | snapshot_id | is_current_ancestor | summary[flink.job-id] | +| ----------------------- | --------- | -------------- | ------------------- | -------------------------------- | +| 2019-02-08 03:29:51.215 | append | 57897183625154 | true | 2e274eecb503d85369fb390e8956c813 | + +### Files + +To show a table's current data files: + +```sql +SELECT * FROM prod.db.table$files; +``` + +| content | file_path | file_format | spec_id | partition | record_count | file_size_in_bytes | column_sizes | value_counts | null_value_counts | nan_value_counts | lower_bounds | upper_bounds | key_metadata | split_offsets | equality_ids | sort_order_id | +| ------- | ------------------------------------------------------------ | ----------- | ------- | ---------------- | ------------ | ------------------ | ------------------ | ---------------- | ----------------- | ---------------- | --------------- | --------------- | ------------ | ------------- | ------------ | ------------- | +| 0 | s3:/.../table/data/00000-3-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 01} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> c] | [1 -> , 2 -> c] | null | [4] | null | null | +| 0 | s3:/.../table/data/00001-4-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 02} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> b] | [1 -> , 2 -> b] | null | [4] | null | null | +| 0 | s3:/.../table/data/00002-5-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 03} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> a] | [1 -> , 2 -> a] | null | [4] | null | null | + +### Manifests + +To show a table's current file manifests: + +```sql +SELECT * FROM prod.db.table$manifests; +``` + +| path | length | partition_spec_id | added_snapshot_id | added_data_files_count | existing_data_files_count | deleted_data_files_count | partition_summaries | +| ------------------------------------------------------------ | ------ | ----------------- | ------------------- | ---------------------- | ------------------------- | ------------------------ | ------------------------------------ | +| s3://.../table/metadata/45b5290b-ee61-4788-b324-b1e2735c0e10-m0.avro | 4479 | 0 | 6668963634911763636 | 8 | 0 | 0 | [[false,null,2019-05-13,2019-05-15]] | + +Note: + +1. Fields within `partition_summaries` column of the manifests table correspond to `field_summary` structs within [manifest list](../../spec.md#manifest-lists), with the following order: + - `contains_null` + - `contains_nan` + - `lower_bound` + - `upper_bound` +2. `contains_nan` could return null, which indicates that this information is not available from the file's metadata. + This usually occurs when reading from V1 table, where `contains_nan` is not populated. + +### Partitions + +To show a table's current partitions: + +```sql +SELECT * FROM prod.db.table$partitions; +``` + +| partition | record_count | file_count | spec_id | +| -------------- | ------------ | ---------- | ------- | +| {20211001, 11} | 1 | 1 | 0 | +| {20211002, 11} | 1 | 1 | 0 | +| {20211001, 10} | 1 | 1 | 0 | +| {20211002, 10} | 1 | 1 | 0 | + +Note: +For unpartitioned tables, the partitions table will contain only the record_count and file_count columns. + +### All Metadata Tables + +These tables are unions of the metadata tables specific to the current snapshot, and return metadata across all snapshots. + +!!! danger + The "all" metadata tables may produce more than one row per data file or manifest file because metadata files may be part of more than one table snapshot. + + +#### All Data Files + +To show all of the table's data files and each file's metadata: + +```sql +SELECT * FROM prod.db.table$all_data_files; +``` + +| content | file_path | file_format | partition | record_count | file_size_in_bytes | column_sizes | value_counts | null_value_counts | nan_value_counts | lower_bounds | upper_bounds | key_metadata | split_offsets | equality_ids | sort_order_id | +| ------- | ------------------------------------------------------------ | ----------- | ---------- | ------------ | ------------------ | ------------------ | ------------------ | ----------------- | ---------------- | ----------------------- | ----------------------- | ------------ | ------------- | ------------ | ------------- | +| 0 | s3://.../dt=20210102/00000-0-756e2512-49ae-45bb-aae3-c0ca475e7879-00001.parquet | PARQUET | {20210102} | 14 | 2444 | {1 -> 94, 2 -> 17} | {1 -> 14, 2 -> 14} | {1 -> 0, 2 -> 0} | {} | {1 -> 1, 2 -> 20210102} | {1 -> 2, 2 -> 20210102} | null | [4] | null | 0 | +| 0 | s3://.../dt=20210103/00000-0-26222098-032f-472b-8ea5-651a55b21210-00001.parquet | PARQUET | {20210103} | 14 | 2444 | {1 -> 94, 2 -> 17} | {1 -> 14, 2 -> 14} | {1 -> 0, 2 -> 0} | {} | {1 -> 1, 2 -> 20210103} | {1 -> 3, 2 -> 20210103} | null | [4] | null | 0 | +| 0 | s3://.../dt=20210104/00000-0-a3bb1927-88eb-4f1c-bc6e-19076b0d952e-00001.parquet | PARQUET | {20210104} | 14 | 2444 | {1 -> 94, 2 -> 17} | {1 -> 14, 2 -> 14} | {1 -> 0, 2 -> 0} | {} | {1 -> 1, 2 -> 20210104} | {1 -> 3, 2 -> 20210104} | null | [4] | null | 0 | + +#### All Manifests + +To show all of the table's manifest files: + +```sql +SELECT * FROM prod.db.table$all_manifests; +``` + +| path | length | partition_spec_id | added_snapshot_id | added_data_files_count | existing_data_files_count | deleted_data_files_count | partition_summaries | +| ------------------------------------------------------------ | ------ | ----------------- | ------------------- | ---------------------- | ------------------------- | ------------------------ | ------------------------------------ | +| s3://.../metadata/a85f78c5-3222-4b37-b7e4-faf944425d48-m0.avro | 6376 | 0 | 6272782676904868561 | 2 | 0 | 0 | [{false, false, 20210101, 20210101}] | + +Note: + +1. Fields within `partition_summaries` column of the manifests table correspond to `field_summary` structs within [manifest list](../../spec.md#manifest-lists), with the following order: + - `contains_null` + - `contains_nan` + - `lower_bound` + - `upper_bound` +2. `contains_nan` could return null, which indicates that this information is not available from the file's metadata. + This usually occurs when reading from V1 table, where `contains_nan` is not populated. + +### References + +To show a table's known snapshot references: + +```sql +SELECT * FROM prod.db.table$refs; +``` + +| name | type | snapshot_id | max_reference_age_in_ms | min_snapshots_to_keep | max_snapshot_age_in_ms | +| ------- | ------ | ------------------- | ----------------------- | --------------------- | ---------------------- | +| main | BRANCH | 4686954189838128572 | 10 | 20 | 30 | +| testTag | TAG | 4686954189838128572 | 10 | null | null | + diff --git a/site/docs/docs/nightly/docs/flink-writes.md b/site/docs/docs/nightly/docs/flink-writes.md new file mode 100644 index 000000000000..9c45f805f29c --- /dev/null +++ b/site/docs/docs/nightly/docs/flink-writes.md @@ -0,0 +1,265 @@ +--- +title: "Flink Writes" +--- + +# Flink Writes + +Iceberg support batch and streaming writes With [Apache Flink](https://flink.apache.org/)'s DataStream API and Table API. + +## Writing with SQL + +Iceberg support both `INSERT INTO` and `INSERT OVERWRITE`. + +### `INSERT INTO` + +To append new data to a table with a Flink streaming job, use `INSERT INTO`: + +```sql +INSERT INTO `hive_catalog`.`default`.`sample` VALUES (1, 'a'); +INSERT INTO `hive_catalog`.`default`.`sample` SELECT id, data from other_kafka_table; +``` + +### `INSERT OVERWRITE` + +To replace data in the table with the result of a query, use `INSERT OVERWRITE` in batch job (flink streaming job does not support `INSERT OVERWRITE`). Overwrites are atomic operations for Iceberg tables. + +Partitions that have rows produced by the SELECT query will be replaced, for example: + +```sql +INSERT OVERWRITE sample VALUES (1, 'a'); +``` + +Iceberg also support overwriting given partitions by the `select` values: + +```sql +INSERT OVERWRITE `hive_catalog`.`default`.`sample` PARTITION(data='a') SELECT 6; +``` + +For a partitioned iceberg table, when all the partition columns are set a value in `PARTITION` clause, it is inserting into a static partition, otherwise if partial partition columns (prefix part of all partition columns) are set a value in `PARTITION` clause, it is writing the query result into a dynamic partition. +For an unpartitioned iceberg table, its data will be completely overwritten by `INSERT OVERWRITE`. + +### `UPSERT` + +Iceberg supports `UPSERT` based on the primary key when writing data into v2 table format. There are two ways to enable upsert. + +1. Enable the `UPSERT` mode as table-level property `write.upsert.enabled`. Here is an example SQL statement to set the table property when creating a table. It would be applied for all write paths to this table (batch or streaming) unless overwritten by write options as described later. + +```sql +CREATE TABLE `hive_catalog`.`default`.`sample` ( + `id` INT UNIQUE COMMENT 'unique id', + `data` STRING NOT NULL, + PRIMARY KEY(`id`) NOT ENFORCED +) with ('format-version'='2', 'write.upsert.enabled'='true'); +``` + +2. Enabling `UPSERT` mode using `upsert-enabled` in the [write options](#write-options) provides more flexibility than a table level config. Note that you still need to use v2 table format and specify the primary key when creating the table. + +```sql +INSERT INTO tableName /*+ OPTIONS('upsert-enabled'='true') */ +... +``` + +!!! info + OVERWRITE and UPSERT can't be set together. In UPSERT mode, if the table is partitioned, the partition fields should be included in equality fields. + + + + +## Writing with DataStream + +Iceberg support writing to iceberg table from different DataStream input. + + +### Appending data. + +Flink supports writing `DataStream` and `DataStream` to the sink iceberg table natively. + +```java +StreamExecutionEnvironment env = ...; + +DataStream input = ... ; +Configuration hadoopConf = new Configuration(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path", hadoopConf); + +FlinkSink.forRowData(input) + .tableLoader(tableLoader) + .append(); + +env.execute("Test Iceberg DataStream"); +``` + +The iceberg API also allows users to write generic `DataStream` to iceberg table, more example could be found in this [unit test](https://github.com/apache/iceberg/blob/master/flink/v1.16/flink/src/test/java/org/apache/iceberg/flink/sink/TestFlinkIcebergSink.java). + +### Overwrite data + +Set the `overwrite` flag in FlinkSink builder to overwrite the data in existing iceberg tables: + +```java +StreamExecutionEnvironment env = ...; + +DataStream input = ... ; +Configuration hadoopConf = new Configuration(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path", hadoopConf); + +FlinkSink.forRowData(input) + .tableLoader(tableLoader) + .overwrite(true) + .append(); + +env.execute("Test Iceberg DataStream"); +``` + +### Upsert data + +Set the `upsert` flag in FlinkSink builder to upsert the data in existing iceberg table. The table must use v2 table format and have a primary key. + +```java +StreamExecutionEnvironment env = ...; + +DataStream input = ... ; +Configuration hadoopConf = new Configuration(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path", hadoopConf); + +FlinkSink.forRowData(input) + .tableLoader(tableLoader) + .upsert(true) + .append(); + +env.execute("Test Iceberg DataStream"); +``` + +!!! info + OVERWRITE and UPSERT can't be set together. In UPSERT mode, if the table is partitioned, the partition fields should be included in equality fields. + + +### Write with Avro GenericRecord + +Flink Iceberg sink provides `AvroGenericRecordToRowDataMapper` that converts +Avro `GenericRecord` to Flink `RowData`. You can use the mapper to write +Avro GenericRecord DataStream to Iceberg. + +Please make sure `flink-avro` jar is included in the classpath. +Also `iceberg-flink-runtime` shaded bundle jar can't be used +because the runtime jar shades the avro package. +Please use non-shaded `iceberg-flink` jar instead. + +```java +DataStream dataStream = ...; + +Schema icebergSchema = table.schema(); + + +// The Avro schema converted from Iceberg schema can't be used +// due to precision difference between how Iceberg schema (micro) +// and Flink AvroToRowDataConverters (milli) deal with time type. +// Instead, use the Avro schema defined directly. +// See AvroGenericRecordToRowDataMapper Javadoc for more details. +org.apache.avro.Schema avroSchema = AvroSchemaUtil.convert(icebergSchema, table.name()); + +GenericRecordAvroTypeInfo avroTypeInfo = new GenericRecordAvroTypeInfo(avroSchema); +RowType rowType = FlinkSchemaUtil.convert(icebergSchema); + +FlinkSink.builderFor( + dataStream, + AvroGenericRecordToRowDataMapper.forAvroSchema(avroSchema), + FlinkCompatibilityUtil.toTypeInfo(rowType)) + .table(table) + .tableLoader(tableLoader) + .append(); +``` + +### Branch Writes +Writing to branches in Iceberg tables is also supported via the `toBranch` API in `FlinkSink` +For more information on branches please refer to [branches](branching.md). +```java +FlinkSink.forRowData(input) + .tableLoader(tableLoader) + .toBranch("audit-branch") + .append(); +``` + +### Metrics + +The following Flink metrics are provided by the Flink Iceberg sink. + +Parallel writer metrics are added under the sub group of `IcebergStreamWriter`. +They should have the following key-value tags. + +* table: full table name (like iceberg.my_db.my_table) +* subtask_index: writer subtask index starting from 0 + + Metric name | Metric type | Description | +| ------------------------- |------------|-----------------------------------------------------------------------------------------------------| +| lastFlushDurationMs | Gague | The duration (in milli) that writer subtasks take to flush and upload the files during checkpoint. | +| flushedDataFiles | Counter | Number of data files flushed and uploaded. | +| flushedDeleteFiles | Counter | Number of delete files flushed and uploaded. | +| flushedReferencedDataFiles| Counter | Number of data files referenced by the flushed delete files. | +| dataFilesSizeHistogram | Histogram | Histogram distribution of data file sizes (in bytes). | +| deleteFilesSizeHistogram | Histogram | Histogram distribution of delete file sizes (in bytes). | + +Committer metrics are added under the sub group of `IcebergFilesCommitter`. +They should have the following key-value tags. + +* table: full table name (like iceberg.my_db.my_table) + + Metric name | Metric type | Description | +|---------------------------------|--------|----------------------------------------------------------------------------| +| lastCheckpointDurationMs | Gague | The duration (in milli) that the committer operator checkpoints its state. | +| lastCommitDurationMs | Gague | The duration (in milli) that the Iceberg table commit takes. | +| committedDataFilesCount | Counter | Number of data files committed. | +| committedDataFilesRecordCount | Counter | Number of records contained in the committed data files. | +| committedDataFilesByteCount | Counter | Number of bytes contained in the committed data files. | +| committedDeleteFilesCount | Counter | Number of delete files committed. | +| committedDeleteFilesRecordCount | Counter | Number of records contained in the committed delete files. | +| committedDeleteFilesByteCount | Counter | Number of bytes contained in the committed delete files. | +| elapsedSecondsSinceLastSuccessfulCommit| Gague | Elapsed time (in seconds) since last successful Iceberg commit. | + +`elapsedSecondsSinceLastSuccessfulCommit` is an ideal alerting metric +to detect failed or missing Iceberg commits. + +* Iceberg commit happened after successful Flink checkpoint in the `notifyCheckpointComplete` callback. + It could happen that Iceberg commits failed (for whatever reason), while Flink checkpoints succeeding. +* It could also happen that `notifyCheckpointComplete` wasn't triggered (for whatever bug). + As a result, there won't be any Iceberg commits attempted. + +If the checkpoint interval (and expected Iceberg commit interval) is 5 minutes, set up alert with rule like `elapsedSecondsSinceLastSuccessfulCommit > 60 minutes` to detect failed or missing Iceberg commits in the past hour. + + + +## Options + +### Write options + +Flink write options are passed when configuring the FlinkSink, like this: + +```java +FlinkSink.Builder builder = FlinkSink.forRow(dataStream, SimpleDataUtil.FLINK_SCHEMA) + .table(table) + .tableLoader(tableLoader) + .set("write-format", "orc") + .set(FlinkWriteOptions.OVERWRITE_MODE, "true"); +``` + +For Flink SQL, write options can be passed in via SQL hints like this: + +```sql +INSERT INTO tableName /*+ OPTIONS('upsert-enabled'='true') */ +... +``` + +Check out all the options here: [write-options](flink-configuration.md#write-options) diff --git a/site/docs/docs/nightly/docs/flink.md b/site/docs/docs/nightly/docs/flink.md new file mode 100644 index 000000000000..a906fd8db7b0 --- /dev/null +++ b/site/docs/docs/nightly/docs/flink.md @@ -0,0 +1,393 @@ +--- +title: "Flink Getting Started" +--- + + +# Flink + +Apache Iceberg supports both [Apache Flink](https://flink.apache.org/)'s DataStream API and Table API. See the [Multi-Engine Support#apache-flink](../../multi-engine-support.md#apache-flink) page for the integration of Apache Flink. + +| Feature support | Flink | Notes | +| ----------------------------------------------------------- |-------|----------------------------------------------------------------------------------------| +| [SQL create catalog](#creating-catalogs-and-using-catalogs) | ✔️ | | +| [SQL create database](#create-database) | ✔️ | | +| [SQL create table](#create-table) | ✔️ | | +| [SQL create table like](#create-table-like) | ✔️ | | +| [SQL alter table](#alter-table) | ✔️ | Only support altering table properties, column and partition changes are not supported | +| [SQL drop_table](#drop-table) | ✔️ | | +| [SQL select](#querying-with-sql) | ✔️ | Support both streaming and batch mode | +| [SQL insert into](#insert-into) | ✔️ ️ | Support both streaming and batch mode | +| [SQL insert overwrite](#insert-overwrite) | ✔️ ️ | | +| [DataStream read](#reading-with-datastream) | ✔️ ️ | | +| [DataStream append](#appending-data) | ✔️ ️ | | +| [DataStream overwrite](#overwrite-data) | ✔️ ️ | | +| [Metadata tables](#inspecting-tables) | ✔️ | | +| [Rewrite files action](#rewrite-files-action) | ✔️ ️ | | + +## Preparation when using Flink SQL Client + +To create Iceberg table in Flink, it is recommended to use [Flink SQL Client](https://ci.apache.org/projects/flink/flink-docs-stable/dev/table/sqlClient.html) as it's easier for users to understand the concepts. + +Download Flink from the [Apache download page](https://flink.apache.org/downloads.html). Iceberg uses Scala 2.12 when compiling the Apache `iceberg-flink-runtime` jar, so it's recommended to use Flink 1.16 bundled with Scala 2.12. + +```bash +FLINK_VERSION=1.16.1 +SCALA_VERSION=2.12 +APACHE_FLINK_URL=https://archive.apache.org/dist/flink/ +wget ${APACHE_FLINK_URL}/flink-${FLINK_VERSION}/flink-${FLINK_VERSION}-bin-scala_${SCALA_VERSION}.tgz +tar xzvf flink-${FLINK_VERSION}-bin-scala_${SCALA_VERSION}.tgz +``` + +Start a standalone Flink cluster within Hadoop environment: + +```bash +# HADOOP_HOME is your hadoop root directory after unpack the binary package. +APACHE_HADOOP_URL=https://archive.apache.org/dist/hadoop/ +HADOOP_VERSION=2.8.5 +wget ${APACHE_HADOOP_URL}/common/hadoop-${HADOOP_VERSION}/hadoop-${HADOOP_VERSION}.tar.gz +tar xzvf hadoop-${HADOOP_VERSION}.tar.gz +HADOOP_HOME=`pwd`/hadoop-${HADOOP_VERSION} + +export HADOOP_CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath` + +# Start the flink standalone cluster +./bin/start-cluster.sh +``` + +Start the Flink SQL client. There is a separate `flink-runtime` module in the Iceberg project to generate a bundled jar, which could be loaded by Flink SQL client directly. To build the `flink-runtime` bundled jar manually, build the `iceberg` project, and it will generate the jar under `/flink-runtime/build/libs`. Or download the `flink-runtime` jar from the [Apache repository](https://repo.maven.apache.org/maven2/org/apache/iceberg/iceberg-flink-runtime-1.16/{{ icebergVersion }}/). + +```bash +# HADOOP_HOME is your hadoop root directory after unpack the binary package. +export HADOOP_CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath` + +./bin/sql-client.sh embedded -j /iceberg-flink-runtime-1.16-{{ icebergVersion }}.jar shell +``` + +By default, Iceberg ships with Hadoop jars for Hadoop catalog. To use Hive catalog, load the Hive jars when opening the Flink SQL client. Fortunately, Flink has provided a [bundled hive jar](https://repo.maven.apache.org/maven2/org/apache/flink/flink-sql-connector-hive-2.3.9_2.12/1.16.1/flink-sql-connector-hive-2.3.9_2.12-1.16.1.jar) for the SQL client. An example on how to download the dependencies and get started: + +```bash +# HADOOP_HOME is your hadoop root directory after unpack the binary package. +export HADOOP_CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath` + +ICEBERG_VERSION={{ icebergVersion }} +MAVEN_URL=https://repo1.maven.org/maven2 +ICEBERG_MAVEN_URL=${MAVEN_URL}/org/apache/iceberg +ICEBERG_PACKAGE=iceberg-flink-runtime +wget ${ICEBERG_MAVEN_URL}/${ICEBERG_PACKAGE}-${FLINK_VERSION_MAJOR}/${ICEBERG_VERSION}/${ICEBERG_PACKAGE}-${FLINK_VERSION_MAJOR}-${ICEBERG_VERSION}.jar -P lib/ + +HIVE_VERSION=2.3.9 +SCALA_VERSION=2.12 +FLINK_VERSION=1.16.1 +FLINK_CONNECTOR_URL=${MAVEN_URL}/org/apache/flink +FLINK_CONNECTOR_PACKAGE=flink-sql-connector-hive +wget ${FLINK_CONNECTOR_URL}/${FLINK_CONNECTOR_PACKAGE}-${HIVE_VERSION}_${SCALA_VERSION}/${FLINK_VERSION}/${FLINK_CONNECTOR_PACKAGE}-${HIVE_VERSION}_${SCALA_VERSION}-${FLINK_VERSION}.jar + +./bin/sql-client.sh embedded shell +``` + +## Flink's Python API + +!!! info + PyFlink 1.6.1 [does not work on OSX with a M1 cpu](https://issues.apache.org/jira/browse/FLINK-28786) + + +Install the Apache Flink dependency using `pip`: + +```python +pip install apache-flink==1.16.1 +``` + +Provide a `file://` path to the `iceberg-flink-runtime` jar, which can be obtained by building the project and looking at `/flink-runtime/build/libs`, or downloading it from the [Apache official repository](https://repo.maven.apache.org/maven2/org/apache/iceberg/iceberg-flink-runtime/). Third-party jars can be added to `pyflink` via: + +- `env.add_jars("file:///my/jar/path/connector.jar")` +- `table_env.get_config().get_configuration().set_string("pipeline.jars", "file:///my/jar/path/connector.jar")` + +This is also mentioned in the official [docs](https://ci.apache.org/projects/flink/flink-docs-release-1.16/docs/dev/python/dependency_management/). The example below uses `env.add_jars(..)`: + +```python +import os + +from pyflink.datastream import StreamExecutionEnvironment + +env = StreamExecutionEnvironment.get_execution_environment() +iceberg_flink_runtime_jar = os.path.join(os.getcwd(), "iceberg-flink-runtime-1.16-{{ icebergVersion }}.jar") + +env.add_jars("file://{}".format(iceberg_flink_runtime_jar)) +``` + +Next, create a `StreamTableEnvironment` and execute Flink SQL statements. The below example shows how to create a custom catalog via the Python Table API: + +```python +from pyflink.table import StreamTableEnvironment +table_env = StreamTableEnvironment.create(env) +table_env.execute_sql(""" +CREATE CATALOG my_catalog WITH ( + 'type'='iceberg', + 'catalog-impl'='com.my.custom.CatalogImpl', + 'my-additional-catalog-config'='my-value' +) +""") +``` + +Run a query: + +```python +(table_env + .sql_query("SELECT PULocationID, DOLocationID, passenger_count FROM my_catalog.nyc.taxis LIMIT 5") + .execute() + .print()) +``` + +``` ++----+----------------------+----------------------+--------------------------------+ +| op | PULocationID | DOLocationID | passenger_count | ++----+----------------------+----------------------+--------------------------------+ +| +I | 249 | 48 | 1.0 | +| +I | 132 | 233 | 1.0 | +| +I | 164 | 107 | 1.0 | +| +I | 90 | 229 | 1.0 | +| +I | 137 | 249 | 1.0 | ++----+----------------------+----------------------+--------------------------------+ +5 rows in set +``` + +For more details, please refer to the [Python Table API](https://ci.apache.org/projects/flink/flink-docs-release-1.16/docs/dev/python/table/intro_to_table_api/). + +## Adding catalogs. + +Flink support to create catalogs by using Flink SQL. + +### Catalog Configuration + +A catalog is created and named by executing the following query (replace `` with your catalog name and +``=`` with catalog implementation config): + +```sql +CREATE CATALOG WITH ( + 'type'='iceberg', + ``=`` +); +``` + +The following properties can be set globally and are not limited to a specific catalog implementation: + +* `type`: Must be `iceberg`. (required) +* `catalog-type`: `hive`, `hadoop` or `rest` for built-in catalogs, or left unset for custom catalog implementations using catalog-impl. (Optional) +* `catalog-impl`: The fully-qualified class name of a custom catalog implementation. Must be set if `catalog-type` is unset. (Optional) +* `property-version`: Version number to describe the property version. This property can be used for backwards compatibility in case the property format changes. The current property version is `1`. (Optional) +* `cache-enabled`: Whether to enable catalog cache, default value is `true`. (Optional) +* `cache.expiration-interval-ms`: How long catalog entries are locally cached, in milliseconds; negative values like `-1` will disable expiration, value 0 is not allowed to set. default value is `-1`. (Optional) + +### Hive catalog + +This creates an Iceberg catalog named `hive_catalog` that can be configured using `'catalog-type'='hive'`, which loads tables from Hive metastore: + +```sql +CREATE CATALOG hive_catalog WITH ( + 'type'='iceberg', + 'catalog-type'='hive', + 'uri'='thrift://localhost:9083', + 'clients'='5', + 'property-version'='1', + 'warehouse'='hdfs://nn:8020/warehouse/path' +); +``` + +The following properties can be set if using the Hive catalog: + +* `uri`: The Hive metastore's thrift URI. (Required) +* `clients`: The Hive metastore client pool size, default value is 2. (Optional) +* `warehouse`: The Hive warehouse location, users should specify this path if neither set the `hive-conf-dir` to specify a location containing a `hive-site.xml` configuration file nor add a correct `hive-site.xml` to classpath. +* `hive-conf-dir`: Path to a directory containing a `hive-site.xml` configuration file which will be used to provide custom Hive configuration values. The value of `hive.metastore.warehouse.dir` from `/hive-site.xml` (or hive configure file from classpath) will be overwritten with the `warehouse` value if setting both `hive-conf-dir` and `warehouse` when creating iceberg catalog. +* `hadoop-conf-dir`: Path to a directory containing `core-site.xml` and `hdfs-site.xml` configuration files which will be used to provide custom Hadoop configuration values. + +## Creating a table + +```sql +CREATE TABLE `hive_catalog`.`default`.`sample` ( + id BIGINT COMMENT 'unique id', + data STRING +); +``` + +## Writing + +To append new data to a table with a Flink streaming job, use `INSERT INTO`: + +```sql +INSERT INTO `hive_catalog`.`default`.`sample` VALUES (1, 'a'); +INSERT INTO `hive_catalog`.`default`.`sample` SELECT id, data from other_kafka_table; +``` + +To replace data in the table with the result of a query, use `INSERT OVERWRITE` in batch job (flink streaming job does not support `INSERT OVERWRITE`). Overwrites are atomic operations for Iceberg tables. + +Partitions that have rows produced by the SELECT query will be replaced, for example: + +```sql +INSERT OVERWRITE `hive_catalog`.`default`.`sample` VALUES (1, 'a'); +``` + +Iceberg also support overwriting given partitions by the `select` values: + +```sql +INSERT OVERWRITE `hive_catalog`.`default`.`sample` PARTITION(data='a') SELECT 6; +``` + +Flink supports writing `DataStream` and `DataStream` to the sink iceberg table natively. + +```java +StreamExecutionEnvironment env = ...; + +DataStream input = ... ; +Configuration hadoopConf = new Configuration(); +TableLoader tableLoader = TableLoader.fromHadoopTable("hdfs://nn:8020/warehouse/path", hadoopConf); + +FlinkSink.forRowData(input) + .tableLoader(tableLoader) + .append(); + +env.execute("Test Iceberg DataStream"); +``` + +### Branch Writes +Writing to branches in Iceberg tables is also supported via the `toBranch` API in `FlinkSink` +For more information on branches please refer to [branches](branching.md). +```java +FlinkSink.forRowData(input) + .tableLoader(tableLoader) + .toBranch("audit-branch") + .append(); +``` + +## Reading + +Submit a Flink __batch__ job using the following sentences: + +```sql +-- Execute the flink job in batch mode for current session context +SET execution.runtime-mode = batch; +SELECT * FROM `hive_catalog`.`default`.`sample`; +``` + +Iceberg supports processing incremental data in flink __streaming__ jobs which starts from a historical snapshot-id: + +```sql +-- Submit the flink job in streaming mode for current session. +SET execution.runtime-mode = streaming; + +-- Enable this switch because streaming read SQL will provide few job options in flink SQL hint options. +SET table.dynamic-table-options.enabled=true; + +-- Read all the records from the iceberg current snapshot, and then read incremental data starting from that snapshot. +SELECT * FROM `hive_catalog`.`default`.`sample` /*+ OPTIONS('streaming'='true', 'monitor-interval'='1s')*/ ; + +-- Read all incremental data starting from the snapshot-id '3821550127947089987' (records from this snapshot will be excluded). +SELECT * FROM `hive_catalog`.`default`.`sample` /*+ OPTIONS('streaming'='true', 'monitor-interval'='1s', 'start-snapshot-id'='3821550127947089987')*/ ; +``` + +SQL is also the recommended way to inspect tables. To view all of the snapshots in a table, use the snapshots metadata table: + +```sql +SELECT * FROM `hive_catalog`.`default`.`sample`.`snapshots` +``` + +Iceberg support streaming or batch read in Java API: + +``` +DataStream batch = FlinkSource.forRowData() + .env(env) + .tableLoader(tableLoader) + .streaming(false) + .build(); +``` + + + + +## Type conversion + +Iceberg's integration for Flink automatically converts between Flink and Iceberg types. When writing to a table with types that are not supported by Flink, like UUID, Iceberg will accept and convert values from the Flink type. + +### Flink to Iceberg + +Flink types are converted to Iceberg types according to the following table: + +| Flink | Iceberg | Notes | +| ------------------- | -------------------------- | ------------- | +| boolean | boolean | | +| tinyint | integer | | +| smallint | integer | | +| integer | integer | | +| bigint | long | | +| float | float | | +| double | double | | +| char | string | | +| varchar | string | | +| string | string | | +| binary | binary | | +| varbinary | fixed | | +| decimal | decimal | | +| date | date | | +| time | time | | +| timestamp | timestamp without timezone | | +| timestamp_ltz | timestamp with timezone | | +| array | list | | +| map | map | | +| multiset | map | | +| row | struct | | +| raw | | Not supported | +| interval | | Not supported | +| structured | | Not supported | +| timestamp with zone | | Not supported | +| distinct | | Not supported | +| null | | Not supported | +| symbol | | Not supported | +| logical | | Not supported | + +### Iceberg to Flink + +Iceberg types are converted to Flink types according to the following table: + +| Iceberg | Flink | +| -------------------------- | --------------------- | +| boolean | boolean | +| struct | row | +| list | array | +| map | map | +| integer | integer | +| long | bigint | +| float | float | +| double | double | +| date | date | +| time | time | +| timestamp without timezone | timestamp(6) | +| timestamp with timezone | timestamp_ltz(6) | +| string | varchar(2147483647) | +| uuid | binary(16) | +| fixed(N) | binary(N) | +| binary | varbinary(2147483647) | +| decimal(P, S) | decimal(P, S) | + +## Future improvements + +There are some features that are do not yet supported in the current Flink Iceberg integration work: + +* Don't support creating iceberg table with hidden partitioning. [Discussion](http://mail-archives.apache.org/mod_mbox/flink-dev/202008.mbox/%3cCABi+2jQCo3MsOa4+ywaxV5J-Z8TGKNZDX-pQLYB-dG+dVUMiMw@mail.gmail.com%3e) in flink mail list. +* Don't support creating iceberg table with computed column. +* Don't support creating iceberg table with watermark. +* Don't support adding columns, removing columns, renaming columns, changing columns. [FLINK-19062](https://issues.apache.org/jira/browse/FLINK-19062) is tracking this. diff --git a/site/docs/docs/nightly/docs/hive-migration.md b/site/docs/docs/nightly/docs/hive-migration.md new file mode 100644 index 000000000000..e14f1c72d2aa --- /dev/null +++ b/site/docs/docs/nightly/docs/hive-migration.md @@ -0,0 +1,55 @@ +--- +title: "Hive Migration" +--- + + +# Hive Table Migration +Apache Hive supports ORC, Parquet, and Avro file formats that could be migrated to Iceberg. +When migrating data to an Iceberg table, which provides versioning and transactional updates, only the most recent data files need to be migrated. + +Iceberg supports all three migration actions: Snapshot Table, Migrate Table, and Add Files for migrating from Hive tables to Iceberg tables. Since Hive tables do not maintain snapshots, +the migration process essentially involves creating a new Iceberg table with the existing schema and committing all data files across all partitions to the new Iceberg table. +After the initial migration, any new data files are added to the new Iceberg table using the Add Files action. + +## Enabling Migration from Hive to Iceberg +The Hive table migration actions are supported by the Spark Integration module via Spark Procedures. +The procedures are bundled in the Spark runtime jar, which is available in the [Iceberg Release Downloads](../../releases.md#downloads). + +## Snapshot Hive Table to Iceberg +To snapshot a Hive table, users can run the following Spark SQL: +```sql +CALL catalog_name.system.snapshot('db.source', 'db.dest') +``` +See [Spark Procedure: snapshot](spark-procedures.md#snapshot) for more details. + +## Migrate Hive Table To Iceberg +To migrate a Hive table to Iceberg, users can run the following Spark SQL: +```sql +CALL catalog_name.system.migrate('db.sample') +``` +See [Spark Procedure: migrate](spark-procedures.md#migrate) for more details. + +## Add Files From Hive Table to Iceberg +To add data files from a Hive table to a given Iceberg table, users can run the following Spark SQL: +```sql +CALL spark_catalog.system.add_files( +table => 'db.tbl', +source_table => 'db.src_tbl' +) +``` +See [Spark Procedure: add_files](spark-procedures.md#add_files) for more details. diff --git a/site/docs/docs/nightly/docs/hive.md b/site/docs/docs/nightly/docs/hive.md new file mode 100644 index 000000000000..7246ab644e8a --- /dev/null +++ b/site/docs/docs/nightly/docs/hive.md @@ -0,0 +1,590 @@ +--- +title: "Hive" +--- + + +# Hive + +Iceberg supports reading and writing Iceberg tables through [Hive](https://hive.apache.org) by using +a [StorageHandler](https://cwiki.apache.org/confluence/display/Hive/StorageHandlers). + +## Feature support +Iceberg compatibility with Hive 2.x and Hive 3.1.2/3 supports the following features: + +* Creating a table +* Dropping a table +* Reading a table +* Inserting into a table (INSERT INTO) + +!!! warning + DML operations work only with MapReduce execution engine. + + +With Hive version 4.0.0-alpha-2 and above, +the Iceberg integration when using HiveCatalog supports the following additional features: + +* Altering a table with expiring snapshots. +* Create a table like an existing table (CTLT table) +* Support adding parquet compression type via Table properties [Compression types](https://spark.apache.org/docs/2.4.3/sql-data-sources-parquet.html#configuration) +* Altering a table metadata location +* Supporting table rollback +* Honors sort orders on existing tables when writing a table [Sort orders specification](../../spec.md#sort-orders) + +With Hive version 4.0.0-alpha-1 and above, +the Iceberg integration when using HiveCatalog supports the following additional features: + +* Creating an Iceberg identity-partitioned table +* Creating an Iceberg table with any partition spec, including the various transforms supported by Iceberg +* Creating a table from an existing table (CTAS table) +* Altering a table while keeping Iceberg and Hive schemas in sync +* Altering the partition schema (updating columns) +* Altering the partition schema by specifying partition transforms +* Truncating a table +* Migrating tables in Avro, Parquet, or ORC (Non-ACID) format to Iceberg +* Reading the schema of a table +* Querying Iceberg metadata tables +* Time travel applications +* Inserting into a table (INSERT INTO) +* Inserting data overwriting existing data (INSERT OVERWRITE) + +!!! warning + DML operations work only with Tez execution engine. + + +## Enabling Iceberg support in Hive + +### Hive 4.0.0-alpha-1 + +Hive 4.0.0-alpha-1 comes with the Iceberg 0.13.1 included. No additional downloads or jars are needed. + +### Hive 2.3.x, Hive 3.1.x + +In order to use Hive 2.3.x or Hive 3.1.x, you must load the Iceberg-Hive runtime jar and enable Iceberg support, either globally or for an individual table using a table property. + +#### Loading runtime jar + +To enable Iceberg support in Hive, the `HiveIcebergStorageHandler` and supporting classes need to be made available on +Hive's classpath. These are provided by the `iceberg-hive-runtime` jar file. For example, if using the Hive shell, this +can be achieved by issuing a statement like so: + +``` +add jar /path/to/iceberg-hive-runtime.jar; +``` + +There are many others ways to achieve this including adding the jar file to Hive's auxiliary classpath so it is +available by default. Please refer to Hive's documentation for more information. + +#### Enabling support + +If the Iceberg storage handler is not in Hive's classpath, then Hive cannot load or update the metadata for an Iceberg +table when the storage handler is set. To avoid the appearance of broken tables in Hive, Iceberg will not add the +storage handler to a table unless Hive support is enabled. The storage handler is kept in sync (added or removed) every +time Hive engine support for the table is updated, i.e. turned on or off in the table properties. There are two ways to +enable Hive support: globally in Hadoop Configuration and per-table using a table property. + +##### Hadoop configuration + +To enable Hive support globally for an application, set `iceberg.engine.hive.enabled=true` in its Hadoop configuration. +For example, setting this in the `hive-site.xml` loaded by Spark will enable the storage handler for all tables created +by Spark. + +!!! danger + Starting with Apache Iceberg 0.11.0, when using Hive with Tez you also have to disable vectorization (hive.vectorized.execution.enabled=false). + + +##### Table property configuration + +Alternatively, the property `engine.hive.enabled` can be set to `true` and added to the table properties when creating +the Iceberg table. Here is an example of doing it programmatically: + +```java +Catalog catalog=...; + Map tableProperties=Maps.newHashMap(); + tableProperties.put(TableProperties.ENGINE_HIVE_ENABLED,"true"); // engine.hive.enabled=true + catalog.createTable(tableId,schema,spec,tableProperties); +``` + +The table level configuration overrides the global Hadoop configuration. + +##### Hive on Tez configuration + +To use the Tez engine on Hive `3.1.2` or later, Tez needs to be upgraded to >= `0.10.1` which contains a necessary fix [TEZ-4248](https://issues.apache.org/jira/browse/TEZ-4248). + +To use the Tez engine on Hive `2.3.x`, you will need to manually build Tez from the `branch-0.9` branch due to a +backwards incompatibility issue with Tez `0.10.1`. + +In both cases, you will also need to set the following property in the `tez-site.xml` configuration file: `tez.mrreader.config.update.properties=hive.io.file.readcolumn.names,hive.io.file.readcolumn.ids`. + +## Catalog Management + +### Global Hive catalog + +From the Hive engine's perspective, there is only one global data catalog that is defined in the Hadoop configuration in +the runtime environment. In contrast, Iceberg supports multiple different data catalog types such as Hive, Hadoop, AWS +Glue, or custom catalog implementations. Iceberg also allows loading a table directly based on its path in the file +system. Those tables do not belong to any catalog. Users might want to read these cross-catalog and path-based tables +through the Hive engine for use cases like join. + +To support this, a table in the Hive metastore can represent three different ways of loading an Iceberg table, depending +on the table's `iceberg.catalog` property: + +1. The table will be loaded using a `HiveCatalog` that corresponds to the metastore configured in the Hive environment + if no `iceberg.catalog` is set +2. The table will be loaded using a custom catalog if `iceberg.catalog` is set to a catalog name (see below) +3. The table can be loaded directly using the table's root location if `iceberg.catalog` is set + to `location_based_table` + +For cases 2 and 3 above, users can create an overlay of an Iceberg table in the Hive metastore, so that different table +types can work together in the same Hive environment. See [CREATE EXTERNAL TABLE](#create-external-table-overlaying-an-existing-iceberg-table) +and [CREATE TABLE](#create-table) for more details. + +### Custom Iceberg catalogs + +To globally register different catalogs, set the following Hadoop configurations: + +| Config Key | Description | +| --------------------------------------------- | ------------------------------------------------------ | +| iceberg.catalog..type | type of catalog: `hive`, `hadoop`, or left unset if using a custom catalog | +| iceberg.catalog..catalog-impl | catalog implementation, must not be null if type is empty | +| iceberg.catalog.. | any config key and value pairs for the catalog | + +Here are some examples using Hive CLI: + +Register a `HiveCatalog` called `another_hive`: + +``` +SET iceberg.catalog.another_hive.type=hive; +SET iceberg.catalog.another_hive.uri=thrift://example.com:9083; +SET iceberg.catalog.another_hive.clients=10; +SET iceberg.catalog.another_hive.warehouse=hdfs://example.com:8020/warehouse; +``` + +Register a `HadoopCatalog` called `hadoop`: + +``` +SET iceberg.catalog.hadoop.type=hadoop; +SET iceberg.catalog.hadoop.warehouse=hdfs://example.com:8020/warehouse; +``` + +Register an AWS `GlueCatalog` called `glue`: + +``` +SET iceberg.catalog.glue.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog; +SET iceberg.catalog.glue.warehouse=s3://my-bucket/my/key/prefix; +SET iceberg.catalog.glue.lock.table=myGlueLockTable; +``` + +## DDL Commands + +Not all the features below are supported with Hive 2.3.x and Hive 3.1.x. Please refer to the +[Feature support](#feature-support) paragraph for further details. + +One generally applicable difference is that Hive 4.0.0-alpha-1 provides the possibility to use +`STORED BY ICEBERG` instead of the old `STORED BY 'org.apache.iceberg.mr.hive.HiveIcebergStorageHandler'` + +### CREATE TABLE + +#### Non partitioned tables + +The Hive `CREATE EXTERNAL TABLE` command creates an Iceberg table when you specify the storage handler as follows: + +```sql +CREATE EXTERNAL TABLE x (i int) STORED BY ICEBERG; +``` + +If you want to create external tables using CREATE TABLE, configure the MetaStoreMetadataTransformer on the cluster, +and `CREATE TABLE` commands are transformed to create external tables. For example: + +```sql +CREATE TABLE x (i int) STORED BY ICEBERG; +``` + +You can specify the default file format (Avro, Parquet, ORC) at the time of the table creation. +The default is Parquet: + +```sql +CREATE TABLE x (i int) STORED BY ICEBERG STORED AS ORC; +``` + +#### Partitioned tables +You can create Iceberg partitioned tables using a command familiar to those who create non-Iceberg tables: + +```sql +CREATE TABLE x (i int) PARTITIONED BY (j int) STORED BY ICEBERG; +``` + +!!! info + The resulting table does not create partitions in HMS, but instead, converts partition data into Iceberg identity partitions. + + +Use the DESCRIBE command to get information about the Iceberg identity partitions: + +```sql +DESCRIBE x; +``` +The result is: + +| col_name | data_type | comment +| ---------------------------------- | -------------- | ------- +| i | int | +| j | int | +| | NULL | NULL +| # Partition Transform Information | NULL | NULL +| # col_name | transform_type | NULL +| j | IDENTITY | NULL + +You can create Iceberg partitions using the following Iceberg partition specification syntax +(supported only from Hive 4.0.0-alpha-1): + +```sql +CREATE TABLE x (i int, ts timestamp) PARTITIONED BY SPEC (month(ts), bucket(2, i)) STORED AS ICEBERG; +DESCRIBE x; +``` +The result is: + +| col_name | data_type | comment +| ---------------------------------- | -------------- | ------- +| i | int | +| ts | timestamp | +| | NULL | NULL +| # Partition Transform Information | NULL | NULL +| # col_name | transform_type | NULL +| ts | MONTH | NULL +| i | BUCKET\[2\] | NULL + +The supported transformations for Hive are the same as for Spark: +* years(ts): partition by year +* months(ts): partition by month +* days(ts) or date(ts): equivalent to dateint partitioning +* hours(ts) or date_hour(ts): equivalent to dateint and hour partitioning +* bucket(N, col): partition by hashed value mod N buckets +* truncate(L, col): partition by value truncated to L + - Strings are truncated to the given length + - Integers and longs truncate to bins: truncate(10, i) produces partitions 0, 10, 20, 30, + +!!! info + The resulting table does not create partitions in HMS, but instead, converts partition data into Iceberg partitions. + + +### CREATE TABLE AS SELECT + +`CREATE TABLE AS SELECT` operation resembles the native Hive operation with a single important difference. +The Iceberg table and the corresponding Hive table are created at the beginning of the query execution. +The data is inserted / committed when the query finishes. So for a transient period the table already exists but contains no data. + +```sql +CREATE TABLE target PARTITIONED BY SPEC (year(year_field), identity_field) STORED BY ICEBERG AS + SELECT * FROM source; +``` + +### CREATE TABLE LIKE TABLE + +```sql +CREATE TABLE target LIKE source STORED BY ICEBERG; +``` + +### CREATE EXTERNAL TABLE overlaying an existing Iceberg table + +The `CREATE EXTERNAL TABLE` command is used to overlay a Hive table "on top of" an existing Iceberg table. Iceberg +tables are created using either a [`Catalog`](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/catalog/Catalog.html), or an implementation of the [`Tables`](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/Tables.html) interface, and Hive needs to be configured accordingly to operate on these different types of table. + +#### Hive catalog tables + +As described before, tables created by the `HiveCatalog` with Hive engine feature enabled are directly visible by the +Hive engine, so there is no need to create an overlay. + +#### Custom catalog tables + +For a table in a registered catalog, specify the catalog name in the statement using table property `iceberg.catalog`. +For example, the SQL below creates an overlay for a table in a `hadoop` type catalog named `hadoop_cat`: + +```sql +SET +iceberg.catalog.hadoop_cat.type=hadoop; +SET +iceberg.catalog.hadoop_cat.warehouse=hdfs://example.com:8020/hadoop_cat; + +CREATE +EXTERNAL TABLE database_a.table_a +STORED BY 'org.apache.iceberg.mr.hive.HiveIcebergStorageHandler' +TBLPROPERTIES ('iceberg.catalog'='hadoop_cat'); +``` + +When `iceberg.catalog` is missing from both table properties and the global Hadoop configuration, `HiveCatalog` will be +used as default. + +#### Path-based Hadoop tables + +Iceberg tables created using `HadoopTables` are stored entirely in a directory in a filesystem like HDFS. These tables +are considered to have no catalog. To indicate that, set `iceberg.catalog` property to `location_based_table`. For +example: + +```sql +CREATE +EXTERNAL TABLE table_a +STORED BY 'org.apache.iceberg.mr.hive.HiveIcebergStorageHandler' +LOCATION 'hdfs://some_bucket/some_path/table_a' +TBLPROPERTIES ('iceberg.catalog'='location_based_table'); +``` + +#### CREATE TABLE overlaying an existing Iceberg table + +You can also create a new table that is managed by a custom catalog. For example, the following code creates a table in +a custom Hadoop catalog: + +```sql +SET +iceberg.catalog.hadoop_cat.type=hadoop; +SET +iceberg.catalog.hadoop_cat.warehouse=hdfs://example.com:8020/hadoop_cat; + +CREATE TABLE database_a.table_a +( + id bigint, + name string +) PARTITIONED BY ( + dept string +) STORED BY 'org.apache.iceberg.mr.hive.HiveIcebergStorageHandler' +TBLPROPERTIES ('iceberg.catalog'='hadoop_cat'); +``` + +!!! danger + table. This means technically you can omit the `EXTERNAL` keyword when creating an overlay table. However, this is **not + recommended** because creating managed overlay tables could pose a risk to the shared data files in case of accidental + drop table commands from the Hive side, which would unintentionally remove all the data in the table. + + +### ALTER TABLE +#### Table properties +For HiveCatalog tables the Iceberg table properties and the Hive table properties stored in HMS are kept in sync. + +!!! info + IMPORTANT: This feature is not available for other Catalog implementations. + +```sql +ALTER TABLE t SET TBLPROPERTIES('...'='...'); +``` + +#### Schema evolution +The Hive table schema is kept in sync with the Iceberg table. If an outside source (Impala/Spark/Java API/etc) +changes the schema, the Hive table immediately reflects the changes. You alter the table schema using Hive commands: + +* Add a column +```sql +ALTER TABLE orders ADD COLUMNS (nickname string); +``` +* Rename a column +```sql +ALTER TABLE orders CHANGE COLUMN item fruit string; +``` +* Reorder columns +```sql +ALTER TABLE orders CHANGE COLUMN quantity quantity int AFTER price; +``` +* Change a column type - only if the Iceberg defined the column type change as safe +```sql +ALTER TABLE orders CHANGE COLUMN price price long; +``` +* Drop column by using REPLACE COLUMN to remove the old column +```sql +ALTER TABLE orders REPLACE COLUMNS (remaining string); +``` +!!! info + Note, that dropping columns is only thing REPLACE COLUMNS can be used for + i.e. if columns are specified out-of-order an error will be thrown signalling this limitation. + + +#### Partition evolution +You change the partitioning schema using the following commands: +* Change the partitioning schema to new identity partitions: +```sql +ALTER TABLE default.customers SET PARTITION SPEC (last_name); +``` +* Alternatively, provide a partition specification: +```sql +ALTER TABLE order SET PARTITION SPEC (month(ts)); +``` +#### Table migration +You can migrate Avro / Parquet / ORC external tables to Iceberg tables using the following command: +```sql +ALTER TABLE t SET TBLPROPERTIES ('storage_handler'='org.apache.iceberg.mr.hive.HiveIcebergStorageHandler'); +``` +During the migration the data files are not changed, only the appropriate Iceberg metadata files are created. +After the migration, handle the table as a normal Iceberg table. + +### TRUNCATE TABLE +The following command truncates the Iceberg table: +```sql +TRUNCATE TABLE t; +``` +Using a partition specification is not allowed. + +### DROP TABLE + +Tables can be dropped using the `DROP TABLE` command: + +```sql +DROP TABLE [IF EXISTS] table_name [PURGE]; +``` + +### METADATA LOCATION + +The metadata location (snapshot location) only can be changed if the new path contains the exact same metadata json. +It can be done only after migrating the table to Iceberg, the two operation cannot be done in one step. + +```sql +ALTER TABLE t set TBLPROPERTIES ('metadata_location'='/hivemetadata/00003-a1ada2b8-fc86-4b5b-8c91-400b6b46d0f2.metadata.json'); +``` + +## DML Commands + +### SELECT +Select statements work the same on Iceberg tables in Hive. You will see the Iceberg benefits over Hive in compilation and execution: +* **No file system listings** - especially important on blob stores, like S3 +* **No partition listing from** the Metastore +* **Advanced partition filtering** - the partition keys are not needed in the queries when they could be calculated +* Could handle **higher number of partitions** than normal Hive tables + +Here are the features highlights for Iceberg Hive read support: +1. **Predicate pushdown**: Pushdown of the Hive SQL `WHERE` clause has been implemented so that these filters are used at the Iceberg `TableScan` level as well as by the Parquet and ORC Readers. +2. **Column projection**: Columns from the Hive SQL `SELECT` clause are projected down to the Iceberg readers to reduce the number of columns read. +3. **Hive query engines**: + - With Hive 2.3.x, 3.1.x both the MapReduce and Tez query execution engines are supported. + - With Hive 4.0.0-alpha-1 Tez query execution engine is supported. + +Some of the advanced / little used optimizations are not yet implemented for Iceberg tables, so you should check your individual queries. +Also currently the statistics stored in the MetaStore are used for query planning. This is something we are planning to improve in the future. + +### INSERT INTO + +Hive supports the standard single-table INSERT INTO operation: + +```sql +INSERT INTO table_a +VALUES ('a', 1); +INSERT INTO table_a +SELECT...; +``` + +Multi-table insert is also supported, but it will not be atomic. Commits occur one table at a time. +Partial changes will be visible during the commit process and failures can leave partial changes committed. +Changes within a single table will remain atomic. + +Here is an example of inserting into multiple tables at once in Hive SQL: + +```sql +FROM customers + INSERT INTO target1 SELECT customer_id, first_name + INSERT INTO target2 SELECT last_name, customer_id; +``` + +### INSERT OVERWRITE +INSERT OVERWRITE can replace data in the table with the result of a query. Overwrites are atomic operations for Iceberg tables. +For nonpartitioned tables the content of the table is always removed. For partitioned tables the partitions +that have rows produced by the SELECT query will be replaced. +```sql +INSERT OVERWRITE TABLE target SELECT * FROM source; +``` + +### QUERYING METADATA TABLES +Hive supports querying of the Iceberg Metadata tables. The tables could be used as normal +Hive tables, so it is possible to use projections / joins / filters / etc. +To reference a metadata table the full name of the table should be used, like: +... + +Currently the following metadata tables are available in Hive: +* files +* entries +* snapshots +* manifests +* partitions + +```sql +SELECT * FROM default.table_a.files; +``` + +### TIMETRAVEL +Hive supports snapshot id based and time base timetravel queries. +For these views it is possible to use projections / joins / filters / etc. +The function is available with the following syntax: +```sql +SELECT * FROM table_a FOR SYSTEM_TIME AS OF '2021-08-09 10:35:57'; +SELECT * FROM table_a FOR SYSTEM_VERSION AS OF 1234567; +``` + +You can expire snapshots of an Iceberg table using an ALTER TABLE query from Hive. You should periodically expire snapshots to delete data files that is no longer needed, and reduce the size of table metadata. + +Each write to an Iceberg table from Hive creates a new snapshot, or version, of a table. Snapshots can be used for time-travel queries, or the table can be rolled back to any valid snapshot. Snapshots accumulate until they are expired by the expire_snapshots operation. +Enter a query to expire snapshots having the following timestamp: `2021-12-09 05:39:18.689000000` +```sql +ALTER TABLE test_table EXECUTE expire_snapshots('2021-12-09 05:39:18.689000000'); +``` + +### Type compatibility + +Hive and Iceberg support different set of types. Iceberg can perform type conversion automatically, but not for all +combinations, so you may want to understand the type conversion in Iceberg in prior to design the types of columns in +your tables. You can enable auto-conversion through Hadoop configuration (not enabled by default): + +| Config key | Default | Description | +| -----------------------------------------| --------------------------- | --------------------------------------------------- | +| iceberg.mr.schema.auto.conversion | false | if Hive should perform type auto-conversion | + +### Hive type to Iceberg type + +This type conversion table describes how Hive types are converted to the Iceberg types. The conversion applies on both +creating Iceberg table and writing to Iceberg table via Hive. + +| Hive | Iceberg | Notes | +|------------------|-------------------------|-------| +| boolean | boolean | | +| short | integer | auto-conversion | +| byte | integer | auto-conversion | +| integer | integer | | +| long | long | | +| float | float | | +| double | double | | +| date | date | | +| timestamp | timestamp without timezone | | +| timestamplocaltz | timestamp with timezone | Hive 3 only | +| interval_year_month | | not supported | +| interval_day_time | | not supported | +| char | string | auto-conversion | +| varchar | string | auto-conversion | +| string | string | | +| binary | binary | | +| decimal | decimal | | +| struct | struct | | +| list | list | | +| map | map | | +| union | | not supported | + +### Table rollback + +Rolling back iceberg table's data to the state at an older table snapshot. + +Rollback to the last snapshot before a specific timestamp + +```sql +ALTER TABLE ice_t EXECUTE ROLLBACK('2022-05-12 00:00:00') +``` + +Rollback to a specific snapshot ID +```sql +ALTER TABLE ice_t EXECUTE ROLLBACK(1111); +``` diff --git a/site/docs/docs/nightly/docs/index.md b/site/docs/docs/nightly/docs/index.md new file mode 100644 index 000000000000..19dc954b51b3 --- /dev/null +++ b/site/docs/docs/nightly/docs/index.md @@ -0,0 +1,52 @@ +--- +title: "Introduction" +--- + + +# Documentation + +**Apache Iceberg is an open table format for huge analytic datasets.** Iceberg adds tables to compute engines including Spark, Trino, PrestoDB, Flink, Hive and Impala using a high-performance table format that works just like a SQL table. + +### User experience + +Iceberg avoids unpleasant surprises. Schema evolution works and won't inadvertently un-delete data. Users don't need to know about partitioning to get fast queries. + +* [Schema evolution](evolution.md#schema-evolution) supports add, drop, update, or rename, and has [no side-effects](evolution.md#correctness) +* [Hidden partitioning](partitioning.md) prevents user mistakes that cause silently incorrect results or extremely slow queries +* [Partition layout evolution](evolution.md#partition-evolution) can update the layout of a table as data volume or query patterns change +* [Time travel](spark-queries.md#time-travel) enables reproducible queries that use exactly the same table snapshot, or lets users easily examine changes +* Version rollback allows users to quickly correct problems by resetting tables to a good state + +### Reliability and performance + +Iceberg was built for huge tables. Iceberg is used in production where a single table can contain tens of petabytes of data and even these huge tables can be read without a distributed SQL engine. + +* [Scan planning is fast](performance.md#scan-planning) -- a distributed SQL engine isn't needed to read a table or find files +* [Advanced filtering](performance.md#data-filtering) -- data files are pruned with partition and column-level stats, using table metadata + +Iceberg was designed to solve correctness problems in eventually-consistent cloud object stores. + +* [Works with any cloud store](reliability.md) and reduces NN congestion when in HDFS, by avoiding listing and renames +* [Serializable isolation](reliability.md) -- table changes are atomic and readers never see partial or uncommitted changes +* [Multiple concurrent writers](reliability.md#concurrent-write-operations) use optimistic concurrency and will retry to ensure that compatible updates succeed, even when writes conflict + +### Open standard + +Iceberg has been designed and developed to be an open community standard with a [specification](../../spec.md) to ensure compatibility across languages and implementations. + +[Apache Iceberg is open source](../../community.md), and is developed at the [Apache Software Foundation](https://www.apache.org/). diff --git a/site/docs/docs/nightly/docs/java-api-quickstart.md b/site/docs/docs/nightly/docs/java-api-quickstart.md new file mode 100644 index 000000000000..cd6d647b62b1 --- /dev/null +++ b/site/docs/docs/nightly/docs/java-api-quickstart.md @@ -0,0 +1,317 @@ +--- +title: "Java Quickstart" +--- + + +# Java API Quickstart + +## Create a table + +Tables are created using either a [`Catalog`](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/catalog/Catalog.html) or an implementation of the [`Tables`](../../javadoc/{{ icebergVersion }}/index.html?org/apache/iceberg/Tables.html) interface. + +### Using a Hive catalog + +The Hive catalog connects to a Hive metastore to keep track of Iceberg tables. +You can initialize a Hive catalog with a name and some properties. +(see: [Catalog properties](configuration.md#catalog-properties)) + +**Note:** Currently, `setConf` is always required for hive catalogs, but this will change in the future. + +```java +import org.apache.iceberg.hive.HiveCatalog; + +HiveCatalog catalog = new HiveCatalog(); +catalog.setConf(spark.sparkContext().hadoopConfiguration()); // Configure using Spark's Hadoop configuration + +Map properties = new HashMap(); +properties.put("warehouse", "..."); +properties.put("uri", "..."); + +catalog.initialize("hive", properties); +``` + +The `Catalog` interface defines methods for working with tables, like `createTable`, `loadTable`, `renameTable`, and `dropTable`. `HiveCatalog` implements the `Catalog` interface. + +To create a table, pass an `Identifier` and a `Schema` along with other initial metadata: + +```java +import org.apache.iceberg.Table; +import org.apache.iceberg.catalog.TableIdentifier; + +TableIdentifier name = TableIdentifier.of("logging", "logs"); +Table table = catalog.createTable(name, schema, spec); + +// or to load an existing table, use the following line +// Table table = catalog.loadTable(name); +``` + +The logs [schema](#create-a-schema) and [partition spec](#create-a-partition-spec) are created below. + + +### Using a Hadoop catalog + +A Hadoop catalog doesn't need to connect to a Hive MetaStore, but can only be used with HDFS or similar file systems that support atomic rename. Concurrent writes with a Hadoop catalog are not safe with a local FS or S3. To create a Hadoop catalog: + +```java +import org.apache.hadoop.conf.Configuration; +import org.apache.iceberg.hadoop.HadoopCatalog; + +Configuration conf = new Configuration(); +String warehousePath = "hdfs://host:8020/warehouse_path"; +HadoopCatalog catalog = new HadoopCatalog(conf, warehousePath); +``` + +Like the Hive catalog, `HadoopCatalog` implements `Catalog`, so it also has methods for working with tables, like `createTable`, `loadTable`, and `dropTable`. + +This example creates a table with Hadoop catalog: + +```java +import org.apache.iceberg.Table; +import org.apache.iceberg.catalog.TableIdentifier; + +TableIdentifier name = TableIdentifier.of("logging", "logs"); +Table table = catalog.createTable(name, schema, spec); + +// or to load an existing table, use the following line +// Table table = catalog.loadTable(name); +``` + +The logs [schema](#create-a-schema) and [partition spec](#create-a-partition-spec) are created below. + + +### Using Hadoop tables + +Iceberg also supports tables that are stored in a directory in HDFS. Concurrent writes with a Hadoop tables are not safe when stored in the local FS or S3. Directory tables don't support all catalog operations, like rename, so they use the `Tables` interface instead of `Catalog`. + +To create a table in HDFS, use `HadoopTables`: + +```java +import org.apache.hadoop.conf.Configuration; +import org.apache.iceberg.hadoop.HadoopTables; +import org.apache.iceberg.Table; + +Configuration conf = new Configuration(); +HadoopTables tables = new HadoopTables(conf); +Table table = tables.create(schema, spec, table_location); + +// or to load an existing table, use the following line +// Table table = tables.load(table_location); +``` + +!!! danger + Hadoop tables shouldn't be used with file systems that do not support atomic rename. Iceberg relies on rename to synchronize concurrent commits for directory tables. + + +### Tables in Spark + +Spark uses both `HiveCatalog` and `HadoopTables` to load tables. Hive is used when the identifier passed to `load` or `save` is not a path, otherwise Spark assumes it is a path-based table. + +To read and write to tables from Spark see: + +* [SQL queries in Spark](spark-queries.md#querying-with-sql) +* [`INSERT INTO` in Spark](spark-writes.md#insert-into) +* [`MERGE INTO` in Spark](spark-writes.md#merge-into) + + +## Schemas + +### Create a schema + +This example creates a schema for a `logs` table: + +```java +import org.apache.iceberg.Schema; +import org.apache.iceberg.types.Types; + +Schema schema = new Schema( + Types.NestedField.required(1, "level", Types.StringType.get()), + Types.NestedField.required(2, "event_time", Types.TimestampType.withZone()), + Types.NestedField.required(3, "message", Types.StringType.get()), + Types.NestedField.optional(4, "call_stack", Types.ListType.ofRequired(5, Types.StringType.get())) + ); +``` + +When using the Iceberg API directly, type IDs are required. Conversions from other schema formats, like Spark, Avro, and Parquet will automatically assign new IDs. + +When a table is created, all IDs in the schema are re-assigned to ensure uniqueness. + +### Convert a schema from Avro + +To create an Iceberg schema from an existing Avro schema, use converters in `AvroSchemaUtil`: + +```java +import org.apache.avro.Schema; +import org.apache.avro.Schema.Parser; +import org.apache.iceberg.avro.AvroSchemaUtil; + +Schema avroSchema = new Parser().parse("{\"type\": \"record\" , ... }"); +Schema icebergSchema = AvroSchemaUtil.toIceberg(avroSchema); +``` + +### Convert a schema from Spark + +To create an Iceberg schema from an existing table, use converters in `SparkSchemaUtil`: + +```java +import org.apache.iceberg.spark.SparkSchemaUtil; + +Schema schema = SparkSchemaUtil.schemaForTable(sparkSession, table_name); +``` + +## Partitioning + +### Create a partition spec + +Partition specs describe how Iceberg should group records into data files. Partition specs are created for a table's schema using a builder. + +This example creates a partition spec for the `logs` table that partitions records by the hour of the log event's timestamp and by log level: + +```java +import org.apache.iceberg.PartitionSpec; + +PartitionSpec spec = PartitionSpec.builderFor(schema) + .hour("event_time") + .identity("level") + .build(); +``` + +For more information on the different partition transforms that Iceberg offers, visit [this page](../../spec.md#partitioning). + +## Branching and Tagging + +### Creating branches and tags + +New branches and tags can be created via the Java library's ManageSnapshots API. + +```java + +/* Create a branch test-branch which is retained for 1 week, and the latest 2 snapshots on test-branch will always be retained. +Snapshots on test-branch which are created within the last hour will also be retained. */ + +String branch = "test-branch"; +table.manageSnapshots() + .createBranch(branch, 3) + .setMinSnapshotsToKeep(branch, 2) + .setMaxSnapshotAgeMs(branch, 3600000) + .setMaxRefAgeMs(branch, 604800000) + .commit(); + +// Create a tag historical-tag at snapshot 10 which is retained for a day +String tag = "historical-tag" +table.manageSnapshots() + .createTag(tag, 10) + .setMaxRefAgeMs(tag, 86400000) + .commit(); +``` + +### Committing to branches + +Writing to a branch can be performed by specifying `toBranch` in the operation. For the full list refer to [UpdateOperations](api.md#update-operations). +```java +// Append FILE_A to branch test-branch +String branch = "test-branch"; + +table.newAppend() + .appendFile(FILE_A) + .toBranch(branch) + .commit(); + + +// Perform row level updates on "test-branch" +table.newRowDelta() + .addRows(DATA_FILE) + .addDeletes(DELETES) + .toBranch(branch) + .commit(); + + +// Perform a rewrite operation replacing small_file_1 and small_file_2 on "test-branch" with compacted_file. +table.newRewrite() + .rewriteFiles(ImmutableSet.of(small_file_1,small_file_2), ImmutableSet.of(compacted_file)) + .toBranch(branch) + .commit(); + +``` + +### Reading from branches and tags +Reading from a branch or tag can be done as usual via the Table Scan API, by passing in a branch or tag in the `useRef` API. When a branch is passed in, the snapshot that's used is the head of the branch. Note that currently reading from a branch and specifying an `asOfSnapshotId` in the scan is not supported. + +```java +// Read from the head snapshot of test-branch +TableScan branchRead = table.newScan().useRef("test-branch"); + +// Read from the snapshot referenced by audit-tag +Table tagRead = table.newScan().useRef("audit-tag"); +``` + +### Replacing and fast forwarding branches and tags + +The snapshots which existing branches and tags point to can be updated via the `replace` APIs. The fast forward operation is similar to git fast-forwarding. Fast forward can be used to advance a target branch to the head of a source branch or a tag when the target branch is an ancestor of the source. For both fast forward and replace, retention properties of the target branch are maintained by default. + +```java + +// Update "test-branch" to point to snapshot 4 +table.manageSnapshots() + .replaceBranch(branch, 4) + .commit() + +String tag = "audit-tag"; +// Replace "audit-tag" to point to snapshot 3 and update its retention +table.manageSnapshots() + .replaceBranch(tag, 4) + .setMaxRefAgeMs(1000) + .commit() + + +``` + +### Updating retention properties + +Retention properties for branches and tags can be updated as well. +Use the setMaxRefAgeMs for updating the retention property of the branch or tag itself. Branch snapshot retention properties can be updated via the `setMinSnapshotsToKeep` and `setMaxSnapshotAgeMs` APIs. + +```java +String branch = "test-branch"; +// Update retention properties for test-branch +table.manageSnapshots() + .setMinSnapshotsToKeep(branch, 10) + .setMaxSnapshotAgeMs(branch, 7200000) + .setMaxRefAgeMs(branch, 604800000) + .commit(); + +// Update retention properties for test-tag +table.manageSnapshots() + .setMaxRefAgeMs("test-tag", 604800000) + .commit(); +``` + +### Removing branches and tags + +Branches and tags can be removed via the `removeBranch` and `removeTag` APIs respectively + +```java +// Remove test-branch +table.manageSnapshots() + .removeBranch("test-branch") + .commit() + +// Remove test-tag +table.manageSnapshots() + .removeTag("test-tag") + .commit() +``` diff --git a/site/docs/docs/nightly/docs/jdbc.md b/site/docs/docs/nightly/docs/jdbc.md new file mode 100644 index 000000000000..7b525fb7ee77 --- /dev/null +++ b/site/docs/docs/nightly/docs/jdbc.md @@ -0,0 +1,70 @@ +--- +title: "JDBC" +--- + + +# Iceberg JDBC Integration + +## JDBC Catalog + +Iceberg supports using a table in a relational database to manage Iceberg tables through JDBC. +The database that JDBC connects to must support atomic transaction to allow the JDBC catalog implementation to +properly support atomic Iceberg table commits and read serializable isolation. + +### Configurations + +Because each database and database service provider might require different configurations, +the JDBC catalog allows arbitrary configurations through: + +| Property | Default | Description | +| -------------------- | --------------------------------- | ------------------------------------------------------ | +| uri | | the JDBC connection string | +| jdbc. | | any key value pairs to configure the JDBC connection | + +### Examples + + +#### Spark + +You can start a Spark session with a MySQL JDBC connection using the following configurations: + +```shell +spark-sql --packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }} \ + --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket/my/key/prefix \ + --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.jdbc.JdbcCatalog \ + --conf spark.sql.catalog.my_catalog.uri=jdbc:mysql://test.1234567890.us-west-2.rds.amazonaws.com:3306/default \ + --conf spark.sql.catalog.my_catalog.jdbc.verifyServerCertificate=true \ + --conf spark.sql.catalog.my_catalog.jdbc.useSSL=true \ + --conf spark.sql.catalog.my_catalog.jdbc.user=admin \ + --conf spark.sql.catalog.my_catalog.jdbc.password=pass +``` + +#### Java API + +```java +Class.forName("com.mysql.cj.jdbc.Driver"); // ensure JDBC driver is at runtime classpath +Map properties = new HashMap<>(); +properties.put(CatalogProperties.CATALOG_IMPL, JdbcCatalog.class.getName()); +properties.put(CatalogProperties.URI, "jdbc:mysql://localhost:3306/test"); +properties.put(JdbcCatalog.PROPERTY_PREFIX + "user", "admin"); +properties.put(JdbcCatalog.PROPERTY_PREFIX + "password", "pass"); +properties.put(CatalogProperties.WAREHOUSE_LOCATION, "s3://warehouse/path"); +Configuration hadoopConf = new Configuration(); // configs if you use HadoopFileIO +JdbcCatalog catalog = CatalogUtil.buildIcebergCatalog("test_jdbc_catalog", properties, hadoopConf); +``` diff --git a/site/docs/docs/nightly/docs/maintenance.md b/site/docs/docs/nightly/docs/maintenance.md new file mode 100644 index 000000000000..dd6bf7abdb5d --- /dev/null +++ b/site/docs/docs/nightly/docs/maintenance.md @@ -0,0 +1,157 @@ +--- +title: Maintenance +--- + + +# Maintenance + +!!! info + Maintenance operations require the `Table` instance. Please refer [Java API quickstart](java-api-quickstart.md#create-a-table) page to refer how to load an existing table. + +## Recommended Maintenance + +### Expire Snapshots + +Each write to an Iceberg table creates a new _snapshot_, or version, of a table. Snapshots can be used for time-travel queries, or the table can be rolled back to any valid snapshot. + +Snapshots accumulate until they are expired by the [`expireSnapshots`](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/Table.html#expireSnapshots--) operation. Regularly expiring snapshots is recommended to delete data files that are no longer needed, and to keep the size of table metadata small. + +This example expires snapshots that are older than 1 day: + +```java +Table table = ... +long tsToExpire = System.currentTimeMillis() - (1000 * 60 * 60 * 24); // 1 day +table.expireSnapshots() + .expireOlderThan(tsToExpire) + .commit(); +``` + +See the [`ExpireSnapshots` Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/ExpireSnapshots.html) to see more configuration options. + +There is also a Spark action that can run table expiration in parallel for large tables: + +```java +Table table = ... +SparkActions + .get() + .expireSnapshots(table) + .expireOlderThan(tsToExpire) + .execute(); +``` + +Expiring old snapshots removes them from metadata, so they are no longer available for time travel queries. + +!!! info + Data files are not deleted until they are no longer referenced by a snapshot that may be used for time travel or rollback. + Regularly expiring snapshots deletes unused data files. + + +### Remove old metadata files + +Iceberg keeps track of table metadata using JSON files. Each change to a table produces a new metadata file to provide atomicity. + +Old metadata files are kept for history by default. Tables with frequent commits, like those written by streaming jobs, may need to regularly clean metadata files. + +To automatically clean metadata files, set `write.metadata.delete-after-commit.enabled=true` in table properties. This will keep some metadata files (up to `write.metadata.previous-versions-max`) and will delete the oldest metadata file after each new one is created. + +| Property | Description | +| -------------------------------------------- |--------------------------------------------------------------------------| +| `write.metadata.delete-after-commit.enabled` | Whether to delete old **tracked** metadata files after each table commit | +| `write.metadata.previous-versions-max` | The number of old metadata files to keep | + +Note that this will only delete metadata files that are **tracked** in the metadata log and will not delete orphaned metadata files. +Example: With `write.metadata.delete-after-commit.enabled=false` and `write.metadata.previous-versions-max=10`, one will have 10 tracked metadata files and 90 orphaned metadata files after 100 commits. +Configuring `write.metadata.delete-after-commit.enabled=true` and `write.metadata.previous-versions-max=20` will not automatically delete metadata files. Tracked metadata files would be deleted again when reaching `write.metadata.previous-versions-max=20`. + +See [table write properties](configuration.md#write-properties) for more details. + +### Delete orphan files + +In Spark and other distributed processing engines, task or job failures can leave files that are not referenced by table metadata, and in some cases normal snapshot expiration may not be able to determine a file is no longer needed and delete it. + +To clean up these "orphan" files under a table location, use the `deleteOrphanFiles` action. + +```java +Table table = ... +SparkActions + .get() + .deleteOrphanFiles(table) + .execute(); +``` + +See the [DeleteOrphanFiles Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/actions/DeleteOrphanFiles.html) to see more configuration options. + +This action may take a long time to finish if you have lots of files in data and metadata directories. It is recommended to execute this periodically, but you may not need to execute this often. + +!!! info + It is dangerous to remove orphan files with a retention interval shorter than the time expected for any write to complete because it + might corrupt the table if in-progress files are considered orphaned and are deleted. The default interval is 3 days. + + +!!! info + Iceberg uses the string representations of paths when determining which files need to be removed. On some file systems, + the path can change over time, but it still represents the same file. For example, if you change authorities for an HDFS cluster, + none of the old path urls used during creation will match those that appear in a current listing. *This will lead to data loss when + RemoveOrphanFiles is run*. Please be sure the entries in your MetadataTables match those listed by the Hadoop + FileSystem API to avoid unintentional deletion. + + +## Optional Maintenance + +Some tables require additional maintenance. For example, streaming queries may produce small data files that should be [compacted into larger files](#compact-data-files). And some tables can benefit from [rewriting manifest files](#rewrite-manifests) to make locating data for queries much faster. + +### Compact data files + +Iceberg tracks each data file in a table. More data files leads to more metadata stored in manifest files, and small data files causes an unnecessary amount of metadata and less efficient queries from file open costs. + +Iceberg can compact data files in parallel using Spark with the `rewriteDataFiles` action. This will combine small files into larger files to reduce metadata overhead and runtime file open cost. + +```java +Table table = ... +SparkActions + .get() + .rewriteDataFiles(table) + .filter(Expressions.equal("date", "2020-08-18")) + .option("target-file-size-bytes", Long.toString(500 * 1024 * 1024)) // 500 MB + .execute(); +``` + +The `files` metadata table is useful for inspecting data file sizes and determining when to compact partitions. + +See the [`RewriteDataFiles` Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/actions/RewriteDataFiles.html) to see more configuration options. + +### Rewrite manifests + +Iceberg uses metadata in its manifest list and manifest files speed up query planning and to prune unnecessary data files. The metadata tree functions as an index over a table's data. + +Manifests in the metadata tree are automatically compacted in the order they are added, which makes queries faster when the write pattern aligns with read filters. For example, writing hourly-partitioned data as it arrives is aligned with time range query filters. + +When a table's write pattern doesn't align with the query pattern, metadata can be rewritten to re-group data files into manifests using `rewriteManifests` or the `rewriteManifests` action (for parallel rewrites using Spark). + +This example rewrites small manifests and groups data files by the first partition field. + +```java +Table table = ... +SparkActions + .get() + .rewriteManifests(table) + .rewriteIf(file -> file.length() < 10 * 1024 * 1024) // 10 MB + .execute(); +``` + +See the [`RewriteManifests` Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/actions/RewriteManifests.html) to see more configuration options. diff --git a/site/docs/docs/nightly/docs/nessie.md b/site/docs/docs/nightly/docs/nessie.md new file mode 100644 index 000000000000..809a3309eb81 --- /dev/null +++ b/site/docs/docs/nightly/docs/nessie.md @@ -0,0 +1,160 @@ +--- +title: "Nessie" +--- + + +# Iceberg Nessie Integration + +Iceberg provides integration with Nessie through the `iceberg-nessie` module. +This section describes how to use Iceberg with Nessie. Nessie provides several key features on top of Iceberg: + +* multi-table transactions +* git-like operations (eg branches, tags, commits) +* hive-like metastore capabilities + +See [Project Nessie](https://projectnessie.org) for more information on Nessie. Nessie requires a server to run, see +[Getting Started](https://projectnessie.org/try/) to start a Nessie server. + +## Enabling Nessie Catalog + +The `iceberg-nessie` module is bundled with Spark and Flink runtimes for all versions from `0.11.0`. To get started +with Nessie (with spark-3.3) and Iceberg simply add the Iceberg runtime to your process. Eg: `spark-sql --packages +org.apache.iceberg:iceberg-spark-runtime-3.3_2.12:{{ icebergVersion }}`. + +## Spark SQL Extensions + +From Spark 3.1 and above, Nessie SQL extensions can be used to manage the Nessie repo as shown below. +Example for Spark 3.3 with scala 2.12: + +``` +bin/spark-sql + --packages "org.apache.iceberg:iceberg-spark-runtime-3.3_2.12:{{ icebergVersion }},org.projectnessie.nessie-integrations:nessie-spark-extensions-3.3_2.12:{{ nessieVersion }}" + --conf spark.sql.extensions="org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions,org.projectnessie.spark.extensions.NessieSparkSessionExtensions" + --conf +``` +Please refer [Nessie SQL extension document](https://projectnessie.org/tools/sql/) to learn more about it. + +## Nessie Catalog + +One major feature introduced in release `0.11.0` is the ability to easily interact with a [Custom Catalog](custom-catalog.md) from Spark and Flink. See [Spark Configuration](spark-configuration.md#catalog-configuration) + and [Flink Configuration](flink.md#custom-catalog) for instructions for adding a custom catalog to Iceberg. + +To use the Nessie Catalog the following properties are required: + +* `warehouse`. Like most other catalogs the warehouse property is a file path to where this catalog should store tables. +* `uri`. This is the Nessie server base uri. Eg `http://localhost:19120/api/v1`. +* `ref` (optional). This is the Nessie branch or tag you want to work in. + +To run directly in Java this looks like: + +``` java +Map options = new HashMap<>(); +options.put("warehouse", "/path/to/warehouse"); +options.put("ref", "main"); +options.put("uri", "https://localhost:19120/api/v1"); +Catalog nessieCatalog = CatalogUtil.loadCatalog("org.apache.iceberg.nessie.NessieCatalog", "nessie", options, hadoopConfig); +``` + +and in Spark: + +``` java +conf.set("spark.sql.catalog.nessie.warehouse", "/path/to/warehouse"); +conf.set("spark.sql.catalog.nessie.uri", "http://localhost:19120/api/v1") +conf.set("spark.sql.catalog.nessie.ref", "main") +conf.set("spark.sql.catalog.nessie.catalog-impl", "org.apache.iceberg.nessie.NessieCatalog") +conf.set("spark.sql.catalog.nessie", "org.apache.iceberg.spark.SparkCatalog") +conf.set("spark.sql.extensions", "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions,org.projectnessie.spark.extensions.NessieSparkSessionExtensions") +``` +This is how it looks in Flink via the Python API (additional details can be found [here](flink.md#preparation-when-using-flinks-python-api)): +```python +import os +from pyflink.datastream import StreamExecutionEnvironment +from pyflink.table import StreamTableEnvironment + +env = StreamExecutionEnvironment.get_execution_environment() +iceberg_flink_runtime_jar = os.path.join(os.getcwd(), "iceberg-flink-runtime-{{ icebergVersion }}.jar") +env.add_jars("file://{}".format(iceberg_flink_runtime_jar)) +table_env = StreamTableEnvironment.create(env) + +table_env.execute_sql("CREATE CATALOG nessie_catalog WITH (" + "'type'='iceberg', " + "'catalog-impl'='org.apache.iceberg.nessie.NessieCatalog', " + "'uri'='http://localhost:19120/api/v1', " + "'ref'='main', " + "'warehouse'='/path/to/warehouse')") +``` + +There is nothing special above about the `nessie` name. A spark catalog can have any name, the important parts are the +settings for the `catalog-impl` and the required config to start Nessie correctly. +Once you have a Nessie catalog you have access to your entire Nessie repo. You can then perform create/delete/merge +operations on branches and perform commits on branches. Each Iceberg table in a Nessie Catalog is identified by an +arbitrary length namespace and table name (eg `data.base.name.table`). These namespaces are implicit and don't need to +be created separately. Any transaction on a Nessie enabled Iceberg table is a single commit in Nessie. Nessie commits +can encompass an arbitrary number of actions on an arbitrary number of tables, however in Iceberg this will be limited +to the set of single table transactions currently available. + +Further operations such as merges, viewing the commit log or diffs are performed by direct interaction with the +`NessieClient` in java or by using the python client or cli. See [Nessie CLI](https://projectnessie.org/tools/cli/) for +more details on the CLI and [Spark Guide](https://projectnessie.org/tools/iceberg/spark/) for a more complete description of +Nessie functionality. + +## Nessie and Iceberg + +For most cases Nessie acts just like any other Catalog for Iceberg: providing a logical organization of a set of tables +and providing atomicity to transactions. However, using Nessie opens up other interesting possibilities. When using Nessie with +Iceberg every Iceberg transaction becomes a Nessie commit. This history can be listed, merged or cherry-picked across branches. + +### Loosely coupled transactions + +By creating a branch and performing a set of operations on that branch you can approximate a multi-table transaction. +A sequence of commits can be performed on the newly created branch and then merged back into the main branch atomically. +This gives the appearance of a series of connected changes being exposed to the main branch simultaneously. While downstream +consumers will see multiple transactions appear at once this isn't a true multi-table transaction on the database. It is +effectively a fast-forward merge of multiple commits (in git language) and each operation from the branch is its own distinct +transaction and commit. This is different from a real multi-table transaction where all changes would be in the same commit. +This does allow multiple applications to take part in modifying a branch and for this distributed set of transactions to be +exposed to the downstream users simultaneously. + + +### Experimentation + +Changes to a table can be tested in a branch before merging back into main. This is particularly useful when performing +large changes like schema evolution or partition evolution. A partition evolution could be performed in a branch and you +would be able to test out the change (eg performance benchmarks) before merging it. This provides great flexibility in +performing on-line table modifications and testing without interrupting downstream use cases. If the changes are +incorrect or not performant the branch can be dropped without being merged. + +### Further use cases + +Please see the [Nessie Documentation](https://projectnessie.org/features/) for further descriptions of +Nessie features. + +!!! danger + Regular table maintenance in Iceberg is complicated when using nessie. Please consult + [Management Services](https://projectnessie.org/features/management/) before performing any + [table maintenance](maintenance.md). + + +## Example + +Please have a look at the [Nessie Demos repo](https://github.com/projectnessie/nessie-demos) +for different examples of Nessie and Iceberg in action together. + +## Future Improvements + +* Iceberg multi-table transactions. Changes to multiple Iceberg tables in the same transaction, isolation levels etc diff --git a/site/docs/docs/nightly/docs/partitioning.md b/site/docs/docs/nightly/docs/partitioning.md new file mode 100644 index 000000000000..757daebd515e --- /dev/null +++ b/site/docs/docs/nightly/docs/partitioning.md @@ -0,0 +1,95 @@ +--- +title: Partitioning +--- + + +# Partitioning + +## What is partitioning? + +Partitioning is a way to make queries faster by grouping similar rows together when writing. + +For example, queries for log entries from a `logs` table would usually include a time range, like this query for logs between 10 and 12 AM: + +```sql +SELECT level, message FROM logs +WHERE event_time BETWEEN '2018-12-01 10:00:00' AND '2018-12-01 12:00:00' +``` + +Configuring the `logs` table to partition by the date of `event_time` will group log events into files with the same event date. Iceberg keeps track of that date and will use it to skip files for other dates that don't have useful data. + +Iceberg can partition timestamps by year, month, day, and hour granularity. It can also use a categorical column, like `level` in this logs example, to store rows together and speed up queries. + + +## What does Iceberg do differently? + +Other tables formats like Hive support partitioning, but Iceberg supports *hidden partitioning*. + +* Iceberg handles the tedious and error-prone task of producing partition values for rows in a table. +* Iceberg avoids reading unnecessary partitions automatically. Consumers don't need to know how the table is partitioned and add extra filters to their queries. +* Iceberg partition layouts can evolve as needed. + +### Partitioning in Hive + +To demonstrate the difference, consider how Hive would handle a `logs` table. + +In Hive, partitions are explicit and appear as a column, so the `logs` table would have a column called `event_date`. When writing, an insert needs to supply the data for the `event_date` column: + +```sql +INSERT INTO logs PARTITION (event_date) + SELECT level, message, event_time, format_time(event_time, 'YYYY-MM-dd') + FROM unstructured_log_source +``` + +Similarly, queries that search through the `logs` table must have an `event_date` filter in addition to an `event_time` filter. + +```sql +SELECT level, count(1) as count FROM logs +WHERE event_time BETWEEN '2018-12-01 10:00:00' AND '2018-12-01 12:00:00' + AND event_date = '2018-12-01' +``` + +If the `event_date` filter were missing, Hive would scan through every file in the table because it doesn't know that the `event_time` column is related to the `event_date` column. + +### Problems with Hive partitioning + +Hive must be given partition values. In the logs example, it doesn't know the relationship between `event_time` and `event_date`. + +This leads to several problems: + +* Hive can't validate partition values -- it is up to the writer to produce the correct value + - Using the wrong format, `2018-12-01` instead of `20181201`, produces silently incorrect results, not query failures + - Using the wrong source column, like `processing_time`, or time zone also causes incorrect results, not failures +* It is up to the user to write queries correctly + - Using the wrong format also leads to silently incorrect results + - Users that don't understand a table's physical layout get needlessly slow queries -- Hive can't translate filters automatically +* Working queries are tied to the table's partitioning scheme, so partitioning configuration cannot be changed without breaking queries + +### Iceberg's hidden partitioning + +Iceberg produces partition values by taking a column value and optionally transforming it. Iceberg is responsible for converting `event_time` into `event_date`, and keeps track of the relationship. + +Table partitioning is configured using these relationships. The `logs` table would be partitioned by `date(event_time)` and `level`. + +Because Iceberg doesn't require user-maintained partition columns, it can hide partitioning. Partition values are produced correctly every time and always used to speed up queries, when possible. Producers and consumers wouldn't even see `event_date`. + +Most importantly, queries no longer depend on a table's physical layout. With a separation between physical and logical, Iceberg tables can evolve partition schemes over time as data volume changes. Misconfigured tables can be fixed without an expensive migration. + +For details about all the supported hidden partition transformations, see the [Partition Transforms](../../spec.md#partition-transforms) section. + +For details about updating a table's partition spec, see the [partition evolution](evolution.md#partition-evolution) section. diff --git a/site/docs/docs/nightly/docs/performance.md b/site/docs/docs/nightly/docs/performance.md new file mode 100644 index 000000000000..cbe870347a1d --- /dev/null +++ b/site/docs/docs/nightly/docs/performance.md @@ -0,0 +1,55 @@ +--- +title: Performance +--- + + +# Performance + +* Iceberg is designed for huge tables and is used in production where a *single table* can contain tens of petabytes of data. +* Even multi-petabyte tables can be read from a single node, without needing a distributed SQL engine to sift through table metadata. + +## Scan planning + +Scan planning is the process of finding the files in a table that are needed for a query. + +Planning in an Iceberg table fits on a single node because Iceberg's metadata can be used to prune *metadata* files that aren't needed, in addition to filtering *data* files that don't contain matching data. + +Fast scan planning from a single node enables: + +* Lower latency SQL queries -- by eliminating a distributed scan to plan a distributed scan +* Access from any client -- stand-alone processes can read data directly from Iceberg tables + +### Metadata filtering + +Iceberg uses two levels of metadata to track the files in a snapshot. + +* **Manifest files** store a list of data files, along each data file's partition data and column-level stats +* A **manifest list** stores the snapshot's list of manifests, along with the range of values for each partition field + +For fast scan planning, Iceberg first filters manifests using the partition value ranges in the manifest list. Then, it reads each manifest to get data files. With this scheme, the manifest list acts as an index over the manifest files, making it possible to plan without reading all manifests. + +In addition to partition value ranges, a manifest list also stores the number of files added or deleted in a manifest to speed up operations like snapshot expiration. + +### Data filtering + +Manifest files include a tuple of partition data and column-level stats for each data file. + +During planning, query predicates are automatically converted to predicates on the partition data and applied first to filter data files. Next, column-level value counts, null counts, lower bounds, and upper bounds are used to eliminate files that cannot match the query predicate. + +By using upper and lower bounds to filter data files at planning time, Iceberg uses clustered data to eliminate splits without running tasks. In some cases, this is a [10x performance improvement](https://conferences.oreilly.com/strata/strata-ny-2018/cdn.oreillystatic.com/en/assets/1/event/278/Introducing%20Iceberg_%20Tables%20designed%20for%20object%20stores%20Presentation.pdf +). diff --git a/site/docs/docs/nightly/docs/reliability.md b/site/docs/docs/nightly/docs/reliability.md new file mode 100644 index 000000000000..7628b017c872 --- /dev/null +++ b/site/docs/docs/nightly/docs/reliability.md @@ -0,0 +1,68 @@ +--- +title: Reliability +--- + + +# Reliability + +Iceberg was designed to solve correctness problems that affect Hive tables running in S3. + +Hive tables track data files using both a central metastore for partitions and a file system for individual files. This makes atomic changes to a table's contents impossible, and eventually consistent stores like S3 may return incorrect results due to the use of listing files to reconstruct the state of a table. It also requires job planning to make many slow listing calls: O(n) with the number of partitions. + +Iceberg tracks the complete list of data files in each [snapshot](../../terms.md#snapshot) using a persistent tree structure. Every write or delete produces a new snapshot that reuses as much of the previous snapshot's metadata tree as possible to avoid high write volumes. + +Valid snapshots in an Iceberg table are stored in the table metadata file, along with a reference to the current snapshot. Commits replace the path of the current table metadata file using an atomic operation. This ensures that all updates to table data and metadata are atomic, and is the basis for [serializable isolation](https://en.wikipedia.org/wiki/Isolation_(database_systems)#Serializable). + +This results in improved reliability guarantees: + +* **Serializable isolation**: All table changes occur in a linear history of atomic table updates +* **Reliable reads**: Readers always use a consistent snapshot of the table without holding a lock +* **Version history and rollback**: Table snapshots are kept as history and tables can roll back if a job produces bad data +* **Safe file-level operations**. By supporting atomic changes, Iceberg enables new use cases, like safely compacting small files and safely appending late data to tables + +This design also has performance benefits: + +* **O(1) RPCs to plan**: Instead of listing O(n) directories in a table to plan a job, reading a snapshot requires O(1) RPC calls +* **Distributed planning**: File pruning and predicate push-down is distributed to jobs, removing the metastore as a bottleneck +* **Finer granularity partitioning**: Distributed planning and O(1) RPC calls remove the current barriers to finer-grained partitioning + + +## Concurrent write operations + +Iceberg supports multiple concurrent writes using optimistic concurrency. + +Each writer assumes that no other writers are operating and writes out new table metadata for an operation. Then, the writer attempts to commit by atomically swapping the new table metadata file for the existing metadata file. + +If the atomic swap fails because another writer has committed, the failed writer retries by writing a new metadata tree based on the new current table state. + +### Cost of retries + +Writers avoid expensive retry operations by structuring changes so that work can be reused across retries. + +For example, appends usually create a new manifest file for the appended data files, which can be added to the table without rewriting the manifest on every attempt. + +### Retry validation + +Commits are structured as assumptions and actions. After a conflict, a writer checks that the assumptions are met by the current table state. If the assumptions are met, then it is safe to re-apply the actions and commit. + +For example, a compaction might rewrite `file_a.avro` and `file_b.avro` as `merged.parquet`. This is safe to commit as long as the table still contains both `file_a.avro` and `file_b.avro`. If either file was deleted by a conflicting commit, then the operation must fail. Otherwise, it is safe to remove the source files and add the merged file. + + +## Compatibility + +By avoiding file listing and rename operations, Iceberg tables are compatible with any object store. No consistent listing is required. diff --git a/site/docs/docs/nightly/docs/schemas.md b/site/docs/docs/nightly/docs/schemas.md new file mode 100644 index 000000000000..a22c9ba7a331 --- /dev/null +++ b/site/docs/docs/nightly/docs/schemas.md @@ -0,0 +1,44 @@ +--- +title: Schemas +--- + + +# Schemas + +Iceberg tables support the following types: + +| Type | Description | Notes | +|--------------------|--------------------------------------------------------------------------|--------------------------------------------------| +| **`boolean`** | True or false | | +| **`int`** | 32-bit signed integers | Can promote to `long` | +| **`long`** | 64-bit signed integers | | +| **`float`** | [32-bit IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) floating point | Can promote to `double` | +| **`double`** | [64-bit IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) floating point | | +| **`decimal(P,S)`** | Fixed-point decimal; precision P, scale S | Scale is fixed and precision must be 38 or less | +| **`date`** | Calendar date without timezone or time | | +| **`time`** | Time of day without date, timezone | Stored as microseconds | +| **`timestamp`** | Timestamp without timezone | Stored as microseconds | +| **`timestamptz`** | Timestamp with timezone | Stored as microseconds | +| **`string`** | Arbitrary-length character sequences | Encoded with UTF-8 | +| **`fixed(L)`** | Fixed-length byte array of length L | | +| **`binary`** | Arbitrary-length byte array | | +| **`struct<...>`** | A record with named fields of any data type | | +| **`list`** | A list with elements of any data type | | +| **`map`** | A map with keys and values of any data type | | + +Iceberg tracks each field in a table schema using an ID that is never reused in a table. See [correctness guarantees](evolution.md#correctness) for more information. diff --git a/site/docs/docs/nightly/docs/spark-configuration.md b/site/docs/docs/nightly/docs/spark-configuration.md new file mode 100644 index 000000000000..a5447dccdacd --- /dev/null +++ b/site/docs/docs/nightly/docs/spark-configuration.md @@ -0,0 +1,188 @@ +--- +title: "Configuration" +--- + + +# Spark Configuration + +## Catalogs + +Spark adds an API to plug in table catalogs that are used to load, create, and manage Iceberg tables. Spark catalogs are configured by setting Spark properties under `spark.sql.catalog`. + +This creates an Iceberg catalog named `hive_prod` that loads tables from a Hive metastore: + +```plain +spark.sql.catalog.hive_prod = org.apache.iceberg.spark.SparkCatalog +spark.sql.catalog.hive_prod.type = hive +spark.sql.catalog.hive_prod.uri = thrift://metastore-host:port +# omit uri to use the same URI as Spark: hive.metastore.uris in hive-site.xml +``` + +Below is an example for a REST catalog named `rest_prod` that loads tables from REST URL `http://localhost:8080`: + +```plain +spark.sql.catalog.rest_prod = org.apache.iceberg.spark.SparkCatalog +spark.sql.catalog.rest_prod.type = rest +spark.sql.catalog.rest_prod.uri = http://localhost:8080 +``` + +Iceberg also supports a directory-based catalog in HDFS that can be configured using `type=hadoop`: + +```plain +spark.sql.catalog.hadoop_prod = org.apache.iceberg.spark.SparkCatalog +spark.sql.catalog.hadoop_prod.type = hadoop +spark.sql.catalog.hadoop_prod.warehouse = hdfs://nn:8020/warehouse/path +``` + +!!! info + The Hive-based catalog only loads Iceberg tables. To load non-Iceberg tables in the same Hive metastore, use a [session catalog](#replacing-the-session-catalog). + + +### Catalog configuration + +A catalog is created and named by adding a property `spark.sql.catalog.(catalog-name)` with an implementation class for its value. + +Iceberg supplies two implementations: + +* `org.apache.iceberg.spark.SparkCatalog` supports a Hive Metastore or a Hadoop warehouse as a catalog +* `org.apache.iceberg.spark.SparkSessionCatalog` adds support for Iceberg tables to Spark's built-in catalog, and delegates to the built-in catalog for non-Iceberg tables + +Both catalogs are configured using properties nested under the catalog name. Common configuration properties for Hive and Hadoop are: + +| Property | Values | Description | +| -------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------- | +| spark.sql.catalog._catalog-name_.type | `hive`, `hadoop` or `rest` | The underlying Iceberg catalog implementation, `HiveCatalog`, `HadoopCatalog`, `RESTCatalog` or left unset if using a custom catalog | +| spark.sql.catalog._catalog-name_.catalog-impl | | The custom Iceberg catalog implementation. If `type` is null, `catalog-impl` must not be null. | +| spark.sql.catalog._catalog-name_.io-impl | | The custom FileIO implementation. | +| spark.sql.catalog._catalog-name_.metrics-reporter-impl | | The custom MetricsReporter implementation. | +| spark.sql.catalog._catalog-name_.default-namespace | default | The default current namespace for the catalog | +| spark.sql.catalog._catalog-name_.uri | thrift://host:port | Hive metastore URL for hive typed catalog, REST URL for REST typed catalog | +| spark.sql.catalog._catalog-name_.warehouse | hdfs://nn:8020/warehouse/path | Base path for the warehouse directory | +| spark.sql.catalog._catalog-name_.cache-enabled | `true` or `false` | Whether to enable catalog cache, default value is `true` | +| spark.sql.catalog._catalog-name_.cache.expiration-interval-ms | `30000` (30 seconds) | Duration after which cached catalog entries are expired; Only effective if `cache-enabled` is `true`. `-1` disables cache expiration and `0` disables caching entirely, irrespective of `cache-enabled`. Default is `30000` (30 seconds) | +| spark.sql.catalog._catalog-name_.table-default._propertyKey_ | | Default Iceberg table property value for property key _propertyKey_, which will be set on tables created by this catalog if not overridden | +| spark.sql.catalog._catalog-name_.table-override._propertyKey_ | | Enforced Iceberg table property value for property key _propertyKey_, which cannot be overridden by user | + +Additional properties can be found in common [catalog configuration](configuration.md#catalog-properties). + + +### Using catalogs + +Catalog names are used in SQL queries to identify a table. In the examples above, `hive_prod` and `hadoop_prod` can be used to prefix database and table names that will be loaded from those catalogs. + +```sql +SELECT * FROM hive_prod.db.table -- load db.table from catalog hive_prod +``` + +Spark 3 keeps track of the current catalog and namespace, which can be omitted from table names. + +```sql +USE hive_prod.db; +SELECT * FROM table -- load db.table from catalog hive_prod +``` + +To see the current catalog and namespace, run `SHOW CURRENT NAMESPACE`. + +### Replacing the session catalog + +To add Iceberg table support to Spark's built-in catalog, configure `spark_catalog` to use Iceberg's `SparkSessionCatalog`. + +```plain +spark.sql.catalog.spark_catalog = org.apache.iceberg.spark.SparkSessionCatalog +spark.sql.catalog.spark_catalog.type = hive +``` + +Spark's built-in catalog supports existing v1 and v2 tables tracked in a Hive Metastore. This configures Spark to use Iceberg's `SparkSessionCatalog` as a wrapper around that session catalog. When a table is not an Iceberg table, the built-in catalog will be used to load it instead. + +This configuration can use same Hive Metastore for both Iceberg and non-Iceberg tables. + +### Using catalog specific Hadoop configuration values + +Similar to configuring Hadoop properties by using `spark.hadoop.*`, it's possible to set per-catalog Hadoop configuration values when using Spark by adding the property for the catalog with the prefix `spark.sql.catalog.(catalog-name).hadoop.*`. These properties will take precedence over values configured globally using `spark.hadoop.*` and will only affect Iceberg tables. + +```plain +spark.sql.catalog.hadoop_prod.hadoop.fs.s3a.endpoint = http://aws-local:9000 +``` + +### Loading a custom catalog + +Spark supports loading a custom Iceberg `Catalog` implementation by specifying the `catalog-impl` property. Here is an example: + +```plain +spark.sql.catalog.custom_prod = org.apache.iceberg.spark.SparkCatalog +spark.sql.catalog.custom_prod.catalog-impl = com.my.custom.CatalogImpl +spark.sql.catalog.custom_prod.my-additional-catalog-config = my-value +``` + +## SQL Extensions + +Iceberg 0.11.0 and later add an extension module to Spark to add new SQL commands, like `CALL` for stored procedures or `ALTER TABLE ... WRITE ORDERED BY`. + +Using those SQL commands requires adding Iceberg extensions to your Spark environment using the following Spark property: + + +| Spark extensions property | Iceberg extensions implementation | +|---------------------------|---------------------------------------------------------------------| +| `spark.sql.extensions` | `org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions` | + +## Runtime configuration + +### Read options + +Spark read options are passed when configuring the DataFrameReader, like this: + +```scala +// time travel +spark.read + .option("snapshot-id", 10963874102873L) + .table("catalog.db.table") +``` + +| Spark option | Default | Description | +| --------------- | --------------------- | ----------------------------------------------------------------------------------------- | +| snapshot-id | (latest) | Snapshot ID of the table snapshot to read | +| as-of-timestamp | (latest) | A timestamp in milliseconds; the snapshot used will be the snapshot current at this time. | +| split-size | As per table property | Overrides this table's read.split.target-size and read.split.metadata-target-size | +| lookback | As per table property | Overrides this table's read.split.planning-lookback | +| file-open-cost | As per table property | Overrides this table's read.split.open-file-cost | +| vectorization-enabled | As per table property | Overrides this table's read.parquet.vectorization.enabled | +| batch-size | As per table property | Overrides this table's read.parquet.vectorization.batch-size | +| stream-from-timestamp | (none) | A timestamp in milliseconds to stream from; if before the oldest known ancestor snapshot, the oldest will be used | + +### Write options + +Spark write options are passed when configuring the DataFrameWriter, like this: + +```scala +// write with Avro instead of Parquet +df.write + .option("write-format", "avro") + .option("snapshot-property.key", "value") + .insertInto("catalog.db.table") +``` + +| Spark option | Default | Description | +| ---------------------- | -------------------------- | ------------------------------------------------------------ | +| write-format | Table write.format.default | File format to use for this write operation; parquet, avro, or orc | +| target-file-size-bytes | As per table property | Overrides this table's write.target-file-size-bytes | +| check-nullability | true | Sets the nullable check on fields | +| snapshot-property._custom-key_ | null | Adds an entry with custom-key and corresponding value in the snapshot summary | +| fanout-enabled | false | Overrides this table's write.spark.fanout.enabled | +| check-ordering | true | Checks if input schema and table schema are same | +| isolation-level | null | Desired isolation level for Dataframe overwrite operations. `null` => no checks (for idempotent writes), `serializable` => check for concurrent inserts or deletes in destination partitions, `snapshot` => checks for concurrent deletes in destination partitions. | +| validate-from-snapshot-id | null | If isolation level is set, id of base snapshot from which to check concurrent write conflicts into a table. Should be the snapshot before any reads from the table. Can be obtained via [Table API](api.md#table-metadata) or [Snapshots table](spark-queries.md#snapshots). If null, the table's oldest known snapshot is used. | diff --git a/site/docs/docs/nightly/docs/spark-ddl.md b/site/docs/docs/nightly/docs/spark-ddl.md new file mode 100644 index 000000000000..8e84a98b3938 --- /dev/null +++ b/site/docs/docs/nightly/docs/spark-ddl.md @@ -0,0 +1,521 @@ +--- +title: "DDL" +--- + + +# Spark DDL + +To use Iceberg in Spark, first configure [Spark catalogs](spark-configuration.md). Iceberg uses Apache Spark's DataSourceV2 API for data source and catalog implementations. + +## `CREATE TABLE` + +Spark 3 can create tables in any Iceberg catalog with the clause `USING iceberg`: + +```sql +CREATE TABLE prod.db.sample ( + id bigint COMMENT 'unique id', + data string) +USING iceberg +``` + +Iceberg will convert the column type in Spark to corresponding Iceberg type. Please check the section of [type compatibility on creating table](spark-writes.md#spark-type-to-iceberg-type) for details. + +Table create commands, including CTAS and RTAS, support the full range of Spark create clauses, including: + +* `PARTITIONED BY (partition-expressions)` to configure partitioning +* `LOCATION '(fully-qualified-uri)'` to set the table location +* `COMMENT 'table documentation'` to set a table description +* `TBLPROPERTIES ('key'='value', ...)` to set [table configuration](configuration.md) + +Create commands may also set the default format with the `USING` clause. This is only supported for `SparkCatalog` because Spark handles the `USING` clause differently for the built-in catalog. + +### `PARTITIONED BY` + +To create a partitioned table, use `PARTITIONED BY`: + +```sql +CREATE TABLE prod.db.sample ( + id bigint, + data string, + category string) +USING iceberg +PARTITIONED BY (category) +``` + +The `PARTITIONED BY` clause supports transform expressions to create [hidden partitions](partitioning.md). + +```sql +CREATE TABLE prod.db.sample ( + id bigint, + data string, + category string, + ts timestamp) +USING iceberg +PARTITIONED BY (bucket(16, id), days(ts), category) +``` + +Supported transformations are: + +* `years(ts)`: partition by year +* `months(ts)`: partition by month +* `days(ts)` or `date(ts)`: equivalent to dateint partitioning +* `hours(ts)` or `date_hour(ts)`: equivalent to dateint and hour partitioning +* `bucket(N, col)`: partition by hashed value mod N buckets +* `truncate(L, col)`: partition by value truncated to L + * Strings are truncated to the given length + * Integers and longs truncate to bins: `truncate(10, i)` produces partitions 0, 10, 20, 30, ... + +## `CREATE TABLE ... AS SELECT` + +Iceberg supports CTAS as an atomic operation when using a [`SparkCatalog`](spark-configuration.md#catalog-configuration). CTAS is supported, but is not atomic when using [`SparkSessionCatalog`](spark-configuration.md#replacing-the-session-catalog). + +```sql +CREATE TABLE prod.db.sample +USING iceberg +AS SELECT ... +``` + +The newly created table won't inherit the partition spec and table properties from the source table in SELECT, you can use PARTITIONED BY and TBLPROPERTIES in CTAS to declare partition spec and table properties for the new table. + +```sql +CREATE TABLE prod.db.sample +USING iceberg +PARTITIONED BY (part) +TBLPROPERTIES ('key'='value') +AS SELECT ... +``` + +## `REPLACE TABLE ... AS SELECT` + +Iceberg supports RTAS as an atomic operation when using a [`SparkCatalog`](spark-configuration.md#catalog-configuration). RTAS is supported, but is not atomic when using [`SparkSessionCatalog`](spark-configuration.md#replacing-the-session-catalog). + +Atomic table replacement creates a new snapshot with the results of the `SELECT` query, but keeps table history. + +```sql +REPLACE TABLE prod.db.sample +USING iceberg +AS SELECT ... +``` +```sql +REPLACE TABLE prod.db.sample +USING iceberg +PARTITIONED BY (part) +TBLPROPERTIES ('key'='value') +AS SELECT ... +``` +```sql +CREATE OR REPLACE TABLE prod.db.sample +USING iceberg +AS SELECT ... +``` + +The schema and partition spec will be replaced if changed. To avoid modifying the table's schema and partitioning, use `INSERT OVERWRITE` instead of `REPLACE TABLE`. +The new table properties in the `REPLACE TABLE` command will be merged with any existing table properties. The existing table properties will be updated if changed else they are preserved. + +## `DROP TABLE` + +The drop table behavior changed in 0.14. + +Prior to 0.14, running `DROP TABLE` would remove the table from the catalog and delete the table contents as well. + +From 0.14 onwards, `DROP TABLE` would only remove the table from the catalog. +In order to delete the table contents `DROP TABLE PURGE` should be used. + +### `DROP TABLE` + +To drop the table from the catalog, run: + +```sql +DROP TABLE prod.db.sample +``` + +### `DROP TABLE PURGE` + +To drop the table from the catalog and delete the table's contents, run: + +```sql +DROP TABLE prod.db.sample PURGE +``` + +## `ALTER TABLE` + +Iceberg has full `ALTER TABLE` support in Spark 3, including: + +* Renaming a table +* Setting or removing table properties +* Adding, deleting, and renaming columns +* Adding, deleting, and renaming nested fields +* Reordering top-level columns and nested struct fields +* Widening the type of `int`, `float`, and `decimal` fields +* Making required columns optional + +In addition, [SQL extensions](spark-configuration.md#sql-extensions) can be used to add support for partition evolution and setting a table's write order + +### `ALTER TABLE ... RENAME TO` + +```sql +ALTER TABLE prod.db.sample RENAME TO prod.db.new_name +``` + +### `ALTER TABLE ... SET TBLPROPERTIES` + +```sql +ALTER TABLE prod.db.sample SET TBLPROPERTIES ( + 'read.split.target-size'='268435456' +) +``` + +Iceberg uses table properties to control table behavior. For a list of available properties, see [Table configuration](configuration.md). + +`UNSET` is used to remove properties: + +```sql +ALTER TABLE prod.db.sample UNSET TBLPROPERTIES ('read.split.target-size') +``` + +`SET TBLPROPERTIES` can also be used to set the table comment (description): + +```sql +ALTER TABLE prod.db.sample SET TBLPROPERTIES ( + 'comment' = 'A table comment.' +) +``` + +### `ALTER TABLE ... ADD COLUMN` + +To add a column to Iceberg, use the `ADD COLUMNS` clause with `ALTER TABLE`: + +```sql +ALTER TABLE prod.db.sample +ADD COLUMNS ( + new_column string comment 'new_column docs' + ) +``` + +Multiple columns can be added at the same time, separated by commas. + +Nested columns should be identified using the full column name: + +```sql +-- create a struct column +ALTER TABLE prod.db.sample +ADD COLUMN point struct; + +-- add a field to the struct +ALTER TABLE prod.db.sample +ADD COLUMN point.z double +``` + +```sql +-- create a nested array column of struct +ALTER TABLE prod.db.sample +ADD COLUMN points array>; + +-- add a field to the struct within an array. Using keyword 'element' to access the array's element column. +ALTER TABLE prod.db.sample +ADD COLUMN points.element.z double +``` + +```sql +-- create a map column of struct key and struct value +ALTER TABLE prod.db.sample +ADD COLUMN points map, struct>; + +-- add a field to the value struct in a map. Using keyword 'value' to access the map's value column. +ALTER TABLE prod.db.sample +ADD COLUMN points.value.b int +``` + +Note: Altering a map 'key' column by adding columns is not allowed. Only map values can be updated. + +Add columns in any position by adding `FIRST` or `AFTER` clauses: + +```sql +ALTER TABLE prod.db.sample +ADD COLUMN new_column bigint AFTER other_column +``` + +```sql +ALTER TABLE prod.db.sample +ADD COLUMN nested.new_column bigint FIRST +``` + +### `ALTER TABLE ... RENAME COLUMN` + +Iceberg allows any field to be renamed. To rename a field, use `RENAME COLUMN`: + +```sql +ALTER TABLE prod.db.sample RENAME COLUMN data TO payload +ALTER TABLE prod.db.sample RENAME COLUMN location.lat TO latitude +``` + +Note that nested rename commands only rename the leaf field. The above command renames `location.lat` to `location.latitude` + +### `ALTER TABLE ... ALTER COLUMN` + +Alter column is used to widen types, make a field optional, set comments, and reorder fields. + +Iceberg allows updating column types if the update is safe. Safe updates are: + +* `int` to `bigint` +* `float` to `double` +* `decimal(P,S)` to `decimal(P2,S)` when P2 > P (scale cannot change) + +```sql +ALTER TABLE prod.db.sample ALTER COLUMN measurement TYPE double +``` + +To add or remove columns from a struct, use `ADD COLUMN` or `DROP COLUMN` with a nested column name. + +Column comments can also be updated using `ALTER COLUMN`: + +```sql +ALTER TABLE prod.db.sample ALTER COLUMN measurement TYPE double COMMENT 'unit is bytes per second' +ALTER TABLE prod.db.sample ALTER COLUMN measurement COMMENT 'unit is kilobytes per second' +``` + +Iceberg allows reordering top-level columns or columns in a struct using `FIRST` and `AFTER` clauses: + +```sql +ALTER TABLE prod.db.sample ALTER COLUMN col FIRST +``` +```sql +ALTER TABLE prod.db.sample ALTER COLUMN nested.col AFTER other_col +``` + +Nullability can be changed using `SET NOT NULL` and `DROP NOT NULL`: + +```sql +ALTER TABLE prod.db.sample ALTER COLUMN id DROP NOT NULL +``` + +!!! info + `ALTER COLUMN` is not used to update `struct` types. Use `ADD COLUMN` and `DROP COLUMN` to add or remove struct fields. + + + +### `ALTER TABLE ... DROP COLUMN` + +To drop columns, use `ALTER TABLE ... DROP COLUMN`: + +```sql +ALTER TABLE prod.db.sample DROP COLUMN id +ALTER TABLE prod.db.sample DROP COLUMN point.z +``` + +## `ALTER TABLE` SQL extensions + +These commands are available in Spark 3 when using Iceberg [SQL extensions](spark-configuration.md#sql-extensions). + +### `ALTER TABLE ... ADD PARTITION FIELD` + +Iceberg supports adding new partition fields to a spec using `ADD PARTITION FIELD`: + +```sql +ALTER TABLE prod.db.sample ADD PARTITION FIELD catalog -- identity transform +``` + +[Partition transforms](#partitioned-by) are also supported: + +```sql +ALTER TABLE prod.db.sample ADD PARTITION FIELD bucket(16, id) +ALTER TABLE prod.db.sample ADD PARTITION FIELD truncate(4, data) +ALTER TABLE prod.db.sample ADD PARTITION FIELD years(ts) +-- use optional AS keyword to specify a custom name for the partition field +ALTER TABLE prod.db.sample ADD PARTITION FIELD bucket(16, id) AS shard +``` + +Adding a partition field is a metadata operation and does not change any of the existing table data. New data will be written with the new partitioning, but existing data will remain in the old partition layout. Old data files will have null values for the new partition fields in metadata tables. + +Dynamic partition overwrite behavior will change when the table's partitioning changes because dynamic overwrite replaces partitions implicitly. To overwrite explicitly, use the new `DataFrameWriterV2` API. + +!!! note + To migrate from daily to hourly partitioning with transforms, it is not necessary to drop the daily partition field. Keeping the field ensures existing metadata table queries continue to work. + + +!!! danger + **Dynamic partition overwrite behavior will change** when partitioning changes + For example, if you partition by days and move to partitioning by hours, overwrites will overwrite hourly partitions but not days anymore. + + +### `ALTER TABLE ... DROP PARTITION FIELD` + +Partition fields can be removed using `DROP PARTITION FIELD`: + +```sql +ALTER TABLE prod.db.sample DROP PARTITION FIELD catalog +ALTER TABLE prod.db.sample DROP PARTITION FIELD bucket(16, id) +ALTER TABLE prod.db.sample DROP PARTITION FIELD truncate(4, data) +ALTER TABLE prod.db.sample DROP PARTITION FIELD years(ts) +ALTER TABLE prod.db.sample DROP PARTITION FIELD shard +``` + +Note that although the partition is removed, the column will still exist in the table schema. + +Dropping a partition field is a metadata operation and does not change any of the existing table data. New data will be written with the new partitioning, but existing data will remain in the old partition layout. + +!!! danger + **Dynamic partition overwrite behavior will change** when partitioning changes + For example, if you partition by days and move to partitioning by hours, overwrites will overwrite hourly partitions but not days anymore. + + +!!! danger + Be careful when dropping a partition field because it will change the schema of metadata tables, like `files`, and may cause metadata queries to fail or produce different results. + + +### `ALTER TABLE ... REPLACE PARTITION FIELD` + +A partition field can be replaced by a new partition field in a single metadata update by using `REPLACE PARTITION FIELD`: + +```sql +ALTER TABLE prod.db.sample REPLACE PARTITION FIELD ts_day WITH days(ts) +-- use optional AS keyword to specify a custom name for the new partition field +ALTER TABLE prod.db.sample REPLACE PARTITION FIELD ts_day WITH days(ts) AS day_of_ts +``` + +### `ALTER TABLE ... WRITE ORDERED BY` + +Iceberg tables can be configured with a sort order that is used to automatically sort data that is written to the table in some engines. For example, `MERGE INTO` in Spark will use the table ordering. + +To set the write order for a table, use `WRITE ORDERED BY`: + +```sql +ALTER TABLE prod.db.sample WRITE ORDERED BY category, id +-- use optional ASC/DEC keyword to specify sort order of each field (default ASC) +ALTER TABLE prod.db.sample WRITE ORDERED BY category ASC, id DESC +-- use optional NULLS FIRST/NULLS LAST keyword to specify null order of each field (default FIRST) +ALTER TABLE prod.db.sample WRITE ORDERED BY category ASC NULLS LAST, id DESC NULLS FIRST +``` + +!!! info + Table write order does not guarantee data order for queries. It only affects how data is written to the table. + + +`WRITE ORDERED BY` sets a global ordering where rows are ordered across tasks, like using `ORDER BY` in an `INSERT` command: + +```sql +INSERT INTO prod.db.sample +SELECT id, data, category, ts FROM another_table +ORDER BY ts, category +``` + +To order within each task, not across tasks, use `LOCALLY ORDERED BY`: + +```sql +ALTER TABLE prod.db.sample WRITE LOCALLY ORDERED BY category, id +``` + +### `ALTER TABLE ... WRITE DISTRIBUTED BY PARTITION` + +`WRITE DISTRIBUTED BY PARTITION` will request that each partition is handled by one writer, the default implementation is hash distribution. + +```sql +ALTER TABLE prod.db.sample WRITE DISTRIBUTED BY PARTITION +``` + +`DISTRIBUTED BY PARTITION` and `LOCALLY ORDERED BY` may be used together, to distribute by partition and locally order rows within each task. + +```sql +ALTER TABLE prod.db.sample WRITE DISTRIBUTED BY PARTITION LOCALLY ORDERED BY category, id +``` + +### `ALTER TABLE ... SET IDENTIFIER FIELDS` + +Iceberg supports setting identifier fields to a spec using `SET IDENTIFIER FIELDS`: + +```sql +ALTER TABLE prod.db.sample SET IDENTIFIER FIELDS id +-- single column +ALTER TABLE prod.db.sample SET IDENTIFIER FIELDS id, data +-- multiple columns +``` + +identifier fields must be `NOT NULL`, The later `ALTER` statement will overwrite the previous setting. + +### `ALTER TABLE ... DROP IDENTIFIER FIELDS` + +Identifier fields can be removed using `DROP IDENTIFIER FIELDS`: + +```sql +ALTER TABLE prod.db.sample DROP IDENTIFIER FIELDS id +-- single column +ALTER TABLE prod.db.sample DROP IDENTIFIER FIELDS id, data +-- multiple columns +``` + +Note that although the identifier is removed, the column will still exist in the table schema. + +### Branching and Tagging DDL + +#### `ALTER TABLE ... CREATE BRANCH` + +Branches can be created via the `CREATE BRANCH` statement, which includes +the snapshot to create the branch at and an optional retention clause. + +```sql +-- CREATE audit-branch at snapshot 1234 with default retention. +ALTER TABLE prod.db.sample CREATE BRANCH audit-branch +AS OF VERSION 1234 + +-- CREATE audit-branch at snapshot 1234, retain audit-branch for 31 days, and retain the latest 31 days. The latest 3 snapshot snapshots, and 2 days worth of snapshots +ALTER TABLE prod.db.sample CREATE BRANCH audit-branch +AS OF VERSION 1234 RETAIN 30 DAYS +WITH RETENTION 3 SNAPSHOTS 2 DAYS +``` + + +#### `ALTER TABLE ... CREATE TAG` + +Tags can be created via the `CREATE TAG` statement, which includes +the snapshot to create the branch at and an optional retention clause. + +```sql +-- CREATE historical-tag at snapshot 1234 with default retention. +ALTER TABLE prod.db.sample CREATE TAG historical-tag AS OF VERSION 1234 + +-- CREATE historical-tag at snapshot 1234 and retain it for 1 year. +ALTER TABLE prod.db.sample CREATE TAG historical-tag +AS OF VERSION 1234 RETAIN 365 DAYS +``` + +### `ALTER TABLE ... REPLACE BRANCH` + +The snapshot which a branch references can be updated via +the `REPLACE BRANCH` sql. Retention can also be updated in this statement. + +```sql +-- REPLACE audit-branch to reference snapshot 4567 and update the retention to 60 days +ALTER TABLE prod.db.sample REPLACE BRANCH audit-branch +AS OF VERSION 4567 RETAIN 60 DAYS +``` + +#### `ALTER TABLE ... DROP BRANCH` + +Branches can be removed via the `DROP BRANCH` sql + +```sql +ALTER TABLE prod.db.sample DROP BRANCH audit-branch +``` + +#### `ALTER TABLE ... DROP TAG` + +Tags can be removed via the `DROP TAG` sql + +```sql +ALTER TABLE prod.db.sample DROP TAG historical-tag +``` diff --git a/site/docs/docs/nightly/docs/spark-getting-started.md b/site/docs/docs/nightly/docs/spark-getting-started.md new file mode 100644 index 000000000000..0c2c63e0760e --- /dev/null +++ b/site/docs/docs/nightly/docs/spark-getting-started.md @@ -0,0 +1,138 @@ +--- +title: "Getting Started" +--- + + +# Getting Started + +The latest version of Iceberg is [{{ icebergVersion }}](../../releases.md). + +Spark is currently the most feature-rich compute engine for Iceberg operations. +We recommend you to get started with Spark to understand Iceberg concepts and features with examples. +You can also view documentations of using Iceberg with other compute engine under the [Multi-Engine Support](../../multi-engine-support.md) page. + +## Using Iceberg in Spark 3 + +To use Iceberg in a Spark shell, use the `--packages` option: + +```sh +spark-shell --packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }} +``` + +!!! info + If you want to include Iceberg in your Spark installation, add the [`iceberg-spark-runtime-3.2_2.12` Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.2_2.12-{{ icebergVersion }}.jar) to Spark's `jars` folder. + + +### Adding catalogs + +Iceberg comes with [catalogs](spark-configuration.md#catalogs) that enable SQL commands to manage tables and load them by name. Catalogs are configured using properties under `spark.sql.catalog.(catalog_name)`. + +This command creates a path-based catalog named `local` for tables under `$PWD/warehouse` and adds support for Iceberg tables to Spark's built-in catalog: + +```sh +spark-sql --packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }}\ + --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \ + --conf spark.sql.catalog.spark_catalog=org.apache.iceberg.spark.SparkSessionCatalog \ + --conf spark.sql.catalog.spark_catalog.type=hive \ + --conf spark.sql.catalog.local=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.local.type=hadoop \ + --conf spark.sql.catalog.local.warehouse=$PWD/warehouse +``` + +### Creating a table + +To create your first Iceberg table in Spark, use the `spark-sql` shell or `spark.sql(...)` to run a [`CREATE TABLE`](spark-ddl.md#create-table) command: + +```sql +-- local is the path-based catalog defined above +CREATE TABLE local.db.table (id bigint, data string) USING iceberg +``` + +Iceberg catalogs support the full range of SQL DDL commands, including: + +* [`CREATE TABLE ... PARTITIONED BY`](spark-ddl.md#create-table) +* [`CREATE TABLE ... AS SELECT`](spark-ddl.md#create-table-as-select) +* [`ALTER TABLE`](spark-ddl.md#alter-table) +* [`DROP TABLE`](spark-ddl.md#drop-table) + +### Writing + +Once your table is created, insert data using [`INSERT INTO`](spark-writes.md#insert-into): + +```sql +INSERT INTO local.db.table VALUES (1, 'a'), (2, 'b'), (3, 'c'); +INSERT INTO local.db.table SELECT id, data FROM source WHERE length(data) = 1; +``` + +Iceberg also adds row-level SQL updates to Spark, [`MERGE INTO`](spark-writes.md#merge-into) and [`DELETE FROM`](spark-writes.md#delete-from): + +```sql +MERGE INTO local.db.target t USING (SELECT * FROM updates) u ON t.id = u.id +WHEN MATCHED THEN UPDATE SET t.count = t.count + u.count +WHEN NOT MATCHED THEN INSERT * +``` + +Iceberg supports writing DataFrames using the new [v2 DataFrame write API](spark-writes.md#writing-with-dataframes): + +```scala +spark.table("source").select("id", "data") + .writeTo("local.db.table").append() +``` + +The old `write` API is supported, but _not_ recommended. + +### Reading + +To read with SQL, use the an Iceberg table name in a `SELECT` query: + +```sql +SELECT count(1) as count, data +FROM local.db.table +GROUP BY data +``` + +SQL is also the recommended way to [inspect tables](spark-queries.md#inspecting-tables). To view all of the snapshots in a table, use the `snapshots` metadata table: +```sql +SELECT * FROM local.db.table.snapshots +``` +``` ++-------------------------+----------------+-----------+-----------+----------------------------------------------------+-----+ +| committed_at | snapshot_id | parent_id | operation | manifest_list | ... | ++-------------------------+----------------+-----------+-----------+----------------------------------------------------+-----+ +| 2019-02-08 03:29:51.215 | 57897183625154 | null | append | s3://.../table/metadata/snap-57897183625154-1.avro | ... | +| | | | | | ... | +| | | | | | ... | +| ... | ... | ... | ... | ... | ... | ++-------------------------+----------------+-----------+-----------+----------------------------------------------------+-----+ +``` + +[DataFrame reads](spark-queries.md#querying-with-dataframes) are supported and can now reference tables by name using `spark.table`: + +```scala +val df = spark.table("local.db.table") +df.count() +``` + +### Next steps + +Next, you can learn more about Iceberg tables in Spark: + +* [DDL commands](spark-ddl.md): `CREATE`, `ALTER`, and `DROP` +* [Querying data](spark-queries.md): `SELECT` queries and metadata tables +* [Writing data](spark-writes.md): `INSERT INTO` and `MERGE INTO` +* [Maintaining tables](spark-procedures.md) with stored procedures diff --git a/site/docs/docs/nightly/docs/spark-procedures.md b/site/docs/docs/nightly/docs/spark-procedures.md new file mode 100644 index 000000000000..919b6de9451b --- /dev/null +++ b/site/docs/docs/nightly/docs/spark-procedures.md @@ -0,0 +1,698 @@ +--- +title: "Procedures" +--- + + +# Spark Procedures + +To use Iceberg in Spark, first configure [Spark catalogs](spark-configuration.md). Stored procedures are only available when using [Iceberg SQL extensions](spark-configuration.md#sql-extensions) in Spark 3. + +## Usage + +Procedures can be used from any configured Iceberg catalog with `CALL`. All procedures are in the namespace `system`. + +`CALL` supports passing arguments by name (recommended) or by position. Mixing position and named arguments is not supported. + +### Named arguments + +All procedure arguments are named. When passing arguments by name, arguments can be in any order and any optional argument can be omitted. + +```sql +CALL catalog_name.system.procedure_name(arg_name_2 => arg_2, arg_name_1 => arg_1) +``` + +### Positional arguments + +When passing arguments by position, only the ending arguments may be omitted if they are optional. + +```sql +CALL catalog_name.system.procedure_name(arg_1, arg_2, ... arg_n) +``` + +## Snapshot management + +### `rollback_to_snapshot` + +Roll back a table to a specific snapshot ID. + +To roll back to a specific time, use [`rollback_to_timestamp`](#rollback_to_timestamp). + +!!! info + This procedure invalidates all cached Spark plans that reference the affected table. + + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to update | +| `snapshot_id` | ✔️ | long | Snapshot ID to rollback to | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `previous_snapshot_id` | long | The current snapshot ID before the rollback | +| `current_snapshot_id` | long | The new current snapshot ID | + +#### Example + +Roll back table `db.sample` to snapshot ID `1`: + +```sql +CALL catalog_name.system.rollback_to_snapshot('db.sample', 1) +``` + +### `rollback_to_timestamp` + +Roll back a table to the snapshot that was current at some time. + +!!! info + This procedure invalidates all cached Spark plans that reference the affected table. + + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to update | +| `timestamp` | ✔️ | timestamp | A timestamp to rollback to | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `previous_snapshot_id` | long | The current snapshot ID before the rollback | +| `current_snapshot_id` | long | The new current snapshot ID | + +#### Example + +Roll back `db.sample` to a specific day and time. +```sql +CALL catalog_name.system.rollback_to_timestamp('db.sample', TIMESTAMP '2021-06-30 00:00:00.000') +``` + +### `set_current_snapshot` + +Sets the current snapshot ID for a table. + +Unlike rollback, the snapshot is not required to be an ancestor of the current table state. + +!!! info + This procedure invalidates all cached Spark plans that reference the affected table. + + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to update | +| `snapshot_id` | ✔️ | long | Snapshot ID to set as current | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `previous_snapshot_id` | long | The current snapshot ID before the rollback | +| `current_snapshot_id` | long | The new current snapshot ID | + +#### Example + +Set the current snapshot for `db.sample` to 1: +```sql +CALL catalog_name.system.set_current_snapshot('db.sample', 1) +``` + +### `cherrypick_snapshot` + +Cherry-picks changes from a snapshot into the current table state. + +Cherry-picking creates a new snapshot from an existing snapshot without altering or removing the original. + +Only append and dynamic overwrite snapshots can be cherry-picked. + +!!! info + This procedure invalidates all cached Spark plans that reference the affected table. + + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to update | +| `snapshot_id` | ✔️ | long | The snapshot ID to cherry-pick | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `source_snapshot_id` | long | The table's current snapshot before the cherry-pick | +| `current_snapshot_id` | long | The snapshot ID created by applying the cherry-pick | + +#### Examples + +Cherry-pick snapshot 1 +```sql +CALL catalog_name.system.cherrypick_snapshot('my_table', 1) +``` + +Cherry-pick snapshot 1 with named args +```sql +CALL catalog_name.system.cherrypick_snapshot(snapshot_id => 1, table => 'my_table' ) +``` + + +## Metadata management + +Many [maintenance actions](maintenance.md) can be performed using Iceberg stored procedures. + +### `expire_snapshots` + +Each write/update/delete/upsert/compaction in Iceberg produces a new snapshot while keeping the old data and metadata +around for snapshot isolation and time travel. The `expire_snapshots` procedure can be used to remove older snapshots +and their files which are no longer needed. + +This procedure will remove old snapshots and data files which are uniquely required by those old snapshots. This means +the `expire_snapshots` procedure will never remove files which are still required by a non-expired snapshot. + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to update | +| `older_than` | ️ | timestamp | Timestamp before which snapshots will be removed (Default: 5 days ago) | +| `retain_last` | | int | Number of ancestor snapshots to preserve regardless of `older_than` (defaults to 1) | +| `max_concurrent_deletes` | | int | Size of the thread pool used for delete file actions (by default, no thread pool is used) | +| `stream_results` | | boolean | When true, deletion files will be sent to Spark driver by RDD partition (by default, all the files will be sent to Spark driver). This option is recommended to set to `true` to prevent Spark driver OOM from large file size | +| `snapshot_ids` | | array of long | Array of snapshot IDs to expire. | + +If `older_than` and `retain_last` are omitted, the table's [expiration properties](configuration.md#table-behavior-properties) will be used. + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `deleted_data_files_count` | long | Number of data files deleted by this operation | +| `deleted_position_delete_files_count` | long | Number of position delete files deleted by this operation | +| `deleted_equality_delete_files_count` | long | Number of equality delete files deleted by this operation | +| `deleted_manifest_files_count` | long | Number of manifest files deleted by this operation | +| `deleted_manifest_lists_count` | long | Number of manifest List files deleted by this operation | + +#### Examples + +Remove snapshots older than specific day and time, but retain the last 100 snapshots: + +```sql +CALL hive_prod.system.expire_snapshots('db.sample', TIMESTAMP '2021-06-30 00:00:00.000', 100) +``` + +Remove snapshots with snapshot ID `123` (note that this snapshot ID should not be the current snapshot): + +```sql +CALL hive_prod.system.expire_snapshots(table => 'db.sample', snapshot_ids => ARRAY(123)) +``` + +### `remove_orphan_files` + +Used to remove files which are not referenced in any metadata files of an Iceberg table and can thus be considered "orphaned". + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to clean | +| `older_than` | ️ | timestamp | Remove orphan files created before this timestamp (Defaults to 3 days ago) | +| `location` | | string | Directory to look for files in (defaults to the table's location) | +| `dry_run` | | boolean | When true, don't actually remove files (defaults to false) | +| `max_concurrent_deletes` | | int | Size of the thread pool used for delete file actions (by default, no thread pool is used) | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `orphan_file_location` | String | The path to each file determined to be an orphan by this command | + +#### Examples + +List all the files that are candidates for removal by performing a dry run of the `remove_orphan_files` command on this table without actually removing them: +```sql +CALL catalog_name.system.remove_orphan_files(table => 'db.sample', dry_run => true) +``` + +Remove any files in the `tablelocation/data` folder which are not known to the table `db.sample`. +```sql +CALL catalog_name.system.remove_orphan_files(table => 'db.sample', location => 'tablelocation/data') +``` + +### `rewrite_data_files` + +Iceberg tracks each data file in a table. More data files leads to more metadata stored in manifest files, and small data files causes an unnecessary amount of metadata and less efficient queries from file open costs. + +Iceberg can compact data files in parallel using Spark with the `rewriteDataFiles` action. This will combine small files into larger files to reduce metadata overhead and runtime file open cost. + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to update | +| `strategy` | | string | Name of the strategy - binpack or sort. Defaults to binpack strategy | +| `sort_order` | | string | For Zorder use a comma separated list of columns within zorder(). (Supported in Spark 3.2 and Above) Example: zorder(c1,c2,c3).
Else, Comma separated sort orders in the format (ColumnName SortDirection NullOrder).
Where SortDirection can be ASC or DESC. NullOrder can be NULLS FIRST or NULLS LAST.
Defaults to the table's sort order | +| `options` | ️ | map | Options to be used for actions| +| `where` | ️ | string | predicate as a string used for filtering the files. Note that all files that may contain data matching the filter will be selected for rewriting| + + +See the [`RewriteDataFiles` Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/actions/RewriteDataFiles.html#field.summary), +
[`BinPackStrategy` Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/actions/BinPackStrategy.html#field.summary) +and
[`SortStrategy` Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/actions/SortStrategy.html#field.summary) +for list of all the supported options for this action. + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `rewritten_data_files_count` | int | Number of data which were re-written by this command | +| `added_data_files_count` | int | Number of new data files which were written by this command | + +#### Examples + +Rewrite the data files in table `db.sample` using the default rewrite algorithm of bin-packing to combine small files +and also split large files according to the default write size of the table. +```sql +CALL catalog_name.system.rewrite_data_files('db.sample') +``` + +Rewrite the data files in table `db.sample` by sorting all the data on id and name +using the same defaults as bin-pack to determine which files to rewrite. +```sql +CALL catalog_name.system.rewrite_data_files(table => 'db.sample', strategy => 'sort', sort_order => 'id DESC NULLS LAST,name ASC NULLS FIRST') +``` + +Rewrite the data files in table `db.sample` by zOrdering on column c1 and c2. +Using the same defaults as bin-pack to determine which files to rewrite. +```sql +CALL catalog_name.system.rewrite_data_files(table => 'db.sample', strategy => 'sort', sort_order => 'zorder(c1,c2)') +``` + +Rewrite the data files in table `db.sample` using bin-pack strategy in any partition where more than 2 or more files need to be rewritten. +```sql +CALL catalog_name.system.rewrite_data_files(table => 'db.sample', options => map('min-input-files','2')) +``` + +Rewrite the data files in table `db.sample` and select the files that may contain data matching the filter (id = 3 and name = "foo") to be rewritten. +```sql +CALL catalog_name.system.rewrite_data_files(table => 'db.sample', where => 'id = 3 and name = "foo"') +``` + +### `rewrite_manifests` + +Rewrite manifests for a table to optimize scan planning. + +Data files in manifests are sorted by fields in the partition spec. This procedure runs in parallel using a Spark job. + +See the [`RewriteManifests` Javadoc](../../javadoc/{{ icebergVersion }}/org/apache/iceberg/actions/RewriteManifests.html) +to see more configuration options. + +!!! info + This procedure invalidates all cached Spark plans that reference the affected table. + + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to update | +| `use_caching` | ️ | boolean | Use Spark caching during operation (defaults to true) | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `rewritten_manifests_count` | int | Number of manifests which were re-written by this command | +| `added_mainfests_count` | int | Number of new manifest files which were written by this command | + +#### Examples + +Rewrite the manifests in table `db.sample` and align manifest files with table partitioning. +```sql +CALL catalog_name.system.rewrite_manifests('db.sample') +``` + +Rewrite the manifests in table `db.sample` and disable the use of Spark caching. This could be done to avoid memory issues on executors. +```sql +CALL catalog_name.system.rewrite_manifests('db.sample', false) +``` + +## Table migration + +The `snapshot` and `migrate` procedures help test and migrate existing Hive or Spark tables to Iceberg. + +### `snapshot` + +Create a light-weight temporary copy of a table for testing, without changing the source table. + +The newly created table can be changed or written to without affecting the source table, but the snapshot uses the original table's data files. + +When inserts or overwrites run on the snapshot, new files are placed in the snapshot table's location rather than the original table location. + +When finished testing a snapshot table, clean it up by running `DROP TABLE`. + +!!! info + Because tables created by `snapshot` are not the sole owners of their data files, they are prohibited from + actions like `expire_snapshots` which would physically delete data files. Iceberg deletes, which only effect metadata, + are still allowed. In addition, any operations which affect the original data files will disrupt the Snapshot's + integrity. DELETE statements executed against the original Hive table will remove original data files and the + `snapshot` table will no longer be able to access them. + + +See [`migrate`](#migrate) to replace an existing table with an Iceberg table. + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `source_table`| ✔️ | string | Name of the table to snapshot | +| `table` | ✔️ | string | Name of the new Iceberg table to create | +| `location` | | string | Table location for the new table (delegated to the catalog by default) | +| `properties` | ️ | map | Properties to add to the newly created table | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `imported_files_count` | long | Number of files added to the new table | + +#### Examples + +Make an isolated Iceberg table which references table `db.sample` named `db.snap` at the +catalog's default location for `db.snap`. +```sql +CALL catalog_name.system.snapshot('db.sample', 'db.snap') +``` + +Migrate an isolated Iceberg table which references table `db.sample` named `db.snap` at +a manually specified location `/tmp/temptable/`. +```sql +CALL catalog_name.system.snapshot('db.sample', 'db.snap', '/tmp/temptable/') +``` + +### `migrate` + +Replace a table with an Iceberg table, loaded with the source's data files. + +Table schema, partitioning, properties, and location will be copied from the source table. + +Migrate will fail if any table partition uses an unsupported format. Supported formats are Avro, Parquet, and ORC. +Existing data files are added to the Iceberg table's metadata and can be read using a name-to-id mapping created from the original table schema. + +To leave the original table intact while testing, use [`snapshot`](#snapshot) to create new temporary table that shares source data files and schema. + +By default, the original table is retained with the name `table_BACKUP_`. + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to migrate | +| `properties` | ️ | map | Properties for the new Iceberg table | +| `drop_backup` | | boolean | When true, the original table will not be retained as backup (defaults to false) | + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `migrated_files_count` | long | Number of files appended to the Iceberg table | + +#### Examples + +Migrate the table `db.sample` in Spark's default catalog to an Iceberg table and add a property 'foo' set to 'bar': + +```sql +CALL catalog_name.system.migrate('spark_catalog.db.sample', map('foo', 'bar')) +``` + +Migrate `db.sample` in the current catalog to an Iceberg table without adding any additional properties: +```sql +CALL catalog_name.system.migrate('db.sample') +``` + +### `add_files` + +Attempts to directly add files from a Hive or file based table into a given Iceberg table. Unlike migrate or +snapshot, `add_files` can import files from a specific partition or partitions and does not create a new Iceberg table. +This command will create metadata for the new files and will not move them. This procedure will not analyze the schema +of the files to determine if they actually match the schema of the Iceberg table. Upon completion, the Iceberg table +will then treat these files as if they are part of the set of files owned by Iceberg. This means any subsequent +`expire_snapshot` calls will be able to physically delete the added files. This method should not be used if +`migrate` or `snapshot` are possible. + +#### Usage + +| Argument Name | Required? | Type | Description | +|-------------------------|-----------|---------------------|-----------------------------------------------------------------------------------------------------| +| `table` | ✔️ | string | Table which will have files added to | +| `source_table` | ✔️ | string | Table where files should come from, paths are also possible in the form of \`file_format\`.\`path\` | +| `partition_filter` | ️ | map | A map of partitions in the source table to import from | +| `check_duplicate_files` | ️ | boolean | Whether to prevent files existing in the table from being added (defaults to true) | + +Warning : Schema is not validated, adding files with different schema to the Iceberg table will cause issues. + +Warning : Files added by this method can be physically deleted by Iceberg operations + +#### Output + +| Output Name | Type | Description | +|---------------------------|------|---------------------------------------------------| +| `added_files_count` | long | The number of files added by this command | +| `changed_partition_count` | long | The number of partitioned changed by this command | + +!!! warning + changed_partition_count will be 0 when table property `compatibility.snapshot-id-inheritance.enabled` is set to true + +#### Examples + +Add the files from table `db.src_table`, a Hive or Spark table registered in the session Catalog, to Iceberg table +`db.tbl`. Only add files that exist within partitions where `part_col_1` is equal to `A`. +```sql +CALL spark_catalog.system.add_files( +table => 'db.tbl', +source_table => 'db.src_tbl', +partition_filter => map('part_col_1', 'A') +) +``` + +Add files from a `parquet` file based table at location `path/to/table` to the Iceberg table `db.tbl`. Add all +files regardless of what partition they belong to. +```sql +CALL spark_catalog.system.add_files( + table => 'db.tbl', + source_table => '`parquet`.`path/to/table`' +) +``` + +### `register_table` + +Creates a catalog entry for a metadata.json file which already exists but does not have a corresponding catalog identifier. + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Table which is to be registered | +| `metadata_file`| ✔️ | string | Metadata file which is to be registered as a new catalog identifier | + +!!! warning + Having the same metadata.json registered in more than one catalog can lead to missing updates, loss of data, and table corruption. + Only use this procedure when the table is no longer registered in an existing catalog, or you are moving a table between catalogs. + + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `current_snapshot_id` | long | The current snapshot ID of the newly registered Iceberg table | +| `total_records_count` | long | Total records count of the newly registered Iceberg table | +| `total_data_files_count` | long | Total data files count of the newly registered Iceberg table | + +#### Examples + +Register a new table as `db.tbl` to `spark_catalog` pointing to metadata.json file `path/to/metadata/file.json`. +```sql +CALL spark_catalog.system.register_table( + table => 'db.tbl', + metadata_file => 'path/to/metadata/file.json' +) +``` + +## Metadata information + +### `ancestors_of` + +Report the live snapshot IDs of parents of a specified snapshot + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|-----------|------|-------------| +| `table` | ✔️ | string | Name of the table to report live snapshot IDs | +| `snapshot_id` | ️ | long | Use a specified snapshot to get the live snapshot IDs of parents | + +> tip : Using snapshot_id +> +> Given snapshots history with roll back to B and addition of C' -> D' +> ```shell +> A -> B - > C -> D +> \ -> C' -> (D') +> ``` +> Not specifying the snapshot ID would return A -> B -> C' -> D', while providing the snapshot ID of +> D as an argument would return A-> B -> C -> D + +#### Output + +| Output Name | Type | Description | +| ------------|------|-------------| +| `snapshot_id` | long | the ancestor snapshot id | +| `timestamp` | long | snapshot creation time | + +#### Examples + +Get all the snapshot ancestors of current snapshots(default) +```sql +CALL spark_catalog.system.ancestors_of('db.tbl') +``` + +Get all the snapshot ancestors by a particular snapshot +```sql +CALL spark_catalog.system.ancestors_of('db.tbl', 1) +CALL spark_catalog.system.ancestors_of(snapshot_id => 1, table => 'db.tbl') +``` + +## Change Data Capture + +### `create_changelog_view` + +Creates a view that contains the changes from a given table. + +#### Usage + +| Argument Name | Required? | Type | Description | +|---------------|----------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `table` | ✔️ | string | Name of the source table for the changelog | +| `changelog_view` | | string | Name of the view to create | +| `options` | | map | A map of Spark read options to use | +|`compute_updates`| | boolean | Whether to compute pre/post update images (see below for more information). Defaults to false. | +|`identifier_columns`| | array | The list of identifier columns to compute updates. If the argument `compute_updates` is set to true and `identifier_columns` are not provided, the table’s current identifier fields will be used to compute updates. | +|`remove_carryovers`| | boolean | Whether to remove carry-over rows (see below for more information). Defaults to true. | + +Here is a list of commonly used Spark read options: +* `start-snapshot-id`: the exclusive start snapshot ID. If not provided, it reads from the table’s first snapshot inclusively. +* `end-snapshot-id`: the inclusive end snapshot id, default to table's current snapshot. +* `start-timestamp`: the exclusive start timestamp. If not provided, it reads from the table’s first snapshot inclusively. +* `end-timestamp`: the inclusive end timestamp, default to table's current snapshot. + +#### Output +| Output Name | Type | Description | +| ------------|------|----------------------------------------| +| `changelog_view` | string | The name of the created changelog view | + +#### Examples + +Create a changelog view `tbl_changes` based on the changes that happened between snapshot `1` (exclusive) and `2` (inclusive). +```sql +CALL spark_catalog.system.create_changelog_view( + table => 'db.tbl', + options => map('start-snapshot-id','1','end-snapshot-id', '2') +) +``` + +Create a changelog view `my_changelog_view` based on the changes that happened between timestamp `1678335750489` (exclusive) and `1678992105265` (inclusive). +```sql +CALL spark_catalog.system.create_changelog_view( + table => 'db.tbl', + options => map('start-timestamp','1678335750489','end-timestamp', '1678992105265'), + changelog_view => 'my_changelog_view' +) +``` + +Create a changelog view that computes updates based on the identifier columns `id` and `name`. +```sql +CALL spark_catalog.system.create_changelog_view( + table => 'db.tbl', + options => map('start-snapshot-id','1','end-snapshot-id', '2'), + identifier_columns => array('id', 'name') +) +``` + +Once the changelog view is created, you can query the view to see the changes that happened between the snapshots. +```sql +SELECT * FROM tbl_changes +``` +```sql +SELECT * FROM tbl_changes where _change_type = 'INSERT' AND id = 3 ORDER BY _change_ordinal +``` +Please note that the changelog view includes Change Data Capture(CDC) metadata columns +that provide additional information about the changes being tracked. These columns are: +- `_change_type`: the type of change. It has one of the following values: `INSERT`, `DELETE`, `UPDATE_BEFORE`, or `UPDATE_AFTER`. +- `_change_ordinal`: the order of changes +- `_commit_snapshot_id`: the snapshot ID where the change occurred + +Here is an example of corresponding results. It shows that the first snapshot inserted 2 records, and the +second snapshot deleted 1 record. + +| id | name |_change_type | _change_ordinal | _change_snapshot_id | +|---|--------|---|---|---| +|1 | Alice |INSERT |0 |5390529835796506035| +|2 | Bob |INSERT |0 |5390529835796506035| +|1 | Alice |DELETE |1 |8764748981452218370| + +#### Carry-over Rows + +The procedure removes the carry-over rows by default. Carry-over rows are the result of row-level operations(`MERGE`, `UPDATE` and `DELETE`) +when using copy-on-write. For example, given a file which contains row1 `(id=1, name='Alice')` and row2 `(id=2, name='Bob')`. +A copy-on-write delete of row2 would require erasing this file and preserving row1 in a new file. The changelog table +reports this as the following pair of rows, despite it not being an actual change to the table. + +| id | name | _change_type | +|-----|-------|--------------| +| 1 | Alice | DELETE | +| 1 | Alice | INSERT | + +By default, this view finds the carry-over rows and removes them from the result. User can disable this +behavior by setting the `remove_carryovers` option to `false`. + +#### Pre/Post Update Images + +The procedure computes the pre/post update images if configured. Pre/post update images are converted from a +pair of a delete row and an insert row. Identifier columns are used for determining whether an insert and a delete record +refer to the same row. If the two records share the same values for the identity columns they are considered to be before +and after states of the same row. You can either set identifier fields in the table schema or input them as the procedure parameters. + +The following example shows pre/post update images computation with an identifier column(`id`), where a row deletion +and an insertion with the same `id` are treated as a single update operation. Specifically, suppose we have the following pair of rows: + +| id | name | _change_type | +|-----|--------|--------------| +| 3 | Robert | DELETE | +| 3 | Dan | INSERT | + +In this case, the procedure marks the row before the update as an `UPDATE_BEFORE` image and the row after the update +as an `UPDATE_AFTER` image, resulting in the following pre/post update images: + +| id | name | _change_type | +|-----|--------|--------------| +| 3 | Robert | UPDATE_BEFORE| +| 3 | Dan | UPDATE_AFTER | diff --git a/site/docs/docs/nightly/docs/spark-queries.md b/site/docs/docs/nightly/docs/spark-queries.md new file mode 100644 index 000000000000..90a3573b6a76 --- /dev/null +++ b/site/docs/docs/nightly/docs/spark-queries.md @@ -0,0 +1,403 @@ +--- +title: "Queries" +--- + + +# Spark Queries + +To use Iceberg in Spark, first configure [Spark catalogs](spark-configuration.md). Iceberg uses Apache Spark's DataSourceV2 API for data source and catalog implementations. + +## Querying with SQL + +In Spark 3, tables use identifiers that include a [catalog name](spark-configuration.md#using-catalogs). + +```sql +SELECT * FROM prod.db.table; -- catalog: prod, namespace: db, table: table +``` + +Metadata tables, like `history` and `snapshots`, can use the Iceberg table name as a namespace. + +For example, to read from the `files` metadata table for `prod.db.table`: + +```sql +SELECT * FROM prod.db.table.files; +``` + +| content | file_path | file_format | spec_id | partition | record_count | file_size_in_bytes | column_sizes | value_counts | null_value_counts | nan_value_counts | lower_bounds | upper_bounds | key_metadata | split_offsets | equality_ids | sort_order_id | +| ------- | ----------------------------------------------------------------------- | ----------- | ------- | ---------------- | ------------ | ------------------ | ------------------ | ---------------- | ----------------- | ---------------- | --------------- | --------------- | ------------ | ------------- | ------------ | ------------- | +| 0 | s3:/.../table/data/00000-3-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 01} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> c] | [1 -> , 2 -> c] | null | [4] | null | null | +| 0 | s3:/.../table/data/00001-4-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 02} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> b] | [1 -> , 2 -> b] | null | [4] | null | null | +| 0 | s3:/.../table/data/00002-5-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 03} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> a] | [1 -> , 2 -> a] | null | [4] | null | null | + +## Querying with DataFrames + +To load a table as a DataFrame, use `table`: + +```scala +val df = spark.table("prod.db.table") +``` + +### Catalogs with DataFrameReader + +Paths and table names can be loaded with Spark's `DataFrameReader` interface. How tables are loaded depends on how +the identifier is specified. When using `spark.read.format("iceberg").load(table)` or `spark.table(table)` the `table` +variable can take a number of forms as listed below: + +* `file:///path/to/table`: loads a HadoopTable at given path +* `tablename`: loads `currentCatalog.currentNamespace.tablename` +* `catalog.tablename`: loads `tablename` from the specified catalog. +* `namespace.tablename`: loads `namespace.tablename` from current catalog +* `catalog.namespace.tablename`: loads `namespace.tablename` from the specified catalog. +* `namespace1.namespace2.tablename`: loads `namespace1.namespace2.tablename` from current catalog + +The above list is in order of priority. For example: a matching catalog will take priority over any namespace resolution. + + +### Time travel + +#### SQL + +Spark 3.3 and later supports time travel in SQL queries using `TIMESTAMP AS OF` or `VERSION AS OF` clauses. +The `VERSION AS OF` clause can contain a long snapshot ID or a string branch or tag name. + +!!! info + Note: If the name of a branch or tag is the same as a snapshot ID, then the snapshot which is selected for time travel is the snapshot + with the given snapshot ID. For example, consider the case where there is a tag named '1' and it references snapshot with ID 2. + If the version travel clause is `VERSION AS OF '1'`, time travel will be done to the snapshot with ID 1. + If this is not desired, rename the tag or branch with a well-defined prefix such as 'snapshot-1'. + + +```sql +-- time travel to October 26, 1986 at 01:21:00 +SELECT * FROM prod.db.table TIMESTAMP AS OF '1986-10-26 01:21:00'; + +-- time travel to snapshot with id 10963874102873L +SELECT * FROM prod.db.table VERSION AS OF 10963874102873; + +-- time travel to the head snapshot of audit-branch +SELECT * FROM prod.db.table VERSION AS OF 'audit-branch'; + +-- time travel to the snapshot referenced by the tag historical-snapshot +SELECT * FROM prod.db.table VERSION AS OF 'historical-snapshot'; +``` + +In addition, `FOR SYSTEM_TIME AS OF` and `FOR SYSTEM_VERSION AS OF` clauses are also supported: + +```sql +SELECT * FROM prod.db.table FOR SYSTEM_TIME AS OF '1986-10-26 01:21:00'; +SELECT * FROM prod.db.table FOR SYSTEM_VERSION AS OF 10963874102873; +SELECT * FROM prod.db.table FOR SYSTEM_VERSION AS OF 'audit-branch'; +SELECT * FROM prod.db.table FOR SYSTEM_VERSION AS OF 'historical-snapshot'; +``` + +Timestamps may also be supplied as a Unix timestamp, in seconds: + +```sql +-- timestamp in seconds +SELECT * FROM prod.db.table TIMESTAMP AS OF 499162860; +SELECT * FROM prod.db.table FOR SYSTEM_TIME AS OF 499162860; +``` + +#### DataFrame + +To select a specific table snapshot or the snapshot at some time in the DataFrame API, Iceberg supports four Spark read options: + +* `snapshot-id` selects a specific table snapshot +* `as-of-timestamp` selects the current snapshot at a timestamp, in milliseconds +* `branch` selects the head snapshot of the specified branch. Note that currently branch cannot be combined with as-of-timestamp. +* `tag` selects the snapshot associated with the specified tag. Tags cannot be combined with `as-of-timestamp`. + +```scala +// time travel to October 26, 1986 at 01:21:00 +spark.read + .option("as-of-timestamp", "499162860000") + .format("iceberg") + .load("path/to/table") +``` + +```scala +// time travel to snapshot with ID 10963874102873L +spark.read + .option("snapshot-id", 10963874102873L) + .format("iceberg") + .load("path/to/table") +``` + +```scala +// time travel to tag historical-snapshot +spark.read + .option(SparkReadOptions.TAG, "historical-snapshot") + .format("iceberg") + .load("path/to/table") +``` + +```scala +// time travel to the head snapshot of audit-branch +spark.read + .option(SparkReadOptions.BRANCH, "audit-branch") + .format("iceberg") + .load("path/to/table") +``` + +!!! info + Spark 3.0 and earlier versions do not support using `option` with `table` in DataFrameReader commands. All options will be silently + ignored. Do not use `table` when attempting to time-travel or use other options. See [SPARK-32592](https://issues.apache.org/jira/browse/SPARK-32592). + + + +### Incremental read + +To read appended data incrementally, use: + +* `start-snapshot-id` Start snapshot ID used in incremental scans (exclusive). +* `end-snapshot-id` End snapshot ID used in incremental scans (inclusive). This is optional. Omitting it will default to the current snapshot. + +```scala +// get the data added after start-snapshot-id (10963874102873L) until end-snapshot-id (63874143573109L) +spark.read() + .format("iceberg") + .option("start-snapshot-id", "10963874102873") + .option("end-snapshot-id", "63874143573109") + .load("path/to/table") +``` + +!!! info + Currently gets only the data from `append` operation. Cannot support `replace`, `overwrite`, `delete` operations. + Incremental read works with both V1 and V2 format-version. + Incremental read is not supported by Spark's SQL syntax. + + +## Inspecting tables + +To inspect a table's history, snapshots, and other metadata, Iceberg supports metadata tables. + +Metadata tables are identified by adding the metadata table name after the original table name. For example, history for `db.table` is read using `db.table.history`. + +!!! info + For Spark 3, prior to 3.2, the Spark [session catalog](spark-configuration.md#replacing-the-session-catalog) does not support table names with multipart identifiers such as `catalog.database.table.metadata`. As a workaround, configure an `org.apache.iceberg.spark.SparkCatalog`, or use the Spark `DataFrameReader` API. + + +### History + +To show table history: + +```sql +SELECT * FROM prod.db.table.history; +``` + +| made_current_at | snapshot_id | parent_id | is_current_ancestor | +| -- | -- | -- | -- | +| 2019-02-08 03:29:51.215 | 5781947118336215154 | NULL | true | +| 2019-02-08 03:47:55.948 | 5179299526185056830 | 5781947118336215154 | true | +| 2019-02-09 16:24:30.13 | 296410040247533544 | 5179299526185056830 | false | +| 2019-02-09 16:32:47.336 | 2999875608062437330 | 5179299526185056830 | true | +| 2019-02-09 19:42:03.919 | 8924558786060583479 | 2999875608062437330 | true | +| 2019-02-09 19:49:16.343 | 6536733823181975045 | 8924558786060583479 | true | + +!!! info + **This shows a commit that was rolled back.** The example has two snapshots with the same parent, and one is *not* an ancestor of the current table state. + + +### Metadata Log Entries + +To show table metadata log entries: + +```sql +SELECT * from prod.db.table.metadata_log_entries; +``` + +| timestamp | file | latest_snapshot_id | latest_schema_id | latest_sequence_number | +| -- | -- | -- | -- | -- | +| 2022-07-28 10:43:52.93 | s3://.../table/metadata/00000-9441e604-b3c2-498a-a45a-6320e8ab9006.metadata.json | null | null | null | +| 2022-07-28 10:43:57.487 | s3://.../table/metadata/00001-f30823df-b745-4a0a-b293-7532e0c99986.metadata.json | 170260833677645300 | 0 | 1 | +| 2022-07-28 10:43:58.25 | s3://.../table/metadata/00002-2cc2837a-02dc-4687-acc1-b4d86ea486f4.metadata.json | 958906493976709774 | 0 | 2 | + +### Snapshots + +To show the valid snapshots for a table: + +```sql +SELECT * FROM prod.db.table.snapshots; +``` + +| committed_at | snapshot_id | parent_id | operation | manifest_list | summary | +| -- | -- | -- | -- | -- | -- | +| 2019-02-08 03:29:51.215 | 57897183625154 | null | append | s3://.../table/metadata/snap-57897183625154-1.avro | { added-records -> 2478404, total-records -> 2478404, added-data-files -> 438, total-data-files -> 438, spark.app.id -> application_1520379288616_155055 } | + +You can also join snapshots to table history. For example, this query will show table history, with the application ID that wrote each snapshot: + +```sql +select + h.made_current_at, + s.operation, + h.snapshot_id, + h.is_current_ancestor, + s.summary['spark.app.id'] +from prod.db.table.history h +join prod.db.table.snapshots s + on h.snapshot_id = s.snapshot_id +order by made_current_at +``` + +| made_current_at | operation | snapshot_id | is_current_ancestor | summary[spark.app.id] | +| -- | -- | -- | -- | -- | +| 2019-02-08 03:29:51.215 | append | 57897183625154 | true | application_1520379288616_155055 | +| 2019-02-09 16:24:30.13 | delete | 29641004024753 | false | application_1520379288616_151109 | +| 2019-02-09 16:32:47.336 | append | 57897183625154 | true | application_1520379288616_155055 | +| 2019-02-08 03:47:55.948 | overwrite | 51792995261850 | true | application_1520379288616_152431 | + +### Files + +To show a table's current data files: + +```sql +SELECT * FROM prod.db.table.files; +``` + +|content|file_path |file_format|spec_id|partition|record_count|file_size_in_bytes|column_sizes |value_counts |null_value_counts|nan_value_counts|lower_bounds |upper_bounds |key_metadata|split_offsets|equality_ids|sort_order_id| +| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | +| 0 | s3:/.../table/data/00000-3-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 01} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> c] | [1 -> , 2 -> c] | null | [4] | null | null | +| 0 | s3:/.../table/data/00001-4-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 02} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> b] | [1 -> , 2 -> b] | null | [4] | null | null | +| 0 | s3:/.../table/data/00002-5-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 03} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> a] | [1 -> , 2 -> a] | null | [4] | null | null | + +### Manifests + +To show a table's current file manifests: + +```sql +SELECT * FROM prod.db.table.manifests; +``` + +| path | length | partition_spec_id | added_snapshot_id | added_data_files_count | existing_data_files_count | deleted_data_files_count | partition_summaries | +| -- | -- | -- | -- | -- | -- | -- | -- | +| s3://.../table/metadata/45b5290b-ee61-4788-b324-b1e2735c0e10-m0.avro | 4479 | 0 | 6668963634911763636 | 8 | 0 | 0 | [[false,null,2019-05-13,2019-05-15]] | + +Note: +1. Fields within `partition_summaries` column of the manifests table correspond to `field_summary` structs within [manifest list](../../spec.md#manifest-lists), with the following order: + - `contains_null` + - `contains_nan` + - `lower_bound` + - `upper_bound` +2. `contains_nan` could return null, which indicates that this information is not available from the file's metadata. + This usually occurs when reading from V1 table, where `contains_nan` is not populated. + +### Partitions + +To show a table's current partitions: + +```sql +SELECT * FROM prod.db.table.partitions; +``` + +| partition | record_count | file_count | spec_id | +| -- | -- | -- | -- | +| {20211001, 11}| 1| 1| 0| +| {20211002, 11}| 1| 1| 0| +| {20211001, 10}| 1| 1| 0| +| {20211002, 10}| 1| 1| 0| + +Note: +1. For unpartitioned tables, the partitions table will contain only the record_count and file_count columns. + +2. The partitions metadata table shows partitions with data files or delete files in the current snapshot. However, delete files are not applied, and so in some cases partitions may be shown even though all their data rows are marked deleted by delete files. + +### All Metadata Tables + +These tables are unions of the metadata tables specific to the current snapshot, and return metadata across all snapshots. + +!!! danger + The "all" metadata tables may produce more than one row per data file or manifest file because metadata files may be part of more than one table snapshot. + + +#### All Data Files + +To show all of the table's data files and each file's metadata: + +```sql +SELECT * FROM prod.db.table.all_data_files; +``` + +| content | file_path | file_format | partition | record_count | file_size_in_bytes | column_sizes| value_counts | null_value_counts | nan_value_counts| lower_bounds| upper_bounds|key_metadata|split_offsets|equality_ids|sort_order_id| +| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | +| 0|s3://.../dt=20210102/00000-0-756e2512-49ae-45bb-aae3-c0ca475e7879-00001.parquet| PARQUET|{20210102}| 14| 2444|{1 -> 94, 2 -> 17}|{1 -> 14, 2 -> 14}| {1 -> 0, 2 -> 0}| {}|{1 -> 1, 2 -> 20210102}|{1 -> 2, 2 -> 20210102}| null| [4]| null| 0| +| 0|s3://.../dt=20210103/00000-0-26222098-032f-472b-8ea5-651a55b21210-00001.parquet| PARQUET|{20210103}| 14| 2444|{1 -> 94, 2 -> 17}|{1 -> 14, 2 -> 14}| {1 -> 0, 2 -> 0}| {}|{1 -> 1, 2 -> 20210103}|{1 -> 3, 2 -> 20210103}| null| [4]| null| 0| +| 0|s3://.../dt=20210104/00000-0-a3bb1927-88eb-4f1c-bc6e-19076b0d952e-00001.parquet| PARQUET|{20210104}| 14| 2444|{1 -> 94, 2 -> 17}|{1 -> 14, 2 -> 14}| {1 -> 0, 2 -> 0}| {}|{1 -> 1, 2 -> 20210104}|{1 -> 3, 2 -> 20210104}| null| [4]| null| 0| + +#### All Manifests + +To show all of the table's manifest files: + +```sql +SELECT * FROM prod.db.table.all_manifests; +``` + +| path | length | partition_spec_id | added_snapshot_id | added_data_files_count | existing_data_files_count | deleted_data_files_count| partition_summaries| +| -- | -- | -- | -- | -- | -- | -- | -- | +| s3://.../metadata/a85f78c5-3222-4b37-b7e4-faf944425d48-m0.avro | 6376 | 0 | 6272782676904868561 | 2 | 0 | 0 |[{false, false, 20210101, 20210101}]| + +Note: +1. Fields within `partition_summaries` column of the manifests table correspond to `field_summary` structs within [manifest list](../../spec.md#manifest-lists), with the following order: + - `contains_null` + - `contains_nan` + - `lower_bound` + - `upper_bound` +2. `contains_nan` could return null, which indicates that this information is not available from the file's metadata. + This usually occurs when reading from V1 table, where `contains_nan` is not populated. + +### References + +To show a table's known snapshot references: + +```sql +SELECT * FROM prod.db.table.refs; +``` + +| name | type | snapshot_id | max_reference_age_in_ms | min_snapshots_to_keep | max_snapshot_age_in_ms | +| -- | -- | -- | -- | -- | -- | +| main | BRANCH | 4686954189838128572 | 10 | 20 | 30 | +| testTag | TAG | 4686954189838128572 | 10 | null | null | + +### Inspecting with DataFrames + +Metadata tables can be loaded using the DataFrameReader API: + +```scala +// named metastore table +spark.read.format("iceberg").load("db.table.files") +// Hadoop path table +spark.read.format("iceberg").load("hdfs://nn:8020/path/to/table#files") +``` + +### Time Travel with Metadata Tables + +To inspect a tables's metadata with the time travel feature: + +```sql +-- get the table's file manifests at timestamp Sep 20, 2021 08:00:00 +SELECT * FROM prod.db.table.manifests TIMESTAMP AS OF '2021-09-20 08:00:00'; + +-- get the table's partitions with snapshot id 10963874102873L +SELECT * FROM prod.db.table.partitions VERSION AS OF 10963874102873; +``` + +Metadata tables can also be inspected with time travel using the DataFrameReader API: + +```scala +// load the table's file metadata at snapshot-id 10963874102873 as DataFrame +spark.read.format("iceberg").option("snapshot-id", 10963874102873L).load("db.table.files") +``` diff --git a/site/docs/docs/nightly/docs/spark-structured-streaming.md b/site/docs/docs/nightly/docs/spark-structured-streaming.md new file mode 100644 index 000000000000..b3858972cbc3 --- /dev/null +++ b/site/docs/docs/nightly/docs/spark-structured-streaming.md @@ -0,0 +1,121 @@ +--- +title: "Structured Streaming" +--- + + +# Spark Structured Streaming + +Iceberg uses Apache Spark's DataSourceV2 API for data source and catalog implementations. Spark DSv2 is an evolving API +with different levels of support in Spark versions. + +As of Spark 3, DataFrame reads and writes are supported. + +## Streaming Reads + +Iceberg supports processing incremental data in spark structured streaming jobs which starts from a historical timestamp: + +```scala +val df = spark.readStream + .format("iceberg") + .option("stream-from-timestamp", Long.toString(streamStartTimestamp)) + .load("database.table_name") +``` + +!!! warning + Iceberg only supports reading data from append snapshots. Overwrite snapshots cannot be processed and will cause an exception by default. Overwrites may be ignored by setting `streaming-skip-overwrite-snapshots=true`. Similarly, delete snapshots will cause an exception by default, and deletes may be ignored by setting `streaming-skip-delete-snapshots=true`. + +## Streaming Writes + +To write values from streaming query to Iceberg table, use `DataStreamWriter`: + +```scala +val tableIdentifier: String = ... +data.writeStream + .format("iceberg") + .outputMode("append") + .trigger(Trigger.ProcessingTime(1, TimeUnit.MINUTES)) + .option("path", tableIdentifier) + .option("checkpointLocation", checkpointPath) + .start() +``` + +The `tableIdentifier` can be: + +* The fully-qualified path to a HDFS table, like `hdfs://nn:8020/path/to/table` +* A table name if the table is tracked by a catalog, like `database.table_name` + +Iceberg doesn't support "continuous processing", as it doesn't provide the interface to "commit" the output. + +Iceberg supports `append` and `complete` output modes: + +* `append`: appends the rows of every micro-batch to the table +* `complete`: replaces the table contents every micro-batch + +The table should be created in prior to start the streaming query. Refer [SQL create table](spark-ddl.md#create-table) +on Spark page to see how to create the Iceberg table. + +### Writing against partitioned table + +Iceberg requires the data to be sorted according to the partition spec per task (Spark partition) in prior to write +against partitioned table. For batch queries you're encouraged to do explicit sort to fulfill the requirement +(see [here](spark-writes.md#writing-distribution-modes)), but the approach would bring additional latency as +repartition and sort are considered as heavy operations for streaming workload. To avoid additional latency, you can +enable fanout writer to eliminate the requirement. + +```scala +val tableIdentifier: String = ... +data.writeStream + .format("iceberg") + .outputMode("append") + .trigger(Trigger.ProcessingTime(1, TimeUnit.MINUTES)) + .option("path", tableIdentifier) + .option("fanout-enabled", "true") + .option("checkpointLocation", checkpointPath) + .start() +``` + +Fanout writer opens the files per partition value and doesn't close these files till write task is finished. +This functionality is discouraged for batch query, as explicit sort against output rows isn't expensive for batch workload. + +## Maintenance for streaming tables + +Streaming queries can create new table versions quickly, which creates lots of table metadata to track those versions. +Maintaining metadata by tuning the rate of commits, expiring old snapshots, and automatically cleaning up metadata files +is highly recommended. + +### Tune the rate of commits + +Having high rate of commits would produce lots of data files, manifests, and snapshots which leads the table hard +to maintain. We encourage having trigger interval 1 minute at minimum, and increase the interval if needed. + +The triggers section in [Structured Streaming Programming Guide](https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html#triggers) +documents how to configure the interval. + +### Expire old snapshots + +Each micro-batch written to a table produces a new snapshot, which are tracked in table metadata until they are expired to remove the metadata and any data files that are no longer needed. Snapshots accumulate quickly with frequent commits, so it is highly recommended that tables written by streaming queries are [regularly maintained](maintenance.md#expire-snapshots). + +### Compacting data files + +The amount of data written in a micro batch is typically small, which can cause the table metadata to track lots of small files. [Compacting small files into larger files](maintenance.md#compact-data-files) reduces the metadata needed by the table, and increases query efficiency. + +### Rewrite manifests + +To optimize write latency on streaming workload, Iceberg may write the new snapshot with a "fast" append that does not automatically compact manifests. +This could lead lots of small manifest files. Manifests can be [rewritten to optimize queries and to compact](maintenance.md#rewrite-manifests). + diff --git a/site/docs/docs/nightly/docs/spark-writes.md b/site/docs/docs/nightly/docs/spark-writes.md new file mode 100644 index 000000000000..52d7e23f644c --- /dev/null +++ b/site/docs/docs/nightly/docs/spark-writes.md @@ -0,0 +1,442 @@ +--- +title: "Writes" +--- + + +# Spark Writes + +To use Iceberg in Spark, first configure [Spark catalogs](spark-configuration.md). + +Some plans are only available when using [Iceberg SQL extensions](spark-configuration.md#sql-extensions) in Spark 3. + +Iceberg uses Apache Spark's DataSourceV2 API for data source and catalog implementations. Spark DSv2 is an evolving API with different levels of support in Spark versions: + +| Feature support | Spark 3 | Notes | +|--------------------------------------------------|-----------|----------------------------------------------| +| [SQL insert into](#insert-into) | ✔️ | | +| [SQL merge into](#merge-into) | ✔️ | ⚠ Requires Iceberg Spark extensions | +| [SQL insert overwrite](#insert-overwrite) | ✔️ | | +| [SQL delete from](#delete-from) | ✔️ | ⚠ Row-level delete requires Spark extensions | +| [SQL update](#update) | ✔️ | ⚠ Requires Iceberg Spark extensions | +| [DataFrame append](#appending-data) | ✔️ | | +| [DataFrame overwrite](#overwriting-data) | ✔️ | | +| [DataFrame CTAS and RTAS](#creating-tables) | ✔️ | | + + +## Writing with SQL + +Spark 3 supports SQL `INSERT INTO`, `MERGE INTO`, and `INSERT OVERWRITE`, as well as the new `DataFrameWriterV2` API. + +### `INSERT INTO` + +To append new data to a table, use `INSERT INTO`. + +```sql +INSERT INTO prod.db.table VALUES (1, 'a'), (2, 'b') +``` +```sql +INSERT INTO prod.db.table SELECT ... +``` + +### `MERGE INTO` + +Spark 3 added support for `MERGE INTO` queries that can express row-level updates. + +Iceberg supports `MERGE INTO` by rewriting data files that contain rows that need to be updated in an `overwrite` commit. + +**`MERGE INTO` is recommended instead of `INSERT OVERWRITE`** because Iceberg can replace only the affected data files, and because the data overwritten by a dynamic overwrite may change if the table's partitioning changes. + + +#### `MERGE INTO` syntax + +`MERGE INTO` updates a table, called the _target_ table, using a set of updates from another query, called the _source_. The update for a row in the target table is found using the `ON` clause that is like a join condition. + +```sql +MERGE INTO prod.db.target t -- a target table +USING (SELECT ...) s -- the source updates +ON t.id = s.id -- condition to find updates for target rows +WHEN ... -- updates +``` + +Updates to rows in the target table are listed using `WHEN MATCHED ... THEN ...`. Multiple `MATCHED` clauses can be added with conditions that determine when each match should be applied. The first matching expression is used. + +```sql +WHEN MATCHED AND s.op = 'delete' THEN DELETE +WHEN MATCHED AND t.count IS NULL AND s.op = 'increment' THEN UPDATE SET t.count = 0 +WHEN MATCHED AND s.op = 'increment' THEN UPDATE SET t.count = t.count + 1 +``` + +Source rows (updates) that do not match can be inserted: + +```sql +WHEN NOT MATCHED THEN INSERT * +``` + +Inserts also support additional conditions: + +```sql +WHEN NOT MATCHED AND s.event_time > still_valid_threshold THEN INSERT (id, count) VALUES (s.id, 1) +``` + +Only one record in the source data can update any given row of the target table, or else an error will be thrown. + + +### `INSERT OVERWRITE` + +`INSERT OVERWRITE` can replace data in the table with the result of a query. Overwrites are atomic operations for Iceberg tables. + +The partitions that will be replaced by `INSERT OVERWRITE` depends on Spark's partition overwrite mode and the partitioning of a table. `MERGE INTO` can rewrite only affected data files and has more easily understood behavior, so it is recommended instead of `INSERT OVERWRITE`. + +#### Overwrite behavior + +Spark's default overwrite mode is **static**, but **dynamic overwrite mode is recommended when writing to Iceberg tables.** Static overwrite mode determines which partitions to overwrite in a table by converting the `PARTITION` clause to a filter, but the `PARTITION` clause can only reference table columns. + +Dynamic overwrite mode is configured by setting `spark.sql.sources.partitionOverwriteMode=dynamic`. + +To demonstrate the behavior of dynamic and static overwrites, consider a `logs` table defined by the following DDL: + +```sql +CREATE TABLE prod.my_app.logs ( + uuid string NOT NULL, + level string NOT NULL, + ts timestamp NOT NULL, + message string) +USING iceberg +PARTITIONED BY (level, hours(ts)) +``` + +#### Dynamic overwrite + +When Spark's overwrite mode is dynamic, partitions that have rows produced by the `SELECT` query will be replaced. + +For example, this query removes duplicate log events from the example `logs` table. + +```sql +INSERT OVERWRITE prod.my_app.logs +SELECT uuid, first(level), first(ts), first(message) +FROM prod.my_app.logs +WHERE cast(ts as date) = '2020-07-01' +GROUP BY uuid +``` + +In dynamic mode, this will replace any partition with rows in the `SELECT` result. Because the date of all rows is restricted to 1 July, only hours of that day will be replaced. + +#### Static overwrite + +When Spark's overwrite mode is static, the `PARTITION` clause is converted to a filter that is used to delete from the table. If the `PARTITION` clause is omitted, all partitions will be replaced. + +Because there is no `PARTITION` clause in the query above, it will drop all existing rows in the table when run in static mode, but will only write the logs from 1 July. + +To overwrite just the partitions that were loaded, add a `PARTITION` clause that aligns with the `SELECT` query filter: + +```sql +INSERT OVERWRITE prod.my_app.logs +PARTITION (level = 'INFO') +SELECT uuid, first(level), first(ts), first(message) +FROM prod.my_app.logs +WHERE level = 'INFO' +GROUP BY uuid +``` + +Note that this mode cannot replace hourly partitions like the dynamic example query because the `PARTITION` clause can only reference table columns, not hidden partitions. + +### `DELETE FROM` + +Spark 3 added support for `DELETE FROM` queries to remove data from tables. + +Delete queries accept a filter to match rows to delete. + +```sql +DELETE FROM prod.db.table +WHERE ts >= '2020-05-01 00:00:00' and ts < '2020-06-01 00:00:00' + +DELETE FROM prod.db.all_events +WHERE session_time < (SELECT min(session_time) FROM prod.db.good_events) + +DELETE FROM prod.db.orders AS t1 +WHERE EXISTS (SELECT oid FROM prod.db.returned_orders WHERE t1.oid = oid) +``` + +If the delete filter matches entire partitions of the table, Iceberg will perform a metadata-only delete. If the filter matches individual rows of a table, then Iceberg will rewrite only the affected data files. + +### `UPDATE` + +Spark 3.1 added support for `UPDATE` queries that update matching rows in tables. + +Update queries accept a filter to match rows to update. + +```sql +UPDATE prod.db.table +SET c1 = 'update_c1', c2 = 'update_c2' +WHERE ts >= '2020-05-01 00:00:00' and ts < '2020-06-01 00:00:00' + +UPDATE prod.db.all_events +SET session_time = 0, ignored = true +WHERE session_time < (SELECT min(session_time) FROM prod.db.good_events) + +UPDATE prod.db.orders AS t1 +SET order_status = 'returned' +WHERE EXISTS (SELECT oid FROM prod.db.returned_orders WHERE t1.oid = oid) +``` + +For more complex row-level updates based on incoming data, see the section on `MERGE INTO`. + +## Writing to Branches +Branch writes can be performed via SQL by providing a branch identifier, `branch_yourBranch` in the operation. +Branch writes can also be performed as part of a write-audit-publish (WAP) workflow by specifying the `spark.wap.branch` config. +Note WAP branch and branch identifier cannot both be specified. +Also, the branch must exist before performing the write. +The operation does **not** create the branch if it does not exist. +For more information on branches please refer to [branches](branching.md). + +```sql +-- INSERT (1,' a') (2, 'b') into the audit branch. +INSERT INTO prod.db.table.branch_audit VALUES (1, 'a'), (2, 'b'); + +-- MERGE INTO audit branch +MERGE INTO prod.db.table.branch_audit t +USING (SELECT ...) s +ON t.id = s.id +WHEN ... + +-- UPDATE audit branch +UPDATE prod.db.table.branch_audit AS t1 +SET val = 'c' + +-- DELETE FROM audit branch +DELETE FROM prod.dbl.table.branch_audit WHERE id = 2; + +-- WAP Branch write +SET spark.wap.branch = audit-branch +INSERT INTO prod.db.table VALUES (3, 'c'); +``` + +## Writing with DataFrames + +Spark 3 introduced the new `DataFrameWriterV2` API for writing to tables using data frames. The v2 API is recommended for several reasons: + +* CTAS, RTAS, and overwrite by filter are supported +* All operations consistently write columns to a table by name +* Hidden partition expressions are supported in `partitionedBy` +* Overwrite behavior is explicit, either dynamic or by a user-supplied filter +* The behavior of each operation corresponds to SQL statements + - `df.writeTo(t).create()` is equivalent to `CREATE TABLE AS SELECT` + - `df.writeTo(t).replace()` is equivalent to `REPLACE TABLE AS SELECT` + - `df.writeTo(t).append()` is equivalent to `INSERT INTO` + - `df.writeTo(t).overwritePartitions()` is equivalent to dynamic `INSERT OVERWRITE` + +The v1 DataFrame `write` API is still supported, but is not recommended. + +!!! danger + When writing with the v1 DataFrame API in Spark 3, use `saveAsTable` or `insertInto` to load tables with a catalog. + Using `format("iceberg")` loads an isolated table reference that will not automatically refresh tables used by queries. + + + +### Appending data + +To append a dataframe to an Iceberg table, use `append`: + +```scala +val data: DataFrame = ... +data.writeTo("prod.db.table").append() +``` + +### Overwriting data + +To overwrite partitions dynamically, use `overwritePartitions()`: + +```scala +val data: DataFrame = ... +data.writeTo("prod.db.table").overwritePartitions() +``` + +To explicitly overwrite partitions, use `overwrite` to supply a filter: + +```scala +data.writeTo("prod.db.table").overwrite($"level" === "INFO") +``` + +### Creating tables + +To run a CTAS or RTAS, use `create`, `replace`, or `createOrReplace` operations: + +```scala +val data: DataFrame = ... +data.writeTo("prod.db.table").create() +``` + +If you have replaced the default Spark catalog (`spark_catalog`) with Iceberg's `SparkSessionCatalog`, do: + +```scala +val data: DataFrame = ... +data.writeTo("db.table").using("iceberg").create() +``` + +Create and replace operations support table configuration methods, like `partitionedBy` and `tableProperty`: + +```scala +data.writeTo("prod.db.table") + .tableProperty("write.format.default", "orc") + .partitionedBy($"level", days($"ts")) + .createOrReplace() +``` + +The Iceberg table location can also be specified by the `location` table property: + +```scala +data.writeTo("prod.db.table") + .tableProperty("location", "/path/to/location") + .createOrReplace() +``` + +## Writing Distribution Modes + +Iceberg's default Spark writers require that the data in each spark task is clustered by partition values. This +distribution is required to minimize the number of file handles that are held open while writing. By default, starting +in Iceberg 1.2.0, Iceberg also requests that Spark pre-sort data to be written to fit this distribution. The +request to Spark is done through the table property `write.distribution-mode` with the value `hash`. + +Let's go through writing the data against below sample table: + +```sql +CREATE TABLE prod.db.sample ( + id bigint, + data string, + category string, + ts timestamp) +USING iceberg +PARTITIONED BY (days(ts), category) +``` + +To write data to the sample table, data needs to be sorted by `days(ts), category` but this is taken care +of automatically by the default `hash` distribution. Previously this would have required manually sorting, but this +is no longer the case. + +```sql +INSERT INTO prod.db.sample +SELECT id, data, category, ts FROM another_table +``` + + +There are 3 options for `write.distribution-mode` + +* `none` - This is the previous default for Iceberg. +This mode does not request any shuffles or sort to be performed automatically by Spark. Because no work is done +automatically by Spark, the data must be *manually* sorted by partition value. The data must be sorted either within +each spark task, or globally within the entire dataset. A global sort will minimize the number of output files. +A sort can be avoided by using the Spark [write fanout](#write-properties) property but this will cause all +file handles to remain open until each write task has completed. +* `hash` - This mode is the new default and requests that Spark uses a hash-based exchange to shuffle the incoming +write data before writing. +Practically, this means that each row is hashed based on the row's partition value and then placed +in a corresponding Spark task based upon that value. Further division and coalescing of tasks may take place because of +[Spark's Adaptive Query planning](#controlling-file-sizes). +* `range` - This mode requests that Spark perform a range based exchanged to shuffle the data before writing. +This is a two stage procedure which is more expensive than the `hash` mode. The first stage samples the data to +be written based on the partition and sort columns. The second stage uses the range information to shuffle the input data into Spark +tasks. Each task gets an exclusive range of the input data which clusters the data by partition and also globally sorts. +While this is more expensive than the hash distribution, the global ordering can be beneficial for read performance if +sorted columns are used during queries. This mode is used by default if a table is created with a +sort-order. Further division and coalescing of tasks may take place because of +[Spark's Adaptive Query planning](#controlling-file-sizes). + + +## Controlling File Sizes + +When writing data to Iceberg with Spark, it's important to note that Spark cannot write a file larger than a Spark +task and a file cannot span an Iceberg partition boundary. This means although Iceberg will always roll over a file +when it grows to [`write.target-file-size-bytes`](configuration.md#write-properties), but unless the Spark task is +large enough that will not happen. The size of the file created on disk will also be much smaller than the Spark task +since the on disk data will be both compressed and in columnar format as opposed to Spark's uncompressed row +representation. This means a 100 megabyte Spark task will create a file much smaller than 100 megabytes even if that +task is writing to a single Iceberg partition. If the task writes to multiple partitions, the files will be even +smaller than that. + +To control what data ends up in each Spark task use a [`write distribution mode`](#writing-distribution-modes) +or manually repartition the data. + +To adjust Spark's task size it is important to become familiar with Spark's various Adaptive Query Execution (AQE) +parameters. When the `write.distribution-mode` is not `none`, AQE will control the coalescing and splitting of Spark +tasks during the exchange to try to create tasks of `spark.sql.adaptive.advisoryPartitionSizeInBytes` size. These +settings will also affect any user performed re-partitions or sorts. +It is important again to note that this is the in-memory Spark row size and not the on disk +columnar-compressed size, so a larger value than the target file size will need to be specified. The ratio of +in-memory size to on disk size is data dependent. Future work in Spark should allow Iceberg to automatically adjust this +parameter at write time to match the `write.target-file-size-bytes`. + +## Type compatibility + +Spark and Iceberg support different set of types. Iceberg does the type conversion automatically, but not for all combinations, +so you may want to understand the type conversion in Iceberg in prior to design the types of columns in your tables. + +### Spark type to Iceberg type + +This type conversion table describes how Spark types are converted to the Iceberg types. The conversion applies on both creating Iceberg table and writing to Iceberg table via Spark. + +| Spark | Iceberg | Notes | +|-----------------|-------------------------|-------| +| boolean | boolean | | +| short | integer | | +| byte | integer | | +| integer | integer | | +| long | long | | +| float | float | | +| double | double | | +| date | date | | +| timestamp | timestamp with timezone | | +| char | string | | +| varchar | string | | +| string | string | | +| binary | binary | | +| decimal | decimal | | +| struct | struct | | +| array | list | | +| map | map | | + +!!! info + The table is based on representing conversion during creating table. In fact, broader supports are applied on write. Here're some points on write: + + * Iceberg numeric types (`integer`, `long`, `float`, `double`, `decimal`) support promotion during writes. e.g. You can write Spark types `short`, `byte`, `integer`, `long` to Iceberg type `long`. + * You can write to Iceberg `fixed` type using Spark `binary` type. Note that assertion on the length will be performed. + + +### Iceberg type to Spark type + +This type conversion table describes how Iceberg types are converted to the Spark types. The conversion applies on reading from Iceberg table via Spark. + +| Iceberg | Spark | Note | +|----------------------------|-------------------------|---------------| +| boolean | boolean | | +| integer | integer | | +| long | long | | +| float | float | | +| double | double | | +| date | date | | +| time | | Not supported | +| timestamp with timezone | timestamp | | +| timestamp without timezone | | Not supported | +| string | string | | +| uuid | string | | +| fixed | binary | | +| binary | binary | | +| decimal | decimal | | +| struct | struct | | +| list | array | | +| map | map | | + diff --git a/site/docs/docs/nightly/docs/table-migration.md b/site/docs/docs/nightly/docs/table-migration.md new file mode 100644 index 000000000000..2e56d378c531 --- /dev/null +++ b/site/docs/docs/nightly/docs/table-migration.md @@ -0,0 +1,74 @@ +--- +title: "Overview" +--- + + +# Table Migration +Apache Iceberg supports converting existing tables in other formats to Iceberg tables. This section introduces the general concept of table migration, its approaches, and existing implementations in Iceberg. + +## Migration Approaches +There are two methods for executing table migration: full data migration and in-place metadata migration. + +Full data migration involves copying all data files from the source table to the new Iceberg table. This method makes the new table fully isolated from the source table, but is slower and doubles the space. +In practice, users can use operations like [Create-Table-As-Select](spark-ddl.md#create-table-as-select), [INSERT](spark-writes.md#insert-into), and Change-Data-Capture pipelines to perform such migration. + +In-place metadata migration preserves the existing data files while incorporating Iceberg metadata on top of them. +This method is not only faster but also eliminates the need for data duplication. However, the new table and the source table are not fully isolated. In other words, if any processes vacuum data files from the source table, the new table will also be affected. + +In this doc, we will describe more about in-place metadata migration. + +![In-Place Metadata Migration](assets/images/iceberg-in-place-metadata-migration.png) + +Apache Iceberg supports the in-place metadata migration approach, which includes three important actions: **Snapshot Table**, **Migrate Table**, and **Add Files**. + +## Snapshot Table +The Snapshot Table action creates a new iceberg table with a different name and with the same schema and partitioning as the source table, leaving the source table unchanged during and after the action. + +- Create a new Iceberg table with the same metadata (schema, partition spec, etc.) as the source table and a different name. Readers and Writers on the source table can continue to work. + +![Snapshot Table Step 1](assets/images/iceberg-snapshotaction-step1.png) + +- Commit all data files across all partitions to the new Iceberg table. The source table remains unchanged. Readers can be switched to the new Iceberg table. + +![Snapshot Table Step 2](assets/images/iceberg-snapshotaction-step2.png) + +- Eventually, all writers can be switched to the new Iceberg table. Once all writers are transitioned to the new Iceberg table, the migration process will be considered complete. + +## Migrate Table +The Migrate Table action also creates a new Iceberg table with the same schema and partitioning as the source table. However, during the action execution, it locks and drops the source table from the catalog. +Consequently, Migrate Table requires all modifications working on the source table to be stopped before the action is performed. + +Stop all writers interacting with the source table. Readers that also support Iceberg may continue reading. + +![Migrate Table Step 1](assets/images/iceberg-migrateaction-step1.png) + +- Create a new Iceberg table with the same identifier and metadata (schema, partition spec, etc.) as the source table. Rename the source table for a backup in case of failure and rollback. + +![Migrate Table Step 2](assets/images/iceberg-migrateaction-step2.png) + +- Commit all data files across all partitions to the new Iceberg table. Drop the source table. Writers can start writing to the new Iceberg table. + +![Migrate Table Step 3](assets/images/iceberg-migrateaction-step3.png) + +## Add Files +After the initial step (either Snapshot Table or Migrate Table), it is common to find some data files that have not been migrated. These files often originate from concurrent writers who continue writing to the source table during or after the migration process. +In practice, these files can be new data files in Hive tables or new snapshots (versions) of Delta Lake tables. The Add Files action is essential for incorporating these files into the Iceberg table. + +# Migrating From Different Table Formats +* [From Hive to Iceberg](hive-migration.md) +* [From Delta Lake to Iceberg](delta-lake-migration.md) diff --git a/site/docs/docs/nightly/mkdocs.yml b/site/docs/docs/nightly/mkdocs.yml new file mode 100644 index 000000000000..3806e06c5d06 --- /dev/null +++ b/site/docs/docs/nightly/mkdocs.yml @@ -0,0 +1,70 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +site_name: docs/nightly + +plugins: + - search + +nav: + - index.md + - Tables: + - branching.md + - configuration.md + - evolution.md + - maintenance.md + - partitioning.md + - performance.md + - reliability.md + - schemas.md + - Spark: + - spark-getting-started.md + - spark-configuration.md + - spark-ddl.md + - spark-procedures.md + - spark-queries.md + - spark-structured-streaming.md + - spark-writes.md + - Flink: + - flink.md + - flink-connector.md + - flink-ddl.md + - flink-queries.md + - flink-writes.md + - flink-actions.md + - flink-configuration.md + - hive.md + - Trino: https://trino.io/docs/current/connector/iceberg.html + - Clickhouse: https://clickhouse.com/docs/en/engines/table-engines/integrations/iceberg + - Presto: https://prestodb.io/docs/current/connector/iceberg.html + - Dremio: https://docs.dremio.com/data-formats/apache-iceberg/ + - Starrocks: https://docs.starrocks.io/en-us/latest/data_source/catalog/iceberg_catalog + - Amazon Athena: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg.html + - Amazon EMR: https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-iceberg-use-cluster.html + - Impala: https://impala.apache.org/docs/build/html/topics/impala_iceberg.html + - Doris: https://doris.apache.org/docs/dev/lakehouse/multi-catalog/iceberg + - Integrations: + - aws.md + - dell.md + - jdbc.md + - nessie.md + - API: + - java-api-quickstart.md + - api.md + - custom-catalog.md + - Javadoc: ../../javadoc/nightly/ + - PyIceberg: https://py.iceberg.apache.org/ diff --git a/site/docs/fileio.md b/site/docs/fileio.md new file mode 100644 index 000000000000..454e70b0eaad --- /dev/null +++ b/site/docs/fileio.md @@ -0,0 +1,44 @@ +--- +title: "FileIO" +--- + + +# Iceberg FileIO + +## Overview + +Iceberg comes with a flexible abstraction around reading and writing data and metadata files. The FileIO interface allows the Iceberg library to communicate with the underlying storage layer. FileIO is used for all metadata operations during the job planning and commit stages. + +## Iceberg Files + +The metadata for an Iceberg table tracks the absolute path for data files which allows greater abstraction over the physical layout. Additionally, changes to table state are performed by writing new metadata files and never involve renaming files. This allows a much smaller set of requirements for file operations. The essential functionality for a FileIO implementation is that it can read files, write files, and seek to any position within a stream. + +## Usage in Processing Engines + +The responsibility of reading and writing data files lies with the processing engines and happens during task execution. However, after data files are written, processing engines use FileIO to write new Iceberg metadata files that capture the new state of the table. A blog post that provides a deeper understanding of FileIO is +[Iceberg FileIO: Cloud Native Tables](https://tabular.io/blog/iceberg-fileio/) + +Different FileIO implementations are used depending on the type of storage. Iceberg comes with a set of FileIO implementations for popular storage providers. +- Amazon S3 +- Google Cloud Storage +- Object Service Storage (including https) +- Dell Enterprise Cloud Storage +- Hadoop (adapts any Hadoop FileSystem implementation) + +As an example, take a look at the blog post [Using Iceberg's S3FileIO Implementation to Store Your Data in MinIO](https://tabular.io/blog/minio/) +which walks through how to use the Amazon S3 FileIO with MinIO. diff --git a/site/docs/gcm-stream-spec.md b/site/docs/gcm-stream-spec.md new file mode 100644 index 000000000000..d2074e02ab6d --- /dev/null +++ b/site/docs/gcm-stream-spec.md @@ -0,0 +1,85 @@ +--- +title: "AES GCM Stream Spec" +--- + + +# AES GCM Stream file format extension + +## Background and Motivation + +Iceberg supports a number of data file formats. Two of these formats (Parquet and ORC) have built-in encryption capabilities, that allow to protect sensitive information in the data files. However, besides the data files, Iceberg tables also have metadata files, that keep sensitive information too (e.g., min/max values in manifest files, or bloom filter bitsets in puffin files). Metadata file formats (AVRO, JSON, Puffin) don't have encryption support. + +Moreover, with the exception of Parquet, no Iceberg data or metadata file format supports integrity verification, required for end-to-end tamper proofing of Iceberg tables. + +This document specifies details of a simple file format extension that adds encryption and tamper-proofing to any existing file format. + +## Goals + +* Metadata encryption: enable encryption of manifests, manifest lists, snapshots and stats. +* Avro data encryption: enable encryption of data files in tables that use the Avro format. +* Support read splitting: enable seekable decrypting streams that can be used with splittable formats like Avro. +* Tamper proofing of Iceberg data and metadata files. + +## Overview + +The output stream, produced by a metadata or data writer, is split into equal-size blocks (plus last block that can be shorter). Each block is enciphered (encrypted/signed) with a given encryption key, and stored in a file in the AES GCM Stream format. Upon reading, the stored cipherblocks are verified for integrity; then decrypted and passed to metadata or data readers. + +## Encryption algorithm + +AES GCM Stream uses the standard AEG GCM cipher, and supports all AES key sizes: 128, 192 and 256 bits. + +AES GCM is an authenticated encryption. Besides data confidentiality (encryption), it supports two levels of integrity verification (authentication): of the data (default), and of the data combined with an optional AAD (“additional authenticated data”). An AAD is a free text to be authenticated, together with the data. The structure of AES GCM Stream AADs is described below. + +AES GCM requires a unique vector to be provided for each encrypted block. In this document, the unique input to GCM encryption is called nonce (“number used once”). AES GCM Stream encryption uses the RBG-based (random bit generator) nonce construction as defined in the section 8.2.2 of the NIST SP 800-38D document. For each encrypted block, AES GCM Stream generates a unique nonce with a length of 12 bytes (96 bits). + +## Format specification + +### File structure + +The AES GCM Stream files have the following structure + +``` +Magic BlockLength CipherBlock₁ CipherBlock₂ ... CipherBlockₙ +``` + +where + +- `Magic` is four bytes 0x41, 0x47, 0x53, 0x31 ("AGS1", short for: AES GCM Stream, version 1) +- `BlockLength` is four bytes (little endian) integer keeping the length of the equal-size split blocks before encryption. The length is specified in bytes. +- `CipherBlockᵢ` is the i-th enciphered block in the file, with the structure defined below. + +### Cipher Block structure + +Cipher blocks have the following structure + +| nonce | ciphertext | tag | +|-------|------------|-----| + +where + +- `nonce` is the AES GCM nonce, with a length of 12 bytes. +- `ciphertext` is the encrypted block. Its length is identical to the length of the block before encryption ("plaintext"). The length of all plaintext blocks, except the last, is `BlockLength` bytes. The last block has a non-zero length <= `BlockLength`. +- `tag` is the AES GCM tag, with a length of 16 bytes. + +AES GCM Stream encrypts all blocks by the GCM cipher, without padding. The AES GCM cipher must be implemented by a cryptographic provider according to the NIST SP 800-38D specification. In AES GCM Stream, an input to the GCM cipher is an AES encryption key, a nonce, a plaintext and an AAD (described below). The output is a ciphertext with the length equal to that of plaintext, and a 16-byte authentication tag used to verify the ciphertext and AAD integrity. + +### Additional Authenticated Data + +The AES GCM cipher protects against byte replacement inside a ciphertext block - but, without an AAD, it can't prevent replacement of one ciphertext block with another (encrypted with the same key). AES GCM Stream leverages AADs to protect against swapping ciphertext blocks inside a file or between files. AES GCM Stream can also protect against swapping full files - for example, replacement of a metadata file with an old version. AADs are built to reflects the identity of a file and of the blocks inside the file. + +AES GCM Stream constructs a block AAD from two components: an AAD prefix - a string provided by Iceberg for the file (with the file ID), and an AAD suffix - the block sequence number in the file, as an int in a 4-byte little-endian form. The block AAD is a direct concatenation of the prefix and suffix parts. diff --git a/site/docs/hive-quickstart.md b/site/docs/hive-quickstart.md new file mode 100644 index 000000000000..57cc02157a53 --- /dev/null +++ b/site/docs/hive-quickstart.md @@ -0,0 +1,115 @@ +--- +title: "Hive and Iceberg Quickstart" +--- + + + +## Hive and Iceberg Quickstart + +This guide will get you up and running with an Iceberg and Hive environment, including sample code to +highlight some powerful features. You can learn more about Iceberg's Hive runtime by checking out the [Hive](docs/latest/hive.md) section. + +- [Docker Images](#docker-images) +- [Creating a Table](#creating-a-table) +- [Writing Data to a Table](#writing-data-to-a-table) +- [Reading Data from a Table](#reading-data-from-a-table) +- [Next Steps](#next-steps) + +### Docker Images + +The fastest way to get started is to use [Apache Hive images](https://hub.docker.com/r/apache/hive) +which provides a SQL-like interface to create and query Iceberg tables from your laptop. You need to install the [Docker Desktop](https://www.docker.com/products/docker-desktop/). + +Take a look at the Tags tab in [Apache Hive docker images](https://hub.docker.com/r/apache/hive/tags?page=1&ordering=-last_updated) to see the available Hive versions. + +Set the version variable. +```sh +export HIVE_VERSION=4.0.0-alpha-2 +``` + +Start the container, using the option `--platform linux/amd64` for a Mac with an M-Series chip: +```sh +docker run -d --platform linux/amd64 -p 10000:10000 -p 10002:10002 --env SERVICE_NAME=hiveserver2 --name hive4 apache/hive:${HIVE_VERSION} +``` + +The docker run command above configures Hive to use the embedded derby database for Hive Metastore. Hive Metastore functions as the Iceberg catalog to locate Iceberg files, which can be anywhere. + +Give HiveServer (HS2) a little time to come up in the docker container, and then start the Hive Beeline client using the following command to connect with the HS2 containers you already started: +```sh +docker exec -it hive4 beeline -u 'jdbc:hive2://localhost:10000/' +``` + +The hive prompt appears: +```sh +0: jdbc:hive2://localhost:10000> +``` + +You can now run SQL queries to create Iceberg tables and query the tables. +```sql +show databases; +``` + +### Creating a Table + +To create your first Iceberg table in Hive, run a [`CREATE TABLE`](docs/latest/hive.md#create-table) command. Let's create a table +using `nyc.taxis` where `nyc` is the database name and `taxis` is the table name. +```sql +CREATE DATABASE nyc; +``` +```sql +CREATE TABLE nyc.taxis +( + trip_id bigint, + trip_distance float, + fare_amount double, + store_and_fwd_flag string +) +PARTITIONED BY (vendor_id bigint) STORED BY ICEBERG; +``` +Iceberg catalogs support the full range of SQL DDL commands, including: + +* [`CREATE TABLE`](docs/latest/hive.md#create-table) +* [`CREATE TABLE AS SELECT`](docs/latest/hive.md#create-table-as-select) +* [`CREATE TABLE LIKE TABLE`](docs/latest/hive.md#create-table-like-table) +* [`ALTER TABLE`](docs/latest/hive.md#alter-table) +* [`DROP TABLE`](docs/latest/hive.md#drop-table) + +### Writing Data to a Table + +After your table is created, you can insert records. +```sql +INSERT INTO nyc.taxis +VALUES (1000371, 1.8, 15.32, 'N', 1), (1000372, 2.5, 22.15, 'N', 2), (1000373, 0.9, 9.01, 'N', 2), (1000374, 8.4, 42.13, 'Y', 1); +``` + +### Reading Data from a Table + +To read a table, simply use the Iceberg table's name. +```sql +SELECT * FROM nyc.taxis; +``` + +### Next steps + +#### Adding Iceberg to Hive + +If you already have a Hive 4.0.0-alpha-1, or later, environment, it comes with the Iceberg 0.13.1 included. No additional downloads or jars are needed. If you have a Hive 2.3.x or Hive 3.1.x environment see [Enabling Iceberg support in Hive](docs/latest/hive.md#enabling-iceberg-support-in-hive). + +#### Learn More + +To learn more about setting up a database other than Derby, see [Apache Hive Quick Start](https://hive.apache.org/developement/quickstart/). You can also [set up a standalone metastore, HS2 and Postgres](https://github.com/apache/hive/blob/master/packaging/src/docker/docker-compose.yml). Now that you're up and running with Iceberg and Hive, check out the [Iceberg-Hive docs](docs/latest/hive.md) to learn more! diff --git a/site/docs/how-to-release.md b/site/docs/how-to-release.md new file mode 100644 index 000000000000..e2d9ae4ceec3 --- /dev/null +++ b/site/docs/how-to-release.md @@ -0,0 +1,516 @@ +--- +title: "How To Release" +--- + + +## Introduction + +This page walks you through the release process of the Iceberg project. [Here](https://www.apache.org/legal/release-policy.html) you can read about the release process in general for an Apache project. + +Decisions about releases are made by three groups: + +* Release Manager: Does the work of creating the release, signing it, counting [votes](#voting), announcing the release and so on. Requires the assistance of a committer for some steps. +* The community: Performs the discussion of whether it is the right time to create a release and what that release should contain. The community can also cast non-binding votes on the release. +* PMC: Gives binding votes on the release. + +This page describes the procedures that the release manager and voting PMC members take during the release process. + +## Setup + +To create a release candidate, you will need: + +* Apache LDAP credentals for Nexus and SVN +* A [GPG key for signing](https://www.apache.org/dev/release-signing#generate), published in [KEYS](https://dist.apache.org/repos/dist/dev/iceberg/KEYS) + +If you have not published your GPG key yet, you must publish it before sending the vote email by doing: + +```shell +svn co https://dist.apache.org/repos/dist/dev/iceberg icebergsvn +cd icebergsvn +echo "" >> KEYS # append a newline +gpg --list-sigs >> KEYS # append signatures +gpg --armor --export >> KEYS # append public key block +svn commit -m "add key for " +``` + +### Nexus access + +Nexus credentials are configured in your personal `~/.gradle/gradle.properties` file using `mavenUser` and `mavenPassword`: + +``` +mavenUser=yourApacheID +mavenPassword=SomePassword +``` + +### PGP signing + +The release scripts use the command-line `gpg` utility so that signing can use the gpg-agent and does not require writing your private key's passphrase to a configuration file. + +To configure gradle to sign convenience binary artifacts, add the following settings to `~/.gradle/gradle.properties`: + +``` +signing.gnupg.keyName=Your Name (CODE SIGNING KEY) +``` + +To use `gpg` instead of `gpg2`, also set `signing.gnupg.executable=gpg` + +For more information, see the Gradle [signing documentation](https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials). + +### Apache repository + +The release should be executed against `https://github.com/apache/iceberg.git` instead of any fork. +Set it as remote with name `apache` for release if it is not already set up. + +## Creating a release candidate + +### Initiate a discussion about the release with the community + +This step can be useful to gather ongoing patches that the community thinks should be in the upcoming release. + +The communication can be started via a [DISCUSS] mail on the dev@ channel and the desired tickets can be added to the github milestone of the next release. + +Note, creating a milestone in github requires a committer. However, a non-committer can assign tasks to a milestone if added to the list of collaborators in [.asf.yaml](https://github.com/apache/iceberg/blob/master/.asf.yaml) + +The release status is discussed during each community sync meeting. Release manager should join the meeting to report status and discuss any release blocker. + +### Build the source release + +To create the source release artifacts, run the `source-release.sh` script with the release version and release candidate number: + +```bash +dev/source-release.sh -v 0.13.0 -r 0 -k +``` + +Example console output: + +```text +Preparing source for apache-iceberg-0.13.0-rc1 +Adding version.txt and tagging release... +[master ca8bb7d0] Add version.txt for release 0.13.0 + 1 file changed, 1 insertion(+) + create mode 100644 version.txt +Pushing apache-iceberg-0.13.0-rc1 to origin... +Enumerating objects: 5, done. +Counting objects: 100% (5/5), done. +Delta compression using up to 12 threads +Compressing objects: 100% (3/3), done. +Writing objects: 100% (4/4), 433 bytes | 433.00 KiB/s, done. +Total 4 (delta 1), reused 0 (delta 0) +remote: Resolving deltas: 100% (1/1), completed with 1 local object. +To https://github.com/apache/iceberg.git + * [new tag] apache-iceberg-0.13.0-rc1 -> apache-iceberg-0.13.0-rc1 +Creating tarball using commit ca8bb7d0821f35bbcfa79a39841be8fb630ac3e5 +Signing the tarball... +Checking out Iceberg RC subversion repo... +Checked out revision 52260. +Adding tarball to the Iceberg distribution Subversion repo... +A tmp/apache-iceberg-0.13.0-rc1 +A tmp/apache-iceberg-0.13.0-rc1/apache-iceberg-0.13.0.tar.gz.asc +A (bin) tmp/apache-iceberg-0.13.0-rc1/apache-iceberg-0.13.0.tar.gz +A tmp/apache-iceberg-0.13.0-rc1/apache-iceberg-0.13.0.tar.gz.sha512 +Adding tmp/apache-iceberg-0.13.0-rc1 +Adding (bin) tmp/apache-iceberg-0.13.0-rc1/apache-iceberg-0.13.0.tar.gz +Adding tmp/apache-iceberg-0.13.0-rc1/apache-iceberg-0.13.0.tar.gz.asc +Adding tmp/apache-iceberg-0.13.0-rc1/apache-iceberg-0.13.0.tar.gz.sha512 +Transmitting file data ...done +Committing transaction... +Committed revision 52261. +Creating release-announcement-email.txt... +Success! The release candidate is available here: + https://dist.apache.org/repos/dist/dev/iceberg/apache-iceberg-0.13.0-rc1 + +Commit SHA1: ca8bb7d0821f35bbcfa79a39841be8fb630ac3e5 + +We have generated a release announcement email for you here: +/Users/jackye/iceberg/release-announcement-email.txt + +Please note that you must update the Nexus repository URL +contained in the mail before sending it out. +``` + +The source release script will create a candidate tag based on the HEAD revision in git and will prepare the release tarball, signature, and checksum files. It will also upload the source artifacts to SVN. + +Note the commit SHA1 and candidate location because those will be added to the vote thread. + +Once the source release is ready, use it to stage convenience binary artifacts in Nexus. + +### Build and stage convenience binaries + +Convenience binaries are created using the source release tarball from in the last step. + +Untar the source release and go into the release directory: + +```bash +tar xzf apache-iceberg-0.13.0.tar.gz +cd apache-iceberg-0.13.0 +``` + +To build and publish the convenience binaries, run the `dev/stage-binaries.sh` script. This will push to a release staging repository. + +Disable gradle parallelism by setting `org.gradle.parallel=false` in `gradle.properties`. + +``` +dev/stage-binaries.sh +``` + +Next, you need to close the staging repository: + +1. Go to [Nexus](https://repository.apache.org/) and log in +2. In the menu on the left, choose "Staging Repositories" +3. Select the Iceberg repository + * If multiple staging repositories are created after running the script, verify that gradle parallelism is disabled and try again. +4. At the top, select "Close" and follow the instructions + * In the comment field use "Apache Iceberg <version> RC<num>" + +### Start a VOTE thread + +The last step for a candidate is to create a VOTE thread on the dev mailing list. +The email template is already generated in `release-announcement-email.txt` with some details filled. + +Example title subject: + +```text +[VOTE] Release Apache Iceberg RC +``` + +Example content: + +```text +Hi everyone, + +I propose the following RC to be released as official Apache Iceberg release. + +The commit id is +* This corresponds to the tag: apache-iceberg--rc +* https://github.com/apache/iceberg/commits/apache-iceberg--rc +* https://github.com/apache/iceberg/tree/ + +The release tarball, signature, and checksums are here: +* https://dist.apache.org/repos/dist/dev/iceberg/apache-iceberg--rc/ + +You can find the KEYS file here: +* https://dist.apache.org/repos/dist/dev/iceberg/KEYS + +Convenience binary artifacts are staged in Nexus. The Maven repository URL is: +* https://repository.apache.org/content/repositories/orgapacheiceberg-/ + +This release includes important changes that I should have summarized here, but I'm lazy. + +Please download, verify, and test. + +Please vote in the next 72 hours. (Weekends excluded) + +[ ] +1 Release this as Apache Iceberg +[ ] +0 +[ ] -1 Do not release this because... + +Only PMC members have binding votes, but other community members are encouraged to cast +non-binding votes. This vote will pass if there are 3 binding +1 votes and more binding ++1 votes than -1 votes. +``` + +When a candidate is passed or rejected, reply with the voting result: + +```text +Subject: [RESULT][VOTE] Release Apache Iceberg RC +``` + +```text +Thanks everyone who participated in the vote for Release Apache Iceberg RC. + +The vote result is: + ++1: 3 (binding), 5 (non-binding) ++0: 0 (binding), 0 (non-binding) +-1: 0 (binding), 0 (non-binding) + +Therefore, the release candidate is passed/rejected. +``` + + +### Finishing the release + +After the release vote has passed, you need to release the last candidate's artifacts. + +But note that releasing the artifacts should happen around the same time the new docs are released +so make sure the [documentation changes](#documentation-release) +are prepared when going through the below steps. + +#### Publishing the release + +First, copy the source release directory to releases: + +```bash +mkdir iceberg +cd iceberg +svn co https://dist.apache.org/repos/dist/dev/iceberg candidates +svn co https://dist.apache.org/repos/dist/release/iceberg releases +cp -r candidates/apache-iceberg--rcN/ releases/apache-iceberg- +cd releases +svn add apache-iceberg- +svn ci -m 'Iceberg: Add release ' +``` + +!!! Note +The above step requires PMC privileges to execute. + +Next, add a release tag to the git repository based on the passing candidate tag: + +```bash +git tag -am 'Release Apache Iceberg ' apache-iceberg- apache-iceberg--rcN +``` + +Then release the candidate repository in [Nexus](https://repository.apache.org/#stagingRepositories). + +#### Announcing the release + +To announce the release, wait until Maven central has mirrored the Apache binaries, then update the Iceberg site and send an announcement email: + +```text +[ANNOUNCE] Apache Iceberg release +``` +```text +I'm pleased to announce the release of Apache Iceberg ! + +Apache Iceberg is an open table format for huge analytic datasets. Iceberg +delivers high query performance for tables with tens of petabytes of data, +along with atomic commits, concurrent writes, and SQL-compatible table +evolution. + +This release can be downloaded from: https://www.apache.org/dyn/closer.cgi/iceberg// + +Java artifacts are available from Maven Central. + +Thanks to everyone for contributing! +``` + +#### Update revapi + +Create a PR in the `iceberg` repo to make revapi run on the new release. For an example see [this PR](https://github.com/apache/iceberg/pull/6275). + +#### Update github issue template + +Create a PR in the `iceberg` repo to add the new version to the github issue template. For an example see [this PR](https://github.com/apache/iceberg/pull/6287). + +### Documentation Release + +Documentation needs to be updated as a part of an Iceberg release after a release candidate is passed. +The commands described below assume you are in a directory containing a local clone of the `iceberg-docs` +repository and `iceberg` repository. Adjust the commands accordingly if it is not the case. Note that all +changes in `iceberg` need to happen against the `master` branch and changes in `iceberg-docs` need to happen +against the `main` branch. + +#### Common documentation update + +1. To start the release process, run the following steps in the `iceberg-docs` repository to copy docs over: +```shell +cp -r ../iceberg/format/* ../iceberg-docs/landing-page/content/common/ +``` +2. Change into the `iceberg-docs` repository and create a branch. +```shell +cd ../iceberg-docs +git checkout -b +``` +3. Commit, push, and open a PR against the `iceberg-docs` repo (`` -> `main`) + +#### Versioned documentation update + +Once the common docs changes have been merged into `main`, the next step is to update the versioned docs. + +1. In the `iceberg-docs` repository, cut a new branch using the version number as the branch name +```shell +cd ../iceberg-docs +git checkout -b +git push --set-upstream apache +``` +2. Copy the versioned docs from the `iceberg` repo into the `iceberg-docs` repo +```shell +rm -rf ../iceberg-docs/docs/content +cp -r ../iceberg/docs ../iceberg-docs/docs/content +``` +3. Commit the changes and open a PR against the `` branch in the `iceberg-docs` repo + +#### Javadoc update + +In the `iceberg` repository, generate the javadoc for your release and copy it to the `javadoc` folder in `iceberg-docs` repo: +```shell +cd ../iceberg +./gradlew refreshJavadoc +rm -rf ../iceberg-docs/javadoc +cp -r site/docs/javadoc/ ../iceberg-docs/javadoc +``` + +This resulted changes in `iceberg-docs` should be approved in a separate PR. + +#### Update the latest branch + +Since `main` is currently the same as the version branch, one needs to rebase `latest` branch against `main`: + +```shell +git checkout latest +git rebase main +git push apache latest +``` + +#### Set latest version in iceberg-docs repo + +The last step is to update the `main` branch in `iceberg-docs` to set the latest version. +A PR needs to be published in the `iceberg-docs` repository with the following changes: +1. Update variable `latestVersions.iceberg` to the new release version in `landing-page/config.toml` +2. Update variable `latestVersions.iceberg` to the new release version and +`versions.nessie` to the version of `org.projectnessie.nessie:*` from [versions.props](https://github.com/apache/iceberg/blob/master/versions.props) in `docs/config.toml` +3. Update list `versions` with the new release in `landing-page/config.toml` +4. Update list `versions` with the new release in `docs/config.toml` +5. Mark the current latest release notes to past releases under `landing-page/content/common/release-notes.md` +6. Add release notes for the new release version in `landing-page/content/common/release-notes.md` + +# How to Verify a Release + +Each Apache Iceberg release is validated by the community by holding a vote. A community release manager +will prepare a release candidate and call a vote on the Iceberg +[dev list](community.md#mailing-lists). +To validate the release candidate, community members will test it out in their downstream projects and environments. +It's recommended to report the Java, Scala, Spark, Flink and Hive versions you have tested against when you vote. + +In addition to testing in downstream projects, community members also check the release's signatures, checksums, and +license documentation. + +## Validating a source release candidate + +Release announcements include links to the following: + +- **A source tarball** +- **A signature (.asc)** +- **A checksum (.sha512)** +- **KEYS file** +- **GitHub change comparison** + +After downloading the source tarball, signature, checksum, and KEYS file, here are instructions on how to +verify signatures, checksums, and documentation. + +### Verifying Signatures + +First, import the keys. +```bash +curl https://dist.apache.org/repos/dist/dev/iceberg/KEYS -o KEYS +gpg --import KEYS +``` + +Next, verify the `.asc` file. +```bash +gpg --verify apache-iceberg-{{ icebergVersion }}.tar.gz.asc +``` + +### Verifying Checksums + +```bash +shasum -a 512 --check apache-iceberg-{{ icebergVersion }}.tar.gz.sha512 +``` + +### Verifying License Documentation + +Untar the archive and change into the source directory. +```bash +tar xzf apache-iceberg-{{ icebergVersion }}.tar.gz +cd apache-iceberg-{{ icebergVersion }} +``` + +Run RAT checks to validate license headers. +```bash +dev/check-license +``` + +### Verifying Build and Test + +To verify that the release candidate builds properly, run the following command. +```bash +./gradlew build +``` + +## Testing release binaries + +Release announcements will also include a maven repository location. You can use this +location to test downstream dependencies by adding it to your maven or gradle build. + +To use the release in your maven build, add the following to your `POM` or `settings.xml`: +```xml +... + + + iceberg-release-candidate + Iceberg Release Candidate + ${MAVEN_URL} + + +... +``` + +To use the release in your gradle build, add the following to your `build.gradle`: +```groovy +repositories { + mavenCentral() + maven { + url "${MAVEN_URL}" + } +} +``` + +!!! Note +Replace `${MAVEN_URL}` with the URL provided in the release announcement + +### Verifying with Spark + +To verify using spark, start a `spark-shell` with a command like the following command (use the appropriate +spark-runtime jar for the Spark installation): +```bash +spark-shell \ + --conf spark.jars.repositories=${MAVEN_URL} \ + --packages org.apache.iceberg:iceberg-spark3-runtime:{{ icebergVersion }} \ + --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \ + --conf spark.sql.catalog.local=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.local.type=hadoop \ + --conf spark.sql.catalog.local.warehouse=${LOCAL_WAREHOUSE_PATH} \ + --conf spark.sql.catalog.local.default-namespace=default \ + --conf spark.sql.defaultCatalog=local +``` + +### Verifying with Flink + +To verify using Flink, start a Flink SQL Client with the following command: +```bash +wget ${MAVEN_URL}/iceberg-flink-runtime/{{ icebergVersion }}/iceberg-flink-runtime-{{ icebergVersion }}.jar + +sql-client.sh embedded \ + -j iceberg-flink-runtime-{{ icebergVersion }}.jar \ + -j ${FLINK_CONNECTOR_PACKAGE}-${HIVE_VERSION}_${SCALA_VERSION}-${FLINK_VERSION}.jar \ + shell +``` + +## Voting + +Votes are cast by replying to the release candidate announcement email on the dev mailing list +with either `+1`, `0`, or `-1`. + +> [ ] +1 Release this as Apache Iceberg {{ icebergVersion }} +[ ] +0 +[ ] -1 Do not release this because... + +In addition to your vote, it's customary to specify if your vote is binding or non-binding. Only members +of the Project Management Committee have formally binding votes. If you're unsure, you can specify that your +vote is non-binding. To read more about voting in the Apache framework, checkout the +[Voting](https://www.apache.org/foundation/voting.html) information page on the Apache foundation's website. diff --git a/site/docs/index.md b/site/docs/index.md new file mode 100644 index 000000000000..287452fd7df3 --- /dev/null +++ b/site/docs/index.md @@ -0,0 +1,108 @@ +--- +title: "Intro to Apache Iceberg" +--- + + + + + +

+
+
+
+ +## Apache Iceberg + +### The open table format for analytic datasets. + +* [Community](community.md) +* [GitHub](https://github.com/apache/iceberg) +* [Slack](https://github.com/apache/iceberg) + +## What is Iceberg? + +Iceberg is a high-performance format for huge analytic tables. Iceberg brings the reliability and simplicity of SQL tables to big data, while making it possible for engines like Spark, Trino, Flink, Presto, Hive and Impala to safely work with the same tables, at the same time. + +[Learn More]('https://iceberg.apache.org/getting-started') + +## Expressive SQL + +Iceberg supports flexible SQL commands to merge new data, update existing rows, and perform targeted deletes. Iceberg can eagerly rewrite data files for read performance, or it can use delete deltas for faster updates. +[Learn More]('https://iceberg.apache.org/docs/latest/spark-writes/') + +
+
MERGE INTO prod.nyc.taxis ptUSING (SELECT * FROM staging.nyc.taxis) stON pt.id = st.idWHEN NOT MATCHED THEN INSERT *;Done! +
+
+ +## Full Schema Evolution + +Schema evolution just works. Adding a column won't bring back "zombie"🧟 data. Columns can be renamed and reordered. Best of all, schema changes never require rewriting your table. + +[Learn More]('https://iceberg.apache.org/docs/latest/evolution/') + +
+
ALTER TABLE taxisALTER COLUMN trip_distanceTYPE double;Done!ALTER TABLE taxisALTER COLUMN trip_distanceAFTER fare;Done!ALTER TABLE taxisRENAME COLUMN trip_distanceTO distance;Done! +
+
+ +## Hidden Partitioning + +Iceberg handles the tedious and error-prone task of producing partition values for rows in a table and skips unnecessary partitions and files automatically. No extra filters are needed for fast queries, and table layout can be updated as data or queries change. + +[Learn More]('https://iceberg.apache.org/docs/latest/partitioning/#icebergs-hidden-partitioning') + +
+
+ + +
+
+ +## Time Travel and Rollback + +Time-travel enables reproducible queries that use exactly the same table snapshot, or lets users easily examine changes. Version rollback allows users to quickly correct problems by resetting tables to a good state. +[Learn More]('https://iceberg.apache.org/docs/latest/spark-queries/#time-travel') + +
+
SELECT count(*) FROM nyc.taxis2,853,020SELECT count(*) FROM nyc.taxis FOR VERSION AS OF 21884653078355854432,798,371SELECT count(*) FROM nyc.taxis FOR TIMESTAMP AS OF TIMESTAMP '2022-01-01 00:00:00.000000 Z'2,798,371 +
+
+ +## Data Compaction + +Data compaction is supported out-of-the-box and you can choose from different rewrite strategies such as bin-packing or sorting to optimize file layout and size. + +
+
CALL system.rewrite_data_files("nyc.taxis"); +
+
+ + diff --git a/site/docs/multi-engine-support.md b/site/docs/multi-engine-support.md new file mode 100644 index 000000000000..75477ffc7106 --- /dev/null +++ b/site/docs/multi-engine-support.md @@ -0,0 +1,116 @@ +--- +title: "Multi-Engine Support" +--- + + +# Multi-Engine Support + +Apache Iceberg is an open standard for huge analytic tables that can be used by any processing engine. +The community continuously improves Iceberg core library components to enable integrations with different compute engines that power analytics, business intelligence, machine learning, etc. +Connectors for Spark, Flink and Hive are maintained in the main Iceberg repository. + +## Multi-Version Support + +Processing engine connectors maintained in the iceberg repository are built for multiple versions. + +For Spark and Flink, each new version that introduces backwards incompatible upgrade has its dedicated integration codebase and release artifacts. +For example, the code for Iceberg Spark 3.1 integration is under `/spark/v3.1` and the code for Iceberg Spark 3.2 integration is under `/spark/v3.2`. +Different artifacts (`iceberg-spark-3.1_2.12` and `iceberg-spark-3.2_2.12`) are released for users to consume. +By doing this, changes across versions are isolated. +New features in Iceberg could be developed against the latest features of an engine without breaking support of old APIs in past engine versions. + +For Hive, Hive 2 uses the `iceberg-mr` package for Iceberg integration, and Hive 3 requires an additional dependency of the `iceberg-hive3` package. + +### Runtime Jar + +Iceberg provides a runtime connector jar for each supported version of Spark, Flink and Hive. +When using Iceberg with these engines, the runtime jar is the only addition to the classpath needed in addition to vendor dependencies. +For example, to use Iceberg with Spark 3.2 and AWS integrations, `iceberg-spark-runtime-3.2_2.12` and AWS SDK dependencies are needed for the Spark installation. + +Spark and Flink provide different runtime jars for each supported engine version. +Hive 2 and Hive 3 currently share the same runtime jar. +The runtime jar names and latest version download links are listed in [the tables below](#current-engine-version-lifecycle-status). + +### Engine Version Lifecycle + +Each engine version undergoes the following lifecycle stages: + +1. **Beta**: a new engine version is supported, but still in the experimental stage. Maybe the engine version itself is still in preview (e.g. Spark `3.0.0-preview`), or the engine does not yet have full feature compatibility compared to old versions yet. This stage allows Iceberg to release an engine version support without the need to wait for feature parity, shortening the release time. +2. **Maintained**: an engine version is actively maintained by the community. Users can expect parity for most features across all the maintained versions. If a feature has to leverage some new engine functionalities that older versions don't have, then feature parity across maintained versions is not guaranteed. +3. **Deprecated**: an engine version is no longer actively maintained. People who are still interested in the version can backport any necessary feature or bug fix from newer versions, but the community will not spend effort in achieving feature parity. Iceberg recommends users to move towards a newer version. Contributions to a deprecated version is expected to diminish over time, so that eventually no change is added to a deprecated version. +4. **End-of-life**: a vote can be initiated in the community to fully remove a deprecated version out of the Iceberg repository to mark as its end of life. + +## Current Engine Version Lifecycle Status + +### Apache Spark + +| Version | Lifecycle Stage | Initial Iceberg Support | Latest Iceberg Support | Latest Runtime Jar | +| ---------- | ------------------ | ----------------------- |------------------------| ------------------ | +| 2.4 | End of Life | 0.7.0-incubating | 1.2.1 | [iceberg-spark-runtime-2.4](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-2.4/1.2.1/iceberg-spark-runtime-2.4-1.2.1.jar) | +| 3.0 | End of Life | 0.9.0 | 1.0.0 | [iceberg-spark-runtime-3.0_2.12](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.0_2.12/1.0.0/iceberg-spark-runtime-3.0_2.12-1.0.0.jar) | +| 3.1 | Deprecated | 0.12.0 | {{ icebergVersion }} | [iceberg-spark-runtime-3.1_2.12](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.1_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.1_2.12-{{ icebergVersion }}.jar) [1] | +| 3.2 | Maintained | 0.13.0 | {{ icebergVersion }} | [iceberg-spark-runtime-3.2_2.12](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.2_2.12-{{ icebergVersion }}.jar) | +| 3.3 | Maintained | 0.14.0 | {{ icebergVersion }} | [iceberg-spark-runtime-3.3_2.12](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.3_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.3_2.12-{{ icebergVersion }}.jar) | +| 3.4 | Maintained | 1.3.0 | {{ icebergVersion }} | [iceberg-spark-runtime-3.4_2.12](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.4_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.4_2.12-{{ icebergVersion }}.jar) | + +* [1] Spark 3.1 shares the same runtime jar `iceberg-spark3-runtime` with Spark 3.0 before Iceberg 0.13.0 + +### Apache Flink + +Based on the guideline of the Flink community, only the latest 2 minor versions are actively maintained. +Users should continuously upgrade their Flink version to stay up-to-date. + +| Version | Lifecycle Stage | Initial Iceberg Support | Latest Iceberg Support | Latest Runtime Jar | +| ------- | --------------- | ----------------------- | ---------------------- | ------------------------------------------------------------ | +| 1.11 | End of Life | 0.9.0 | 0.12.1 | [iceberg-flink-runtime](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime/0.12.1/iceberg-flink-runtime-0.12.1.jar) | +| 1.12 | End of Life | 0.12.0 | 0.13.1 | [iceberg-flink-runtime-1.12](https://repo1.maven.org/maven2/org/apache/iceberg/iceberg-flink-runtime-1.12/0.13.2/iceberg-flink-runtime-1.12-0.13.2.jar) [3] | +| 1.13 | End of Life | 0.13.0 | 1.0.0 | [iceberg-flink-runtime-1.13](https://repo1.maven.org/maven2/org/apache/iceberg/iceberg-flink-runtime-1.13/1.2.0/iceberg-flink-runtime-1.13-1.0.0.jar) | +| 1.14 | End of Life | 0.13.0 | 1.2.0 | [iceberg-flink-runtime-1.14](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.14/1.2.0/iceberg-flink-runtime-1.14-1.2.0.jar) | +| 1.15 | Deprecated | 0.14.0 | {{ icebergVersion }} | [iceberg-flink-runtime-1.15](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.15/{{ icebergVersion }}/iceberg-flink-runtime-1.15-{{ icebergVersion }}.jar) | +| 1.16 | Maintained | 1.1.0 | {{ icebergVersion }} | [iceberg-flink-runtime-1.16](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.16/{{ icebergVersion }}/iceberg-flink-runtime-1.16-{{ icebergVersion }}.jar) | +| 1.17 | Maintained | 1.3.0 | {{ icebergVersion }} | [iceberg-flink-runtime-1.17](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.17/{{ icebergVersion }}/iceberg-flink-runtime-1.17-{{ icebergVersion }}.jar) | + +* [3] Flink 1.12 shares the same runtime jar `iceberg-flink-runtime` with Flink 1.11 before Iceberg 0.13.0 + +### Apache Hive + +| Version | Recommended minor version | Lifecycle Stage | Initial Iceberg Support | Latest Iceberg Support | Latest Runtime Jar | +| -------------- | ------------------------- | ----------------- | ----------------------- | ---------------------- | ------------------ | +| 2 | 2.3.8 | Maintained | 0.8.0-incubating | {{ icebergVersion }} | [iceberg-hive-runtime](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/{{ icebergVersion }}/iceberg-hive-runtime-{{ icebergVersion }}.jar) | +| 3 | 3.1.2 | Maintained | 0.10.0 | {{ icebergVersion }} | [iceberg-hive-runtime](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/{{ icebergVersion }}/iceberg-hive-runtime-{{ icebergVersion }}.jar) | + +## Developer Guide + +### Maintaining existing engine versions + +Iceberg recommends the following for developers who are maintaining existing engine versions: + +1. New features should always be prioritized first in the latest version, which is either a maintained or beta version. +2. For features that could be backported, contributors are encouraged to either perform backports to all maintained versions, or at least create some issues to track the backport. +3. If the change is small enough, updating all versions in a single PR is acceptable. Otherwise, using separated PRs for each version is recommended. + +### Supporting new engines + +Iceberg recommends new engines to build support by importing the Iceberg libraries to the engine's project. +This allows the Iceberg support to evolve with the engine. +Projects such as [Trino](https://trino.io/docs/current/connector/iceberg.html) and [Presto](https://prestodb.io/docs/current/connector/iceberg.html) are good examples of such support strategy. + +In this approach, an Iceberg version upgrade is needed for an engine to consume new Iceberg features. +To facilitate engine development against unreleased Iceberg features, a daily snapshot is published in the [Apache snapshot repository](https://repository.apache.org/content/repositories/snapshots/org/apache/iceberg/). + +If bringing an engine directly to the Iceberg main repository is needed, please raise a discussion thread in the [Iceberg community](community.md). diff --git a/site/docs/puffin-spec.md b/site/docs/puffin-spec.md new file mode 100644 index 000000000000..1fe9e5b9bc56 --- /dev/null +++ b/site/docs/puffin-spec.md @@ -0,0 +1,143 @@ +--- +title: "Puffin Spec" +--- + + +# Puffin file format + +This is a specification for Puffin, a file format designed to store +information such as indexes and statistics about data managed in an +Iceberg table that cannot be stored directly within the Iceberg manifest. A +Puffin file contains arbitrary pieces of information (here called "blobs"), +along with metadata necessary to interpret them. The blobs supported by Iceberg +are documented at [Blob types](#blob-types). + +## Format specification + +A file conforming to the Puffin file format specification should have the structure +as described below. + +### Versions + +Currently, there is a single version of the Puffin file format, described below. + +### File structure + +The Puffin file has the following structure + +``` +Magic Blob₁ Blob₂ ... Blobₙ Footer +``` + +where + +- `Magic` is four bytes 0x50, 0x46, 0x41, 0x31 (short for: Puffin _Fratercula + arctica_, version 1), +- `Blobᵢ` is i-th blob contained in the file, to be interpreted by application + according to the footer, +- `Footer` is defined below. + +### Footer structure + +Footer has the following structure + +``` +Magic FooterPayload FooterPayloadSize Flags Magic +``` + +where + +- `Magic`: four bytes, same as at the beginning of the file +- `FooterPayload`: optionally compressed, UTF-8 encoded JSON payload describing the + blobs in the file, with the structure described below +- `FooterPayloadSize`: a length in bytes of the `FooterPayload` (after compression, + if compressed), stored as 4 byte integer +- `Flags`: 4 bytes for boolean flags + - byte 0 (first) + - bit 0 (lowest bit): whether `FooterPayload` is compressed + - all other bits are reserved for future use and should be set to 0 on write + - all other bytes are reserved for future use and should be set to 0 on write + +A 4 byte integer is always signed, in a two's complement representation, stored +little-endian. + +### Footer Payload + +Footer payload bytes is either uncompressed or LZ4-compressed (as a single +[LZ4 compression frame](https://github.com/lz4/lz4/blob/77d1b93f72628af7bbde0243b4bba9205c3138d9/doc/lz4_Frame_format.md) +with content size present), UTF-8 encoded JSON payload representing a single +`FileMetadata` object. + +#### FileMetadata + +`FileMetadata` has the following fields + + +| Field Name | Field Type | Required | Description | +| ---------- | --------------------------------------- | -------- | ----------- | +| blobs | list of BlobMetadata objects | yes | +| properties | JSON object with string property values | no | storage for arbitrary meta-information, like writer identification/version. See [Common properties](#common-properties) for properties that are recommended to be set by a writer. + +#### BlobMetadata + +`BlobMetadata` has the following fields + +| Field Name | Field Type | Required | Description | +|-------------------|-----------------------------------------|----------| ----------- | +| type | JSON string | yes | See [Blob types](#blob-types) +| fields | JSON list of ints | yes | List of field IDs the blob was computed for; the order of items is used to compute sketches stored in the blob. +| snapshot-id | JSON long | yes | ID of the Iceberg table's snapshot the blob was computed from. +| sequence-number | JSON long | yes | Sequence number of the Iceberg table's snapshot the blob was computed from. +| offset | JSON long | yes | The offset in the file where the blob contents start +| length | JSON long | yes | The length of the blob stored in the file (after compression, if compressed) +| compression-codec | JSON string | no | See [Compression codecs](#compression-codecs). If omitted, the data is assumed to be uncompressed. +| properties | JSON object with string property values | no | storage for arbitrary meta-information about the blob + +### Blob types + +The blobs can be of a type listed below + +#### `apache-datasketches-theta-v1` blob type + +A serialized form of a "compact" Theta sketch produced by the [Apache +DataSketches](https://datasketches.apache.org/) library. The sketch is obtained by +constructing Alpha family sketch with default seed, and feeding it with individual +distinct values converted to bytes using Iceberg's single-value serialization. + +The blob metadata for this blob may include following properties: + +- `ndv`: estimate of number of distinct values, derived from the sketch. + +### Compression codecs + +The data can also be uncompressed. If it is compressed the codec should be one of +codecs listed below. For maximal interoperability, other codecs are not supported. + +| Codec name | Description | +|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| lz4 | Single [LZ4 compression frame](https://github.com/lz4/lz4/blob/77d1b93f72628af7bbde0243b4bba9205c3138d9/doc/lz4_Frame_format.md), with content size present | +| zstd | Single [Zstandard compression frame](https://github.com/facebook/zstd/blob/8af64f41161f6c2e0ba842006fe238c664a6a437/doc/zstd_compression_format.md#zstandard-frames), with content size present | +__ + +### Common properties + +When writing a Puffin file it is recommended to set the following fields in the +[FileMetadata](#filemetadata)'s `properties` field. + +- `created-by` - human-readable identification of the application writing the file, + along with its version. Example "Trino version 381". diff --git a/site/docs/releases.md b/site/docs/releases.md new file mode 100644 index 000000000000..264773b46dc7 --- /dev/null +++ b/site/docs/releases.md @@ -0,0 +1,777 @@ +--- +title: "Releases" +--- + + +## Downloads + +The latest version of Iceberg is [{{ icebergVersion }}](https://github.com/apache/iceberg/releases/tag/apache-iceberg-{{ icebergVersion }}). + +* [{{ icebergVersion }} source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-{{ icebergVersion }}/apache-iceberg-{{ icebergVersion }}.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-{{ icebergVersion }}/apache-iceberg-{{ icebergVersion }}.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-{{ icebergVersion }}/apache-iceberg-{{ icebergVersion }}.tar.gz.sha512) +* [{{ icebergVersion }} Spark 3.4\_2.12 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.4_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.4_2.12-{{ icebergVersion }}.jar) -- [3.4\_2.13](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.4_2.13/{{ icebergVersion }}/iceberg-spark-runtime-3.4_2.13-{{ icebergVersion }}.jar) +* [{{ icebergVersion }} Spark 3.3\_2.12 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.3_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.3_2.12-{{ icebergVersion }}.jar) -- [3.3\_2.13](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.3_2.13/{{ icebergVersion }}/iceberg-spark-runtime-3.3_2.13-{{ icebergVersion }}.jar) +* [{{ icebergVersion }} Spark 3.2\_2.12 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.2_2.12-{{ icebergVersion }}.jar) -- [3.2\_2.13](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.13/{{ icebergVersion }}/iceberg-spark-runtime-3.2_2.13-{{ icebergVersion }}.jar) +* [{{ icebergVersion }} Spark 3.1 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.1_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.1_2.12-{{ icebergVersion }}.jar) +* [{{ icebergVersion }} Flink 1.17 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.17/{{ icebergVersion }}/iceberg-flink-runtime-1.17-{{ icebergVersion }}.jar) +* [{{ icebergVersion }} Flink 1.16 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.16/{{ icebergVersion }}/iceberg-flink-runtime-1.16-{{ icebergVersion }}.jar) +* [{{ icebergVersion }} Flink 1.15 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.15/{{ icebergVersion }}/iceberg-flink-runtime-1.15-{{ icebergVersion }}.jar) +* [{{ icebergVersion }} Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/{{ icebergVersion }}/iceberg-hive-runtime-{{ icebergVersion }}.jar) + +To use Iceberg in Spark or Flink, download the runtime JAR for your engine version and add it to the jars folder of your installation. + +To use Iceberg in Hive 2 or Hive 3, download the Hive runtime JAR and add it to Hive using `ADD JAR`. + +### Gradle + +To add a dependency on Iceberg in Gradle, add the following to `build.gradle`: + +``` +dependencies { + compile 'org.apache.iceberg:iceberg-core:{{ icebergVersion }}' +} +``` + +You may also want to include `iceberg-parquet` for Parquet file support. + +### Maven + +To add a dependency on Iceberg in Maven, add the following to your `pom.xml`: + +``` + + ... + + org.apache.iceberg + iceberg-core + {{ icebergVersion }} + + ... + +``` + +## 1.3.1 release + +Apache Iceberg 1.3.1 was released on July 25, 2023. +The 1.3.1 release addresses various issues identified in the 1.3.0 release. + +* Core + - Table Metadata parser now accepts null for fields: current-snapshot-id, properties, and snapshots ([\#8064](https://github.com/apache/iceberg/pull/8064)) +* Hive + - Fix HiveCatalog deleting metadata on failures in checking lock status ([\#7931](https://github.com/apache/iceberg/pull/7931)) +* Spark + - Fix RewritePositionDeleteFiles failure for certain partition types ([\#8059](https://github.com/apache/iceberg/pull/8059)) + - Fix RewriteDataFiles concurrency edge-case on commit timeouts ([\#7933](https://github.com/apache/iceberg/pull/7933)) + - Fix partition-level DELETE operations for WAP branches ([\#7900](https://github.com/apache/iceberg/pull/7900)) +* Flink + - FlinkCatalog creation no longer creates the default database ([\#8039](https://github.com/apache/iceberg/pull/8039)) + +## Past releases + +### 1.3.0 release + +Apache Iceberg 1.3.0 was released on May 30th, 2023. +The 1.3.0 release adds a variety of new features and bug fixes. + +* Core + - Expose file and data sequence numbers in ContentFile ([\#7555](https://github.com/apache/iceberg/pull/7555)) + - Improve bit density in object storage layout ([\#7128](https://github.com/apache/iceberg/pull/7128)) + - Store split offsets for delete files ([\#7011](https://github.com/apache/iceberg/pull/7011)) + - Readable metrics in entries metadata table ([\#7539](https://github.com/apache/iceberg/pull/7539)) + - Delete file stats in partitions metadata table ([\#6661](https://github.com/apache/iceberg/pull/6661)) + - Optimized vectorized reads for Parquet Decimal ([\#3249](https://github.com/apache/iceberg/pull/3249)) + - Vectorized reads for Parquet INT96 timestamps in imported data ([\#6962](https://github.com/apache/iceberg/pull/6962)) + - Support selected vector with ORC row and batch readers ([\#7197](https://github.com/apache/iceberg/pull/7197)) + - Clean up expired metastore clients ([\#7310](https://github.com/apache/iceberg/pull/7310)) + - Support for deleting old partition spec columns in V1 tables ([\#7398](https://github.com/apache/iceberg/pull/7398)) +* Spark + - Initial support for Spark 3.4 + - Removed integration for Spark 2.4 + - Support for storage-partitioned joins with mismatching keys in Spark 3.4 (MERGE commands) ([\#7424](https://github.com/apache/iceberg/pull/7424)) + - Support for TimestampNTZ in Spark 3.4 ([\#7553](https://github.com/apache/iceberg/pull/7553)) + - Ability to handle skew during writes in Spark 3.4 ([\#7520](https://github.com/apache/iceberg/pull/7520)) + - Ability to coalesce small tasks during writes in Spark 3.4 ([\#7532](https://github.com/apache/iceberg/pull/7532)) + - Distribution and ordering enhancements in Spark 3.4 ([\#7637](https://github.com/apache/iceberg/pull/7637)) + - Action for rewriting position deletes ([\#7389](https://github.com/apache/iceberg/pull/7389)) + - Procedure for rewriting position deletes ([\#7572](https://github.com/apache/iceberg/pull/7572)) + - Avoid local sort for MERGE cardinality check ([\#7558](https://github.com/apache/iceberg/pull/7558)) + - Support for rate limits in Structured Streaming ([\#4479](https://github.com/apache/iceberg/pull/4479)) + - Read and write support for UUIDs ([\#7399](https://github.com/apache/iceberg/pull/7399)) + - Concurrent compaction is enabled by default ([\#6907](https://github.com/apache/iceberg/pull/6907)) + - Support for metadata columns in changelog tables ([\#7152](https://github.com/apache/iceberg/pull/7152)) + - Add file group failure info for data compaction ([\#7361](https://github.com/apache/iceberg/pull/7361)) +* Flink + - Initial support for Flink 1.17 + - Removed integration for Flink 1.14 + - Data statistics operator to collect traffic distribution for guiding smart shuffling ([\#6382](https://github.com/apache/iceberg/pull/6382)) + - Data statistics operator sends local data statistics to coordinator and receives aggregated data statistics from coordinator for smart shuffling ([\#7269](https://github.com/apache/iceberg/pull/7269)) + - Exposed write parallelism in SQL hints ([\#7039](https://github.com/apache/iceberg/pull/7039)) + - Row-level filtering ([\#7109](https://github.com/apache/iceberg/pull/7109)) + - Use starting sequence number by default when rewriting data files ([\#7218](https://github.com/apache/iceberg/pull/7218)) + - Config for max allowed consecutive planning failures in IcebergSource before failing the job ([\#7571](https://github.com/apache/iceberg/pull/7571)) +* Vendor Integrations + - AWS: Use Apache HTTP client as default AWS HTTP client ([\#7119](https://github.com/apache/iceberg/pull/7119)) + - AWS: Prevent token refresh scheduling on every sign request ([\#7270](https://github.com/apache/iceberg/pull/7270)) + - AWS: Disable local credentials if remote signing is enabled ([\#7230](https://github.com/apache/iceberg/pull/7230)) +* Dependencies + - Bump Arrow to 12.0.0 + - Bump ORC to 1.8.3 + - Bump Parquet to 1.13.1 + - Bump Nessie to 0.59.0 + +### 1.2.1 release + +Apache Iceberg 1.2.1 was released on April 11th, 2023. +The 1.2.1 release is a patch release to address various issues identified in the prior release. +Here is an overview: + +* CORE + - REST: fix previous locations for refs-only load [\#7284](https://github.com/apache/iceberg/pull/7284) + - Parse snapshot-id as long in remove-statistics update [\#7235](https://github.com/apache/iceberg/pull/7235) +* Spark + - Broadcast table instead of file IO in rewrite manifests [\#7263](https://github.com/apache/iceberg/pull/7263) + - Revert "Spark: Add "Iceberg" prefix to SparkTable name string for SparkUI [\#7273](https://github.com/apache/iceberg/pull/7273) +* AWS + - Make AuthSession cache static [\#7289](https://github.com/apache/iceberg/pull/7289) + - Abort S3 input stream on close if not EOS [\#7262](https://github.com/apache/iceberg/pull/7262) + - Disable local credentials if remote signing is enabled [\#7230](https://github.com/apache/iceberg/pull/7230) + - Prevent token refresh scheduling on every sign request [\#7270](https://github.com/apache/iceberg/pull/7270) + - S3 Credentials provider support in DefaultAwsClientFactory [\#7066](https://github.com/apache/iceberg/pull/7066) + +### 1.2.0 release + +Apache Iceberg 1.2.0 was released on March 20th, 2023. +The 1.2.0 release adds a variety of new features and bug fixes. +Here is an overview: + +* Core + - Added AES GCM encrpytion stream spec ([\#5432](https://github.com/apache/iceberg/pull/5432)) + - Added support for Delta Lake to Iceberg table conversion ([\#6449](https://github.com/apache/iceberg/pull/6449), [\#6880](https://github.com/apache/iceberg/pull/6880)) + - Added support for `position_deletes` metadata table ([\#6365](https://github.com/apache/iceberg/pull/6365), [\#6716](https://github.com/apache/iceberg/pull/6716)) + - Added support for scan and commit metrics reporter that is pluggable through catalog ([\#6404](https://github.com/apache/iceberg/pull/6404), [\#6246](https://github.com/apache/iceberg/pull/6246), [\#6410](https://github.com/apache/iceberg/pull/6410)) + - Added support for branch commit for all operations ([\#4926](https://github.com/apache/iceberg/pull/4926), [\#5010](https://github.com/apache/iceberg/pull/5010)) + - Added `FileIO` support for ORC readers and writers ([\#6293](https://github.com/apache/iceberg/pull/6293)) + - Updated all actions to leverage bulk delete whenever possible ([\#6682](https://github.com/apache/iceberg/pull/6682)) + - Updated snapshot ID definition in Puffin spec to support statistics file reuse ([\#6272](https://github.com/apache/iceberg/pull/6267)) + - Added human-readable metrics information in `files` metadata table ([\#5376](https://github.com/apache/iceberg/pull/5376)) + - Fixed incorrect Parquet row group skipping when min and max values are `NaN` ([\#6517](https://github.com/apache/iceberg/pull/6517)) + - Fixed a bug that location provider could generate paths with double slash (`//`) which is not compatible in a Hadoop file system ([\#6777](https://github.com/apache/iceberg/pull/6777)) + - Fixed metadata table time travel failure for tables that performed schema evolution ([\#6980](https://github.com/apache/iceberg/pull/6980)) +* Spark + - Added time range query support for changelog table ([\#6350](https://github.com/apache/iceberg/pull/6350)) + - Added changelog view procedure for v1 table ([\#6012](https://github.com/apache/iceberg/pull/6012)) + - Added support for storage partition joins to improve read and write performance ([\#6371](https://github.com/apache/iceberg/pull/6371)) + - Updated default Arrow environment settings to improve read performance ([\#6550](https://github.com/apache/iceberg/pull/6550)) + - Added aggregate pushdown support for `min`, `max` and `count` to improve read performance ([\#6622](https://github.com/apache/iceberg/pull/6622)) + - Updated default distribution mode settings to improve write performance ([\#6828](https://github.com/apache/iceberg/pull/6828), [\#6838](https://github.com/apache/iceberg/pull/6838)) + - Updated DELETE to perform metadata-only update whenever possible to improve write performance ([\#6899](https://github.com/apache/iceberg/pull/6899)) + - Improved predicate pushdown support for write operations ([\#6636](https://github.com/apache/iceberg/pull/6633)) + - Added support for reading a branch or tag through table identifier and `VERSION AS OF` (a.k.a. `FOR SYSTEM_VERSION AS OF`) SQL syntax ([\#6717](https://github.com/apache/iceberg/pull/6717), [\#6575](https://github.com/apache/iceberg/pull/6575)) + - Added support for writing to a branch through identifier or through write-audit-publish (WAP) workflow settings ([\#6965](https://github.com/apache/iceberg/pull/6965), [\#7050](https://github.com/apache/iceberg/pull/7050)) + - Added DDL SQL extensions to create, replace and drop a branch or tag ([\#6638](https://github.com/apache/iceberg/pull/6638), [\#6637](https://github.com/apache/iceberg/pull/6637), [\#6752](https://github.com/apache/iceberg/pull/6752), [\#6807](https://github.com/apache/iceberg/pull/6807)) + - Added UDFs for `years`, `months`, `days` and `hours` transforms ([\#6207](https://github.com/apache/iceberg/pull/6207), [\#6261](https://github.com/apache/iceberg/pull/6261), [\#6300](https://github.com/apache/iceberg/pull/6300), [\#6339](https://github.com/apache/iceberg/pull/6339)) + - Added partition related stats for `add_files` procedure result ([\#6797](https://github.com/apache/iceberg/pull/6797)) + - Fixed a bug that `rewrite_manifests` procedure produced a new manifest even when there was no rewrite performed ([\#6659](https://github.com/apache/iceberg/pull/6695)) + - Fixed a bug that statistics files were not cleaned up in `expire_snapshots` procedure ([\#6090](https://github.com/apache/iceberg/pull/6090)) +* Flink + - Added support for metadata tables ([\#6222](https://github.com/apache/iceberg/pull/6222)) + - Added support for read options in Flink source ([\#5967](https://github.com/apache/iceberg/pull/5967)) + - Added support for reading and writing Avro `GenericRecord` ([\#6557](https://github.com/apache/iceberg/pull/6557), [\#6584](https://github.com/apache/iceberg/pull/6584)) + - Added support for reading a branch or tag and write to a branch ([\#6660](https://github.com/apache/iceberg/pull/6660), [\#5029](https://github.com/apache/iceberg/pull/5029)) + - Added throttling support for streaming read ([\#6299](https://github.com/apache/iceberg/pull/6299)) + - Added support for multiple sinks for the same table in the same job ([\#6528](https://github.com/apache/iceberg/pull/6528)) + - Fixed a bug that metrics config was not applied to equality and position deletes ([\#6271](https://github.com/apache/iceberg/pull/6271), [\#6313](https://github.com/apache/iceberg/pull/6313)) +* Vendor Integrations + - Added Snowflake catalog integration ([\#6428](https://github.com/apache/iceberg/pull/6428)) + - Added AWS sigV4 authentication support for REST catalog ([\#6951](https://github.com/apache/iceberg/pull/6951)) + - Added support for AWS S3 remote signing ([\#6169](https://github.com/apache/iceberg/pull/6169), [\#6835](https://github.com/apache/iceberg/pull/6835), [\#7080](https://github.com/apache/iceberg/pull/7080)) + - Updated AWS Glue catalog to skip table version archive by default ([\#6919](https://github.com/apache/iceberg/pull/6916)) + - Updated AWS Glue catalog to not require a warehouse location ([\#6586](https://github.com/apache/iceberg/pull/6586)) + - Fixed a bug that a bucket-only AWS S3 location such as `s3://my-bucket` could not be parsed ([\#6352](https://github.com/apache/iceberg/pull/6352)) + - Fixed a bug that unnecessary HTTP client dependencies had to be included to use any AWS integration ([\#6746](https://github.com/apache/iceberg/pull/6746)) + - Fixed a bug that AWS Glue catalog did not respect custom catalog ID when determining default warehouse location ([\#6223](https://github.com/apache/iceberg/pull/6223)) + - Fixes a bug that AWS DynamoDB catalog namespace listing result was incomplete ([\#6823](https://github.com/apache/iceberg/pull/6823)) +* Dependencies + - Upgraded ORC to 1.8.1 ([\#6349](https://github.com/apache/iceberg/pull/6349)) + - Upgraded Jackson to 2.14.1 ([\#6168](https://github.com/apache/iceberg/pull/6168)) + - Upgraded AWS SDK V2 to 2.20.18 ([\#7003](https://github.com/apache/iceberg/pull/7003)) + - Upgraded Nessie to 0.50.0 ([\#6875](https://github.com/apache/iceberg/pull/6875)) + +For more details, please visit [Github](https://github.com/apache/iceberg/releases/tag/apache-iceberg-1.2.0). + + +### 1.1.0 release + +Apache Iceberg 1.1.0 was released on November 28th, 2022. +The 1.1.0 release deprecates various pre-1.0.0 methods, +and adds a variety of new features. +Here is an overview: + +* Core + - Puffin statistics have been [added to the Table API](https://github.com/apache/iceberg/pull/4945) + - Support for [Table scan reporting](https://github.com/apache/iceberg/pull/5268), which enables collection of statistics of the table scans. + - [Add file sequence number to ManifestEntry](https://github.com/apache/iceberg/pull/6002) + - [Support register table](https://github.com/apache/iceberg/pull/5037) for all the catalogs (previously it was only for Hive) + - [Support performing merge appends and delete files on branches](https://github.com/apache/iceberg/pull/5618) + - [Improved Expire Snapshots FileCleanupStrategy](https://github.com/apache/iceberg/pull/5669) + - [SnapshotProducer supports branch writes](https://github.com/apache/iceberg/pull/4926) +* Spark + - [Support for aggregate expressions](https://github.com/apache/iceberg/pull/5961) + - [SparkChangelogTable for querying changelogs](https://github.com/apache/iceberg/pull/5740) + - Dropped support for Apache Spark 3.0 +* Flink + - [FLIP-27 reader is supported in SQL](https://github.com/apache/iceberg/pull/5318) + - Added support for Flink 1.16, dropped support for Flink 1.13 +* Dependencies + - [AWS SDK: 2.17.257](https://github.com/apache/iceberg/pull/5612) + - [Nessie: 0.44](https://github.com/apache/iceberg/pull/6008) + - [Apache ORC: 1.8.0](https://github.com/apache/iceberg/pull/5699) (Also, supports [setting bloom filters on row groups](https://github.com/apache/iceberg/pull/5313/files)) + +For more details, please visit [Github](https://github.com/apache/iceberg/releases/tag/apache-iceberg-1.1.0). + +### 1.0.0 release + +The 1.0.0 release officially guarantees the stability of the Iceberg API. + +Iceberg's API has been largely stable since very early releases and has been integrated with many processing engines, but was still released under a 0.y.z version number indicating that breaking changes may happen. From 1.0.0 forward, the project will follow semver in the public API module, iceberg-api. + +This release removes deprecated APIs that are no longer part of the API. To make transitioning to the new release easier, it is based on the 0.14.1 release with only important bug fixes: + +* Increase metrics limit to 100 columns ([#5933](https://github.com/apache/iceberg/pull/5933)) +* Bump Spark patch versions for CVE-2022-33891 ([#5292](https://github.com/apache/iceberg/pull/5292)) +* Exclude Scala from Spark runtime Jars ([#5884](https://github.com/apache/iceberg/pull/5884)) + +### 0.14.1 release + +This release includes all bug fixes from the 0.14.x patch releases. + +#### Notable bug fixes + +* API + - API: Fix ID assignment in schema merging ([#5395](https://github.com/apache/iceberg/pull/5395)) +* Core + - Core: Fix snapshot log with intermediate transaction snapshots ([#5568](https://github.com/apache/iceberg/pull/5568)) + - Core: Fix exception handling in BaseTaskWriter ([#5683](https://github.com/apache/iceberg/pull/5683)) + - Core: Support deleting tables without metadata files ([#5510](https://github.com/apache/iceberg/pull/5510)) + - Core: Add CommitStateUnknownException handling to REST ([#5694](https://github.com/apache/iceberg/pull/5694)) +* Spark + - Spark: Fix stats in rewrite metadata action ([#5691](https://github.com/apache/iceberg/pull/5691)) +* File Formats + - Parquet: Close zstd input stream early to avoid memory pressure ([#5681](https://github.com/apache/iceberg/pull/5681)) +* Vendor Integrations + - Core, AWS: Fix Kryo serialization failure for FileIO ([#5437](https://github.com/apache/iceberg/pull/5437)) + - AWS: S3OutputStream - failure to close should persist on subsequent close calls ([#5311](https://github.com/apache/iceberg/pull/5311)) + +### 0.14.0 release + +Apache Iceberg 0.14.0 was released on 16 July 2022. + +#### Highlights + +* Added several [performance improvements](#performance-improvements) for scan planning and Spark queries +* Added a common REST catalog client that uses change-based commits to resolve commit conflicts on the service side +* Added support for Spark 3.3, including `AS OF` syntax for SQL time travel queries +* Added support for Scala 2.13 with Spark 3.2 or later +* Added merge-on-read support for MERGE and UPDATE queries in Spark 3.2 or later +* Added support to rewrite partitions using zorder +* Added support for Flink 1.15 and dropped support for Flink 1.12 +* Added a spec and implementation for Puffin, a format for large stats and index blobs, like [Theta sketches](https://datasketches.apache.org/docs/Theta/InverseEstimate.html) or bloom filters +* Added new interfaces for consuming data incrementally (both append and changelog scans) +* Added support for bulk operations and ranged reads to FileIO interfaces +* Added more metadata tables to show delete files in the metadata tree + +#### High-level features + +* API + - Added IcebergBuild to expose Iceberg version and build information + - Added binary compatibility checking to the build ([#4638](https://github.com/apache/iceberg/pull/4638), [#4798](https://github.com/apache/iceberg/pull/4798)) + - Added a new IncrementalAppendScan interface and planner implementation ([#4580](https://github.com/apache/iceberg/pull/4580)) + - Added a new IncrementalChangelogScan interface ([#4870](https://github.com/apache/iceberg/pull/4870)) + - Refactored the ScanTask hierarchy to create new task types for changelog scans ([#5077](https://github.com/apache/iceberg/pull/5077)) + - Added expression sanitizer ([#4672](https://github.com/apache/iceberg/pull/4672)) + - Added utility to check expression equivalence ([#4947](https://github.com/apache/iceberg/pull/4947)) + - Added support for serializing FileIO instances using initialization properties ([#5178](https://github.com/apache/iceberg/pull/5178)) + - Updated Snapshot methods to accept a FileIO to read metadata files, deprecated old methods ([#4873](https://github.com/apache/iceberg/pull/4873)) + - Added optional interfaces to FileIO, for batch deletes ([#4052](https://github.com/apache/iceberg/pull/4052)), prefix operations ([#5096](https://github.com/apache/iceberg/pull/5096)), and ranged reads ([#4608](https://github.com/apache/iceberg/pull/4608)) +* Core + - Added a common client for REST-based catalog services that uses a change-based protocol ([#4320](https://github.com/apache/iceberg/pull/4320), [#4319](https://github.com/apache/iceberg/pull/4319)) + - Added Puffin, a file format for statistics and index payloads or sketches ([#4944](https://github.com/apache/iceberg/pull/4944), [#4537](https://github.com/apache/iceberg/pull/4537)) + - Added snapshot references to track tags and branches ([#4019](https://github.com/apache/iceberg/pull/4019)) + - ManageSnapshots now supports multiple operations using transactions, and added branch and tag operations ([#4128](https://github.com/apache/iceberg/pull/4128), [#4071](https://github.com/apache/iceberg/pull/4071)) + - ReplacePartitions and OverwriteFiles now support serializable isolation ([#2925](https://github.com/apache/iceberg/pull/2925), [#4052](https://github.com/apache/iceberg/pull/4052)) + - Added new metadata tables: `data_files` ([#4336](https://github.com/apache/iceberg/pull/4336)), `delete_files` ([#4243](https://github.com/apache/iceberg/pull/4243)), `all_delete_files`, and `all_files` ([#4694](https://github.com/apache/iceberg/pull/4694)) + - Added deleted files to the `files` metadata table ([#4336](https://github.com/apache/iceberg/pull/4336)) and delete file counts to the `manifests` table ([#4764](https://github.com/apache/iceberg/pull/4764)) + - Added support for predicate pushdown for the `all_data_files` metadata table ([#4382](https://github.com/apache/iceberg/pull/4382)) and the `all_manifests` table ([#4736](https://github.com/apache/iceberg/pull/4736)) + - Added support for catalogs to default table properties on creation ([#4011](https://github.com/apache/iceberg/pull/4011)) + - Updated sort order construction to ensure all partition fields are added to avoid partition closed failures ([#5131](https://github.com/apache/iceberg/pull/5131)) +* Spark + - Spark 3.3 is now supported ([#5056](https://github.com/apache/iceberg/pull/5056)) + - Added SQL time travel using `AS OF` syntax in Spark 3.3 ([#5156](https://github.com/apache/iceberg/pull/5156)) + - Scala 2.13 is now supported for Spark 3.2 and 3.3 ([#4009](https://github.com/apache/iceberg/pull/4009)) + - Added support for the `mergeSchema` option for DataFrame writes ([#4154](https://github.com/apache/iceberg/pull/4154)) + - MERGE and UPDATE queries now support the lazy / merge-on-read strategy ([#3984](https://github.com/apache/iceberg/pull/3984), [#4047](https://github.com/apache/iceberg/pull/4047)) + - Added zorder rewrite strategy to the `rewrite_data_files` stored procedure and action ([#3983](https://github.com/apache/iceberg/pull/3983), [#4902](https://github.com/apache/iceberg/pull/4902)) + - Added a `register_table` stored procedure to create tables from metadata JSON files ([#4810](https://github.com/apache/iceberg/pull/4810)) + - Added a `publish_changes` stored procedure to publish staged commits by ID ([#4715](https://github.com/apache/iceberg/pull/4715)) + - Added `CommitMetadata` helper class to set snapshot summary properties from SQL ([#4956](https://github.com/apache/iceberg/pull/4956)) + - Added support to supply a file listing to remove orphan data files procedure and action ([#4503](https://github.com/apache/iceberg/pull/4503)) + - Added FileIO metrics to the Spark UI ([#4030](https://github.com/apache/iceberg/pull/4030), [#4050](https://github.com/apache/iceberg/pull/4050)) + - DROP TABLE now supports the PURGE flag ([#3056](https://github.com/apache/iceberg/pull/3056)) + - Added support for custom isolation level for dynamic partition overwrites ([#2925](https://github.com/apache/iceberg/pull/2925)) and filter overwrites ([#4293](https://github.com/apache/iceberg/pull/4293)) + - Schema identifier fields are now shown in table properties ([#4475](https://github.com/apache/iceberg/pull/4475)) + - Abort cleanup now supports parallel execution ([#4704](https://github.com/apache/iceberg/pull/4704)) +* Flink + - Flink 1.15 is now supported ([#4553](https://github.com/apache/iceberg/pull/4553)) + - Flink 1.12 support was removed ([#4551](https://github.com/apache/iceberg/pull/4551)) + - Added a FLIP-27 source and builder to 1.14 and 1.15 ([#5109](https://github.com/apache/iceberg/pull/5109)) + - Added an option to set the monitor interval ([#4887](https://github.com/apache/iceberg/pull/4887)) and an option to limit the number of snapshots in a streaming read planning operation ([#4943](https://github.com/apache/iceberg/pull/4943)) + - Added support for write options, like `write-format` to Flink sink builder ([#3998](https://github.com/apache/iceberg/pull/3998)) + - Added support for task locality when reading from HDFS ([#3817](https://github.com/apache/iceberg/pull/3817)) + - Use Hadoop configuration files from `hadoop-conf-dir` property ([#4622](https://github.com/apache/iceberg/pull/4622)) +* Vendor integrations + - Added Dell ECS integration ([#3376](https://github.com/apache/iceberg/pull/3376), [#4221](https://github.com/apache/iceberg/pull/4221)) + - JDBC catalog now supports namespace properties ([#3275](https://github.com/apache/iceberg/pull/3275)) + - AWS Glue catalog supports native Glue locking ([#4166](https://github.com/apache/iceberg/pull/4166)) + - AWS S3FileIO supports using S3 access points ([#4334](https://github.com/apache/iceberg/pull/4334)), bulk operations ([#4052](https://github.com/apache/iceberg/pull/4052), [#5096](https://github.com/apache/iceberg/pull/5096)), ranged reads ([#4608](https://github.com/apache/iceberg/pull/4608)), and tagging at write time or in place of deletes ([#4259](https://github.com/apache/iceberg/pull/4259), [#4342](https://github.com/apache/iceberg/pull/4342)) + - AWS GlueCatalog supports passing LakeFormation credentials ([#4280](https://github.com/apache/iceberg/pull/4280)) + - AWS DynamoDB catalog and lock supports overriding the DynamoDB endpoint ([#4726](https://github.com/apache/iceberg/pull/4726)) + - Nessie now supports namespaces and namespace properties ([#4385](https://github.com/apache/iceberg/pull/4385), [#4610](https://github.com/apache/iceberg/pull/4610)) + - Nessie now passes most common catalog tests ([#4392](https://github.com/apache/iceberg/pull/4392)) +* Parquet + - Added support for row group skipping using Parquet bloom filters ([#4938](https://github.com/apache/iceberg/pull/4938)) + - Added table configuration options for writing Parquet bloom filters ([#5035](https://github.com/apache/iceberg/pull/5035)) +* ORC + - Support file rolling at a target file size ([#4419](https://github.com/apache/iceberg/pull/4419)) + - Support table compression settings, `write.orc.compression-codec` and `write.orc.compression-strategy` ([#4273](https://github.com/apache/iceberg/pull/4273)) + +#### Performance improvements + +* Core + - Fixed manifest file handling in scan planning to open manifests in the planning threadpool ([#5206](https://github.com/apache/iceberg/pull/5206)) + - Avoided an extra S3 HEAD request by passing file length when opening manifest files ([#5207](https://github.com/apache/iceberg/pull/5207)) + - Refactored Arrow vectorized readers to avoid extra dictionary copies ([#5137](https://github.com/apache/iceberg/pull/5137)) + - Improved Arrow decimal handling to improve decimal performance ([#5168](https://github.com/apache/iceberg/pull/5168), [#5198](https://github.com/apache/iceberg/pull/5198)) + - Added support for Avro files with Zstd compression ([#4083](https://github.com/apache/iceberg/pull/4083)) + - Column metrics are now disabled by default after the first 32 columns ([#3959](https://github.com/apache/iceberg/pull/3959), [#5215](https://github.com/apache/iceberg/pull/5215)) + - Updated delete filters to copy row wrappers to avoid expensive type analysis ([#5249](https://github.com/apache/iceberg/pull/5249)) + - Snapshot expiration supports parallel execution ([#4148](https://github.com/apache/iceberg/pull/4148)) + - Manifest updates can use a custom thread pool ([#4146](https://github.com/apache/iceberg/pull/4146)) +* Spark + - Parquet vectorized reads are enabled by default ([#4196](https://github.com/apache/iceberg/pull/4196)) + - Scan statistics now adjust row counts for split data files ([#4446](https://github.com/apache/iceberg/pull/4446)) + - Implemented `SupportsReportStatistics` in `ScanBuilder` to work around SPARK-38962 ([#5136](https://github.com/apache/iceberg/pull/5136)) + - Updated Spark tables to avoid expensive (and inaccurate) size estimation ([#5225](https://github.com/apache/iceberg/pull/5225)) +* Flink + - Operators will now use a worker pool per job ([#4177](https://github.com/apache/iceberg/pull/4177)) + - Fixed `ClassCastException` thrown when reading arrays from Parquet ([#4432](https://github.com/apache/iceberg/pull/4432)) +* Hive + - Added vectorized Parquet reads for Hive 3 ([#3980](https://github.com/apache/iceberg/pull/3980)) + - Improved generic reader performance using copy instead of create ([#4218](https://github.com/apache/iceberg/pull/4218)) + +#### Notable bug fixes + +This release includes all bug fixes from the 0.13.x patch releases. + +* Core + - Fixed an exception thrown when metadata-only deletes encounter delete files that are partially matched ([#4304](https://github.com/apache/iceberg/pull/4304)) + - Fixed transaction retries for changes without validations, like schema updates, that could ignore an update ([#4464](https://github.com/apache/iceberg/pull/4464)) + - Fixed failures when reading metadata tables with evolved partition specs ([#4520](https://github.com/apache/iceberg/pull/4520), [#4560](https://github.com/apache/iceberg/pull/4560)) + - Fixed delete files dropped when a manifest is rewritten following a format version upgrade ([#4514](https://github.com/apache/iceberg/pull/4514)) + - Fixed missing metadata files resulting from an OOM during commit cleanup ([#4673](https://github.com/apache/iceberg/pull/4673)) + - Updated logging to use sanitized expressions to avoid leaking values ([#4672](https://github.com/apache/iceberg/pull/4672)) +* Spark + - Fixed Spark to skip calling abort when CommitStateUnknownException is thrown ([#4687](https://github.com/apache/iceberg/pull/4687)) + - Fixed MERGE commands with mixed case identifiers ([#4848](https://github.com/apache/iceberg/pull/4848)) +* Flink + - Fixed table property update failures when tables have a primary key ([#4561](https://github.com/apache/iceberg/pull/4561)) +* Integrations + - JDBC catalog behavior has been updated to pass common catalog tests ([#4220](https://github.com/apache/iceberg/pull/4220), [#4231](https://github.com/apache/iceberg/pull/4231)) + +#### Dependency changes + +* Updated Apache Avro to 1.10.2 (previously 1.10.1) +* Updated Apache Parquet to 1.12.3 (previously 1.12.2) +* Updated Apache ORC to 1.7.5 (previously 1.7.2) +* Updated Apache Arrow to 7.0.0 (previously 6.0.0) +* Updated AWS SDK to 2.17.131 (previously 2.15.7) +* Updated Nessie to 0.30.0 (previously 0.18.0) +* Updated Caffeine to 2.9.3 (previously 2.8.4) + +### 0.13.2 + +Apache Iceberg 0.13.2 was released on June 15th, 2022. + +* Git tag: [0.13.2](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.13.2) +* [0.13.2 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.13.2/apache-iceberg-0.13.2.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.13.2/apache-iceberg-0.13.2.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.13.2/apache-iceberg-0.13.2.tar.gz.sha512) +* [0.13.2 Spark 3.2 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.12/0.13.2/iceberg-spark-runtime-3.2_2.12-0.13.2.jar) +* [0.13.2 Spark 3.1 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.1_2.12/0.13.2/iceberg-spark-runtime-3.1_2.12-0.13.2.jar) +* [0.13.2 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.13.2/iceberg-spark3-runtime-0.13.2.jar) +* [0.13.2 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.13.2/iceberg-spark-runtime-0.13.2.jar) +* [0.13.2 Flink 1.14 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.14/0.13.2/iceberg-flink-runtime-1.14-0.13.2.jar) +* [0.13.2 Flink 1.13 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.13/0.13.2/iceberg-flink-runtime-1.13-0.13.2.jar) +* [0.13.2 Flink 1.12 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.12/0.13.2/iceberg-flink-runtime-1.12-0.13.2.jar) +* [0.13.2 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.13.2/iceberg-hive-runtime-0.13.2.jar) + +**Important bug fixes and changes:** + +* **Core** + * [\#4673](https://github.com/apache/iceberg/pull/4673) fixes table corruption from OOM during commit cleanup + * [\#4514](https://github.com/apache/iceberg/pull/4514) row delta delete files were dropped in sequential commits after table format updated to v2 + * [\#4464](https://github.com/apache/iceberg/pull/4464) fixes an issue were conflicting transactions have been ignored during a commit + * [\#4520](https://github.com/apache/iceberg/pull/4520) fixes an issue with wrong table predicate filtering with evolved partition specs +* **Spark** + * [\#4663](https://github.com/apache/iceberg/pull/4663) fixes NPEs in Spark value converter + * [\#4687](https://github.com/apache/iceberg/pull/4687) fixes an issue with incorrect aborts when non-runtime exceptions were thrown in Spark +* **Flink** + * Note that there's a correctness issue when using upsert mode in Flink 1.12. Given that Flink 1.12 is deprecated, it was decided to not fix this bug but rather log a warning (see also [\#4754](https://github.com/apache/iceberg/pull/4754)). +* **Nessie** + * [\#4509](https://github.com/apache/iceberg/pull/4509) fixes a NPE that occurred when accessing refreshed tables in NessieCatalog + + +A more exhaustive list of changes is available under the [0.13.2 release milestone](https://github.com/apache/iceberg/milestone/18?closed=1). + +### 0.13.1 + +Apache Iceberg 0.13.1 was released on February 14th, 2022. + +* Git tag: [0.13.1](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.13.1) +* [0.13.1 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.13.1/apache-iceberg-0.13.1.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.13.1/apache-iceberg-0.13.1.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.13.1/apache-iceberg-0.13.1.tar.gz.sha512) +* [0.13.1 Spark 3.2 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.12/0.13.1/iceberg-spark-runtime-3.2_2.12-0.13.1.jar) +* [0.13.1 Spark 3.1 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.1_2.12/0.13.1/iceberg-spark-runtime-3.1_2.12-0.13.1.jar) +* [0.13.1 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.13.1/iceberg-spark3-runtime-0.13.1.jar) +* [0.13.1 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.13.1/iceberg-spark-runtime-0.13.1.jar) +* [0.13.1 Flink 1.14 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.14/0.13.1/iceberg-flink-runtime-1.14-0.13.1.jar) +* [0.13.1 Flink 1.13 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.13/0.13.1/iceberg-flink-runtime-1.13-0.13.1.jar) +* [0.13.1 Flink 1.12 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.12/0.13.1/iceberg-flink-runtime-1.12-0.13.1.jar) +* [0.13.1 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.13.1/iceberg-hive-runtime-0.13.1.jar) + +**Important bug fixes:** + +* **Spark** + * [\#4023](https://github.com/apache/iceberg/pull/4023) fixes predicate pushdown in row-level operations for merge conditions in Spark 3.2. + Prior to the fix, filters would not be extracted and targeted merge conditions were not pushed down leading to degraded performance + for these targeted merge operations. + * [\#4024](https://github.com/apache/iceberg/pull/4024) fixes table creation in the root namespace of a Hadoop Catalog. + +* **Flink** + * [\#3986](https://github.com/apache/iceberg/pull/3986) fixes manifest location collisions when there are multiple committers + in the same Flink job. + + +### 0.13.0 + +Apache Iceberg 0.13.0 was released on February 4th, 2022. + +* Git tag: [0.13.0](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.13.0) +* [0.13.0 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.13.0/apache-iceberg-0.13.0.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.13.0/apache-iceberg-0.13.0.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.13.0/apache-iceberg-0.13.0.tar.gz.sha512) +* [0.13.0 Spark 3.2 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.12/0.13.0/iceberg-spark-runtime-3.2_2.12-0.13.0.jar) +* [0.13.0 Spark 3.1 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.1_2.12/0.13.0/iceberg-spark-runtime-3.1_2.12-0.13.0.jar) +* [0.13.0 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.13.0/iceberg-spark3-runtime-0.13.0.jar) +* [0.13.0 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.13.0/iceberg-spark-runtime-0.13.0.jar) +* [0.13.0 Flink 1.14 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.14/0.13.0/iceberg-flink-runtime-1.14-0.13.0.jar) +* [0.13.0 Flink 1.13 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.13/0.13.0/iceberg-flink-runtime-1.13-0.13.0.jar) +* [0.13.0 Flink 1.12 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime-1.12/0.13.0/iceberg-flink-runtime-1.12-0.13.0.jar) +* [0.13.0 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.13.0/iceberg-hive-runtime-0.13.0.jar) + +**High-level features:** + +* **Core** + * Catalog caching now supports cache expiration through catalog property `cache.expiration-interval-ms` [[\#3543](https://github.com/apache/iceberg/pull/3543)] + * Catalog now supports registration of Iceberg table from a given metadata file location [[\#3851](https://github.com/apache/iceberg/pull/3851)] + * Hadoop catalog can be used with S3 and other file systems safely by using a lock manager [[\#3663](https://github.com/apache/iceberg/pull/3663)] +* **Vendor Integrations** + * Google Cloud Storage (GCS) `FileIO` is supported with optimized read and write using GCS streaming transfer [[\#3711](https://github.com/apache/iceberg/pull/3711)] + * Aliyun Object Storage Service (OSS) `FileIO` is supported [[\#3553](https://github.com/apache/iceberg/pull/3553)] + * Any S3-compatible storage (e.g. MinIO) can now be accessed through AWS `S3FileIO` with custom endpoint and credential configurations [[\#3656](https://github.com/apache/iceberg/pull/3656)] [[\#3658](https://github.com/apache/iceberg/pull/3658)] + * AWS `S3FileIO` now supports server-side checksum validation [[\#3813](https://github.com/apache/iceberg/pull/3813)] + * AWS `GlueCatalog` now displays more table information including table location, description [[\#3467](https://github.com/apache/iceberg/pull/3467)] and columns [[\#3888](https://github.com/apache/iceberg/pull/3888)] + * Using multiple `FileIO`s based on file path scheme is supported by configuring a `ResolvingFileIO` [[\#3593](https://github.com/apache/iceberg/pull/3593)] +* **Spark** + * Spark 3.2 is supported [[\#3335](https://github.com/apache/iceberg/pull/3335)] with merge-on-read `DELETE` [[\#3970](https://github.com/apache/iceberg/pull/3970)] + * `RewriteDataFiles` action now supports sort-based table optimization [[\#2829](https://github.com/apache/iceberg/pull/2829)] and merge-on-read delete compaction [[\#3454](https://github.com/apache/iceberg/pull/3454)]. The corresponding Spark call procedure `rewrite_data_files` is also supported [[\#3375](https://github.com/apache/iceberg/pull/3375)] + * Time travel queries now use snapshot schema instead of the table's latest schema [[\#3722](https://github.com/apache/iceberg/pull/3722)] + * Spark vectorized reads now support row-level deletes [[\#3557](https://github.com/apache/iceberg/pull/3557)] [[\#3287](https://github.com/apache/iceberg/pull/3287)] + * `add_files` procedure now skips duplicated files by default (can be turned off with the `check_duplicate_files` flag) [[\#2895](https://github.com/apache/iceberg/issues/2779)], skips folder without file [[\#2895](https://github.com/apache/iceberg/issues/3455)] and partitions with `null` values [[\#2895](https://github.com/apache/iceberg/issues/3778)] instead of throwing exception, and supports partition pruning for faster table import [[\#3745](https://github.com/apache/iceberg/issues/3745)] +* **Flink** + * Flink 1.13 and 1.14 are supported [[\#3116](https://github.com/apache/iceberg/pull/3116)] [[\#3434](https://github.com/apache/iceberg/pull/3434)] + * Flink connector support is supported [[\#2666](https://github.com/apache/iceberg/pull/2666)] + * Upsert write option is supported [[\#2863](https://github.com/apache/iceberg/pull/2863)] +* **Hive** + * Table listing in Hive catalog can now skip non-Iceberg tables by disabling flag `list-all-tables` [[\#3908](https://github.com/apache/iceberg/pull/3908)] + * Hive tables imported to Iceberg can now be read by `IcebergInputFormat` [[\#3312](https://github.com/apache/iceberg/pull/3312)] +* **File Formats** + * ORC now supports writing delete file [[\#3248](https://github.com/apache/iceberg/pull/3248)] [[\#3250](https://github.com/apache/iceberg/pull/3250)] [[\#3366](https://github.com/apache/iceberg/pull/3366)] + +**Important bug fixes:** + +* **Core** + * Iceberg new data file root path is configured through `write.data.path` going forward. `write.folder-storage.path` and `write.object-storage.path` are deprecated [[\#3094](https://github.com/apache/iceberg/pull/3094)] + * Catalog commit status is `UNKNOWN` instead of `FAILURE` when new metadata location cannot be found in snapshot history [[\#3717](https://github.com/apache/iceberg/pull/3717)] + * Dropping table now also deletes old metadata files instead of leaving them strained [[\#3622](https://github.com/apache/iceberg/pull/3622)] + * `history` and `snapshots` metadata tables can now query tables with no current snapshot instead of returning empty [[\#3812](https://github.com/apache/iceberg/pull/3812)] +* **Vendor Integrations** + * Using cloud service integrations such as AWS `GlueCatalog` and `S3FileIO` no longer fail when missing Hadoop dependencies in the execution environment [[\#3590](https://github.com/apache/iceberg/pull/3590)] + * AWS clients are now auto-closed when related `FileIO` or `Catalog` is closed. There is no need to close the AWS clients separately [[\#2878](https://github.com/apache/iceberg/pull/2878)] +* **Spark** + * For Spark >= 3.1, `REFRESH TABLE` can now be used with Spark session catalog instead of throwing exception [[\#3072](https://github.com/apache/iceberg/pull/3072)] + * Insert overwrite mode now skips partition with 0 record instead of failing the write operation [[\#2895](https://github.com/apache/iceberg/issues/2895)] + * Spark snapshot expiration action now supports custom `FileIO` instead of just `HadoopFileIO` [[\#3089](https://github.com/apache/iceberg/pull/3089)] + * `REPLACE TABLE AS SELECT` can now work with tables with columns that have changed partition transform. Each old partition field of the same column is converted to a void transform with a different name [[\#3421](https://github.com/apache/iceberg/issues/3421)] + * Spark SQL filters containing binary or fixed literals can now be pushed down instead of throwing exception [[\#3728](https://github.com/apache/iceberg/pull/3728)] +* **Flink** + * A `ValidationException` will be thrown if a user configures both `catalog-type` and `catalog-impl`. Previously it chose to use `catalog-type`. The new behavior brings Flink consistent with Spark and Hive [[\#3308](https://github.com/apache/iceberg/issues/3308)] + * Changelog tables can now be queried without `RowData` serialization issues [[\#3240](https://github.com/apache/iceberg/pull/3240)] + * `java.sql.Time` data type can now be written without data overflow problem [[\#3740](https://github.com/apache/iceberg/pull/3740)] + * Avro position delete files can now be read without encountering `NullPointerException` [[\#3540](https://github.com/apache/iceberg/pull/3540)] +* **Hive** + * Hive catalog can now be initialized with a `null` Hadoop configuration instead of throwing exception [[\#3252](https://github.com/apache/iceberg/pull/3252)] + * Table creation can now succeed instead of throwing exception when some columns do not have comments [[\#3531](https://github.com/apache/iceberg/pull/3531)] +* **File Formats** + * Parquet file writing issue is fixed for string data with over 16 unparseable chars (e.g. high/low surrogates) [[\#3760](https://github.com/apache/iceberg/pull/3760)] + * ORC vectorized read is now configured using `read.orc.vectorization.batch-size` instead of `read.parquet.vectorization.batch-size` [[\#3133](https://github.com/apache/iceberg/pull/3133)] + +**Other notable changes:** + +* The community has finalized the long-term strategy of Spark, Flink and Hive support. See [Multi-Engine Support](multi-engine-support.md) page for more details. + +### 0.12.1 + +Apache Iceberg 0.12.1 was released on November 8th, 2021. + +* Git tag: [0.12.1](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.12.1) +* [0.12.1 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.12.1/apache-iceberg-0.12.1.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.12.1/apache-iceberg-0.12.1.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.12.1/apache-iceberg-0.12.1.tar.gz.sha512) +* [0.12.1 Spark 3.x runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.12.1/iceberg-spark3-runtime-0.12.1.jar) +* [0.12.1 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.12.1/iceberg-spark-runtime-0.12.1.jar) +* [0.12.1 Flink runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime/0.12.1/iceberg-flink-runtime-0.12.1.jar) +* [0.12.1 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.12.1/iceberg-hive-runtime-0.12.1.jar) + +Important bug fixes and changes: + +* [\#3264](https://github.com/apache/iceberg/pull/3258) fixes validation failures that occurred after snapshot expiration when writing Flink CDC streams to Iceberg tables. +* [\#3264](https://github.com/apache/iceberg/pull/3264) fixes reading projected map columns from Parquet files written before Parquet 1.11.1. +* [\#3195](https://github.com/apache/iceberg/pull/3195) allows validating that commits that produce row-level deltas don't conflict with concurrently added files. Ensures users can maintain serializable isolation for update and delete operations, including merge operations. +* [\#3199](https://github.com/apache/iceberg/pull/3199) allows validating that commits that overwrite files don't conflict with concurrently added files. Ensures users can maintain serializable isolation for overwrite operations. +* [\#3135](https://github.com/apache/iceberg/pull/3135) fixes equality-deletes using `DATE`, `TIMESTAMP`, and `TIME` types. +* [\#3078](https://github.com/apache/iceberg/pull/3078) prevents the JDBC catalog from overwriting the `jdbc.user` property if any property called user exists in the environment. +* [\#3035](https://github.com/apache/iceberg/pull/3035) fixes drop namespace calls with the DyanmoDB catalog. +* [\#3273](https://github.com/apache/iceberg/pull/3273) fixes importing Avro files via `add_files` by correctly setting the number of records. +* [\#3332](https://github.com/apache/iceberg/pull/3332) fixes importing ORC files with float or double columns in `add_files`. + +A more exhaustive list of changes is available under the [0.12.1 release milestone](https://github.com/apache/iceberg/milestone/15?closed=1). + +### 0.12.0 + +Apache Iceberg 0.12.0 was released on August 15, 2021. It consists of 395 commits authored by 74 contributors over a 139 day period. + +* Git tag: [0.12.0](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.12.0) +* [0.12.0 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.12.0/apache-iceberg-0.12.0.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.12.0/apache-iceberg-0.12.0.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.12.0/apache-iceberg-0.12.0.tar.gz.sha512) +* [0.12.0 Spark 3.x runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.12.0/iceberg-spark3-runtime-0.12.0.jar) +* [0.12.0 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.12.0/iceberg-spark-runtime-0.12.0.jar) +* [0.12.0 Flink runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime/0.12.0/iceberg-flink-runtime-0.12.0.jar) +* [0.12.0 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.12.0/iceberg-hive-runtime-0.12.0.jar) + +**High-level features:** + +* **Core** + * Allow Iceberg schemas to specify one or more columns as row identifiers [[\#2465](https://github.com/apache/iceberg/pull/2465)]. Note that this is a prerequisite for supporting upserts in Flink. + * Added JDBC [[\#1870](https://github.com/apache/iceberg/pull/1870)] and DynamoDB [[\#2688](https://github.com/apache/iceberg/pull/2688)] catalog implementations. + * Added predicate pushdown for partitions and files metadata tables [[\#2358](https://github.com/apache/iceberg/pull/2358), [\#2926](https://github.com/apache/iceberg/pull/2926)]. + * Added a new, more flexible compaction action for Spark that can support different strategies such as bin packing and sorting. [[\#2501](https://github.com/apache/iceberg/pull/2501), [\#2609](https://github.com/apache/iceberg/pull/2609)]. + * Added the ability to upgrade to v2 or create a v2 table using the table property format-version=2 [[\#2887](https://github.com/apache/iceberg/pull/2887)]. + * Added support for nulls in StructLike collections [[\#2929](https://github.com/apache/iceberg/pull/2929)]. + * Added `key_metadata` field to manifest lists for encryption [[\#2675](https://github.com/apache/iceberg/pull/2675)]. +* **Flink** + * Added support for SQL primary keys [[\#2410](https://github.com/apache/iceberg/pull/2410)]. +* **Hive** + * Added the ability to set the catalog at the table level in the Hive Metastore. This makes it possible to write queries that reference tables from multiple catalogs [[\#2129](https://github.com/apache/iceberg/pull/2129)]. + * As a result of [[\#2129](https://github.com/apache/iceberg/pull/2129)], deprecated the configuration property `iceberg.mr.catalog` which was previously used to configure the Iceberg catalog in MapReduce and Hive [[\#2565](https://github.com/apache/iceberg/pull/2565)]. + * Added table-level JVM lock on commits[[\#2547](https://github.com/apache/iceberg/pull/2547)]. + * Added support for Hive's vectorized ORC reader [[\#2613](https://github.com/apache/iceberg/pull/2613)]. +* **Spark** + * Added `SET` and `DROP IDENTIFIER FIELDS` clauses to `ALTER TABLE` so people don't have to look up the DDL [[\#2560](https://github.com/apache/iceberg/pull/2560)]. + * Added support for `ALTER TABLE REPLACE PARTITION FIELD` DDL [[\#2365](https://github.com/apache/iceberg/pull/2365)]. + * Added support for micro-batch streaming reads for structured streaming in Spark3 [[\#2660](https://github.com/apache/iceberg/pull/2660)]. + * Improved the performance of importing a Hive table by not loading all partitions from Hive and instead pushing the partition filter to the Metastore [[\#2777](https://github.com/apache/iceberg/pull/2777)]. + * Added support for `UPDATE` statements in Spark [[\#2193](https://github.com/apache/iceberg/pull/2193), [\#2206](https://github.com/apache/iceberg/pull/2206)]. + * Added support for Spark 3.1 [[\#2512](https://github.com/apache/iceberg/pull/2512)]. + * Added `RemoveReachableFiles` action [[\#2415](https://github.com/apache/iceberg/pull/2415)]. + * Added `add_files` stored procedure [[\#2210](https://github.com/apache/iceberg/pull/2210)]. + * Refactored Actions API and added a new entry point. + * Added support for Hadoop configuration overrides [[\#2922](https://github.com/apache/iceberg/pull/2922)]. + * Added support for the `TIMESTAMP WITHOUT TIMEZONE` type in Spark [[\#2757](https://github.com/apache/iceberg/pull/2757)]. + * Added validation that files referenced by row-level deletes are not concurrently rewritten [[\#2308](https://github.com/apache/iceberg/pull/2308)]. + + +**Important bug fixes:** + +* **Core** + * Fixed string bucketing with non-BMP characters [[\#2849](https://github.com/apache/iceberg/pull/2849)]. + * Fixed Parquet dictionary filtering with fixed-length byte arrays and decimals [[\#2551](https://github.com/apache/iceberg/pull/2551)]. + * Fixed a problem with the configuration of HiveCatalog [[\#2550](https://github.com/apache/iceberg/pull/2550)]. + * Fixed partition field IDs in table replacement [[\#2906](https://github.com/apache/iceberg/pull/2906)]. +* **Hive** + * Enabled dropping HMS tables even if the metadata on disk gets corrupted [[\#2583](https://github.com/apache/iceberg/pull/2583)]. +* **Parquet** + * Fixed Parquet row group filters when types are promoted from `int` to `long` or from `float` to `double` [[\#2232](https://github.com/apache/iceberg/pull/2232)] +* **Spark** + * Fixed `MERGE INTO` in Spark when used with `SinglePartition` partitioning [[\#2584](https://github.com/apache/iceberg/pull/2584)]. + * Fixed nested struct pruning in Spark [[\#2877](https://github.com/apache/iceberg/pull/2877)]. + * Fixed NaN handling for float and double metrics [[\#2464](https://github.com/apache/iceberg/pull/2464)]. + * Fixed Kryo serialization for data and delete files [[\#2343](https://github.com/apache/iceberg/pull/2343)]. + +**Other notable changes:** + +* The Iceberg Community [voted to approve](https://mail-archives.apache.org/mod_mbox/iceberg-dev/202107.mbox/%3cCAMwmD1-k1gnShK=wQ0PD88it6cg9mY7Y1hKHjDZ7L-jcDzpyZA@mail.gmail.com%3e) version 2 of the Apache Iceberg Format Specification. The differences between version 1 and 2 of the specification are documented [here](spec.md#version-2). +* Bugfixes and stability improvements for NessieCatalog. +* Improvements and fixes for Iceberg's Python library. +* Added a vectorized reader for Apache Arrow [[\#2286](https://github.com/apache/iceberg/pull/2286)]. +* The following Iceberg dependencies were upgraded: + * Hive 2.3.8 [[\#2110](https://github.com/apache/iceberg/pull/2110)]. + * Avro 1.10.1 [[\#1648](https://github.com/apache/iceberg/pull/1648)]. + * Parquet 1.12.0 [[\#2441](https://github.com/apache/iceberg/pull/2441)]. + +### 0.11.1 + +* Git tag: [0.11.1](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.11.1) +* [0.11.1 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.11.1/apache-iceberg-0.11.1.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.11.1/apache-iceberg-0.11.1.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.11.1/apache-iceberg-0.11.1.tar.gz.sha512) +* [0.11.1 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.11.1/iceberg-spark3-runtime-0.11.1.jar) +* [0.11.1 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.11.1/iceberg-spark-runtime-0.11.1.jar) +* [0.11.1 Flink runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime/0.11.1/iceberg-flink-runtime-0.11.1.jar) +* [0.11.1 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.11.1/iceberg-hive-runtime-0.11.1.jar) + +Important bug fixes: + +* [\#2367](https://github.com/apache/iceberg/pull/2367) prohibits deleting data files when tables are dropped if GC is disabled. +* [\#2196](https://github.com/apache/iceberg/pull/2196) fixes data loss after compaction when large files are split into multiple parts and only some parts are combined with other files. +* [\#2232](https://github.com/apache/iceberg/pull/2232) fixes row group filters with promoted types in Parquet. +* [\#2267](https://github.com/apache/iceberg/pull/2267) avoids listing non-Iceberg tables in Glue. +* [\#2254](https://github.com/apache/iceberg/pull/2254) fixes predicate pushdown for Date in Hive. +* [\#2126](https://github.com/apache/iceberg/pull/2126) fixes writing of Date, Decimal, Time, UUID types in Hive. +* [\#2241](https://github.com/apache/iceberg/pull/2241) fixes vectorized ORC reads with metadata columns in Spark. +* [\#2154](https://github.com/apache/iceberg/pull/2154) refreshes the relation cache in DELETE and MERGE operations in Spark. + +### 0.11.0 + +* Git tag: [0.11.0](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.11.0) +* [0.11.0 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.11.0/apache-iceberg-0.11.0.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.11.0/apache-iceberg-0.11.0.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.11.0/apache-iceberg-0.11.0.tar.gz.sha512) +* [0.11.0 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.11.0/iceberg-spark3-runtime-0.11.0.jar) +* [0.11.0 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.11.0/iceberg-spark-runtime-0.11.0.jar) +* [0.11.0 Flink runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime/0.11.0/iceberg-flink-runtime-0.11.0.jar) +* [0.11.0 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.11.0/iceberg-hive-runtime-0.11.0.jar) + +High-level features: + +* **Core API** now supports partition spec and sort order evolution +* **Spark 3** now supports the following SQL extensions: + * MERGE INTO (experimental) + * DELETE FROM (experimental) + * ALTER TABLE ... ADD/DROP PARTITION + * ALTER TABLE ... WRITE ORDERED BY + * Invoke stored procedures using CALL +* **Flink** now supports streaming reads, CDC writes (experimental), and filter pushdown +* **AWS module** is added to support better integration with AWS, with [AWS Glue catalog](https://aws.amazon.com/glue/) support and dedicated S3 FileIO implementation +* **Nessie module** is added to support integration with [project Nessie](https://projectnessie.org/) + +Important bug fixes: + +* [\#1981](https://github.com/apache/iceberg/pull/1981) fixes bug that date and timestamp transforms were producing incorrect values for dates and times before 1970. Before the fix, negative values were incorrectly transformed by date and timestamp transforms to 1 larger than the correct value. For example, `day(1969-12-31 10:00:00)` produced 0 instead of -1. The fix is backwards compatible, which means predicate projection can still work with the incorrectly transformed partitions written using older versions. +* [\#2091](https://github.com/apache/iceberg/pull/2091) fixes `ClassCastException` for type promotion `int` to `long` and `float` to `double` during Parquet vectorized read. Now Arrow vector is created by looking at Parquet file schema instead of Iceberg schema for `int` and `float` fields. +* [\#1998](https://github.com/apache/iceberg/pull/1998) fixes bug in `HiveTableOperation` that `unlock` is not called if new metadata cannot be deleted. Now it is guaranteed that `unlock` is always called for Hive catalog users. +* [\#1979](https://github.com/apache/iceberg/pull/1979) fixes table listing failure in Hadoop catalog when user does not have permission to some tables. Now the tables with no permission are ignored in listing. +* [\#1798](https://github.com/apache/iceberg/pull/1798) fixes scan task failure when encountering duplicate entries of data files. Spark and Flink readers can now ignore duplicated entries in data files for each scan task. +* [\#1785](https://github.com/apache/iceberg/pull/1785) fixes invalidation of metadata tables in `CachingCatalog`. When a table is dropped, all the metadata tables associated with it are also invalidated in the cache. +* [\#1960](https://github.com/apache/iceberg/pull/1960) fixes bug that ORC writer does not read metrics config and always use the default. Now customized metrics config is respected. + +Other notable changes: + +* NaN counts are now supported in metadata +* Shared catalog properties are added in core library to standardize catalog level configurations +* Spark and Flink now support dynamically loading customized `Catalog` and `FileIO` implementations +* Spark 2 now supports loading tables from other catalogs, like Spark 3 +* Spark 3 now supports catalog names in DataFrameReader when using Iceberg as a format +* Flink now uses the number of Iceberg read splits as its job parallelism to improve performance and save resource. +* Hive (experimental) now supports INSERT INTO, case insensitive query, projection pushdown, create DDL with schema and auto type conversion +* ORC now supports reading tinyint, smallint, char, varchar types +* Avro to Iceberg schema conversion now preserves field docs + + + +### 0.10.0 + +* Git tag: [0.10.0](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.10.0) +* [0.10.0 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.10.0/apache-iceberg-0.10.0.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.10.0/apache-iceberg-0.10.0.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.10.0/apache-iceberg-0.10.0.tar.gz.sha512) +* [0.10.0 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.10.0/iceberg-spark3-runtime-0.10.0.jar) +* [0.10.0 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.10.0/iceberg-spark-runtime-0.10.0.jar) +* [0.10.0 Flink runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-flink-runtime/0.10.0/iceberg-flink-runtime-0.10.0.jar) +* [0.10.0 Hive runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-hive-runtime/0.10.0/iceberg-hive-runtime-0.10.0.jar) + +High-level features: + +* **Format v2 support** for building row-level operations (`MERGE INTO`) in processing engines + * Note: format v2 is not yet finalized and does not have a forward-compatibility guarantee +* **Flink integration** for writing to Iceberg tables and reading from Iceberg tables (reading supports batch mode only) +* **Hive integration** for reading from Iceberg tables, with filter pushdown (experimental; configuration may change) + +Important bug fixes: + +* [\#1706](https://github.com/apache/iceberg/pull/1706) fixes non-vectorized ORC reads in Spark that incorrectly skipped rows +* [\#1536](https://github.com/apache/iceberg/pull/1536) fixes ORC conversion of `notIn` and `notEqual` to match null values +* [\#1722](https://github.com/apache/iceberg/pull/1722) fixes `Expressions.notNull` returning an `isNull` predicate; API only, method was not used by processing engines +* [\#1736](https://github.com/apache/iceberg/pull/1736) fixes `IllegalArgumentException` in vectorized Spark reads with negative decimal values +* [\#1666](https://github.com/apache/iceberg/pull/1666) fixes file lengths returned by the ORC writer, using compressed size rather than uncompressed size +* [\#1674](https://github.com/apache/iceberg/pull/1674) removes catalog expiration in HiveCatalogs +* [\#1545](https://github.com/apache/iceberg/pull/1545) automatically refreshes tables in Spark when not caching table instances + +Other notable changes: + +* The `iceberg-hive` module has been renamed to `iceberg-hive-metastore` to avoid confusion +* Spark 3 is based on 3.0.1 that includes the fix for [SPARK-32168](https://issues.apache.org/jira/browse/SPARK-32168) +* Hadoop tables will recover from version hint corruption +* Tables can be configured with a required sort order +* Data file locations can be customized with a dynamically loaded `LocationProvider` +* ORC file imports can apply a name mapping for stats + + +A more exhaustive list of changes is available under the [0.10.0 release milestone](https://github.com/apache/iceberg/milestone/10?closed=1). + +### 0.9.1 + +* Git tag: [0.9.1](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.9.1) +* [0.9.1 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.9.1/apache-iceberg-0.9.1.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.9.1/apache-iceberg-0.9.1.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.9.1/apache-iceberg-0.9.1.tar.gz.sha512) +* [0.9.1 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.9.1/iceberg-spark3-runtime-0.9.1.jar) +* [0.9.1 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.9.1/iceberg-spark-runtime-0.9.1.jar) + +### 0.9.0 + +* Git tag: [0.9.0](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.9.0) +* [0.9.0 source tar.gz](https://www.apache.org/dyn/closer.cgi/iceberg/apache-iceberg-0.9.0/apache-iceberg-0.9.0.tar.gz) -- [signature](https://downloads.apache.org/iceberg/apache-iceberg-0.9.0/apache-iceberg-0.9.0.tar.gz.asc) -- [sha512](https://downloads.apache.org/iceberg/apache-iceberg-0.9.0/apache-iceberg-0.9.0.tar.gz.sha512) +* [0.9.0 Spark 3.0 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark3-runtime/0.9.0/iceberg-spark3-runtime-0.9.0.jar) +* [0.9.0 Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.9.0/iceberg-spark-runtime-0.9.0.jar) + +### 0.8.0 + +* Git tag: [apache-iceberg-0.8.0-incubating](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.8.0-incubating) +* [0.8.0-incubating source tar.gz](https://www.apache.org/dyn/closer.cgi/incubator/iceberg/apache-iceberg-0.8.0-incubating/apache-iceberg-0.8.0-incubating.tar.gz) -- [signature](https://downloads.apache.org/incubator/iceberg/apache-iceberg-0.8.0-incubating/apache-iceberg-0.8.0-incubating.tar.gz.asc) -- [sha512](https://downloads.apache.org/incubator/iceberg/apache-iceberg-0.8.0-incubating/apache-iceberg-0.8.0-incubating.tar.gz.sha512) +* [0.8.0-incubating Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.8.0-incubating/iceberg-spark-runtime-0.8.0-incubating.jar) + + +### 0.7.0 + +* Git tag: [apache-iceberg-0.7.0-incubating](https://github.com/apache/iceberg/releases/tag/apache-iceberg-0.7.0-incubating) +* [0.7.0-incubating source tar.gz](https://www.apache.org/dyn/closer.cgi/incubator/iceberg/apache-iceberg-0.7.0-incubating/apache-iceberg-0.7.0-incubating.tar.gz) -- [signature](https://dist.apache.org/repos/dist/release/incubator/iceberg/apache-iceberg-0.7.0-incubating/apache-iceberg-0.7.0-incubating.tar.gz.asc) -- [sha512](https://dist.apache.org/repos/dist/release/incubator/iceberg/apache-iceberg-0.7.0-incubating/apache-iceberg-0.7.0-incubating.tar.gz.sha512) +* [0.7.0-incubating Spark 2.4 runtime Jar](https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime/0.7.0-incubating/iceberg-spark-runtime-0.7.0-incubating.jar) + diff --git a/site/docs/roadmap.md b/site/docs/roadmap.md new file mode 100644 index 000000000000..c0b0efe9dd8d --- /dev/null +++ b/site/docs/roadmap.md @@ -0,0 +1,47 @@ +--- +title: "Roadmap" +--- + + +# Roadmap Overview + +This roadmap outlines projects that the Iceberg community is working on, their priority, and a rough size estimate. +This is based on the latest [community priority discussion](https://lists.apache.org/thread.html/r84e80216c259c81f824c6971504c321cd8c785774c489d52d4fc123f%40%3Cdev.iceberg.apache.org%3E). +Each high-level item links to a Github project board that tracks the current status. +Related design docs will be linked on the planning boards. + +# Priority 1 + +* API: [Iceberg 1.0.0](https://github.com/apache/iceberg/projects/3) [medium] +* Python: [Pythonic refactor](https://github.com/apache/iceberg/projects/7) [medium] +* Spec: [Z-ordering / Space-filling curves](https://github.com/apache/iceberg/projects/16) [medium] +* Spec: [Snapshot tagging and branching](https://github.com/apache/iceberg/projects/4) [small] +* Views: [Spec](https://github.com/apache/iceberg/projects/6) [medium] +* Puffin: [Implement statistics information in table snapshot](https://github.com/apache/iceberg/pull/4741) [medium] +* Flink: [FLIP-27 based Iceberg source](https://github.com/apache/iceberg/projects/23) [large] + +# Priority 2 + +* ORC: [Support delete files stored as ORC](https://github.com/apache/iceberg/projects/13) [small] +* Spark: [DSv2 streaming improvements](https://github.com/apache/iceberg/projects/2) [small] +* Flink: [Inline file compaction](https://github.com/apache/iceberg/projects/14) [small] +* Flink: [Support UPSERT](https://github.com/apache/iceberg/projects/15) [small] +* Spec: [Secondary indexes](https://github.com/apache/iceberg/projects/17) [large] +* Spec v3: [Encryption](https://github.com/apache/iceberg/projects/5) [large] +* Spec v3: [Relative paths](https://github.com/apache/iceberg/projects/18) [large] +* Spec v3: [Default field values](https://github.com/apache/iceberg/projects/19) [medium] diff --git a/site/docs/security.md b/site/docs/security.md new file mode 100644 index 000000000000..ee0850263912 --- /dev/null +++ b/site/docs/security.md @@ -0,0 +1,33 @@ +--- +title: "Security" +--- + + +# Reporting Security Issues + +The Apache Iceberg Project uses the standard process outlined by the [Apache +Security Team](https://www.apache.org/security/) for reporting vulnerabilities. +Note that vulnerabilities should not be publicly disclosed until the project has +responded. + +To report a possible security vulnerability, please email
security@iceberg.apache.org. + + +# Verifying Signed Releases + +Please refer to the instructions on the [Release Verification](https://www.apache.org/info/verification.html) page. diff --git a/site/docs/spark-quickstart.md b/site/docs/spark-quickstart.md new file mode 100644 index 000000000000..cd5efdb44c00 --- /dev/null +++ b/site/docs/spark-quickstart.md @@ -0,0 +1,342 @@ +--- +title: "Spark and Iceberg Quickstart" +--- + + +## Spark and Iceberg Quickstart + +This guide will get you up and running with an Iceberg and Spark environment, including sample code to +highlight some powerful features. You can learn more about Iceberg's Spark runtime by checking out the [Spark](docs/latest/spark-ddl.md) section. + +- [Docker-Compose](#docker-compose) +- [Creating a table](#creating-a-table) +- [Writing Data to a Table](#writing-data-to-a-table) +- [Reading Data from a Table](#reading-data-from-a-table) +- [Adding A Catalog](#adding-a-catalog) +- [Next Steps](#next-steps) + +### Docker-Compose + +The fastest way to get started is to use a docker-compose file that uses the [tabulario/spark-iceberg](https://hub.docker.com/r/tabulario/spark-iceberg) image +which contains a local Spark cluster with a configured Iceberg catalog. To use this, you'll need to install the [Docker CLI](https://docs.docker.com/get-docker/) as well as the [Docker Compose CLI](https://github.com/docker/compose-cli/blob/main/INSTALL.md). + +Once you have those, save the yaml below into a file named `docker-compose.yml`: + +```yaml +version: "3" + +services: + spark-iceberg: + image: tabulario/spark-iceberg + container_name: spark-iceberg + build: spark/ + networks: + iceberg_net: + depends_on: + - rest + - minio + volumes: + - ./warehouse:/home/iceberg/warehouse + - ./notebooks:/home/iceberg/notebooks/notebooks + environment: + - AWS_ACCESS_KEY_ID=admin + - AWS_SECRET_ACCESS_KEY=password + - AWS_REGION=us-east-1 + ports: + - 8888:8888 + - 8080:8080 + - 10000:10000 + - 10001:10001 + rest: + image: tabulario/iceberg-rest + container_name: iceberg-rest + networks: + iceberg_net: + ports: + - 8181:8181 + environment: + - AWS_ACCESS_KEY_ID=admin + - AWS_SECRET_ACCESS_KEY=password + - AWS_REGION=us-east-1 + - CATALOG_WAREHOUSE=s3://warehouse/ + - CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO + - CATALOG_S3_ENDPOINT=http://minio:9000 + minio: + image: minio/minio + container_name: minio + environment: + - MINIO_ROOT_USER=admin + - MINIO_ROOT_PASSWORD=password + - MINIO_DOMAIN=minio + networks: + iceberg_net: + aliases: + - warehouse.minio + ports: + - 9001:9001 + - 9000:9000 + command: ["server", "/data", "--console-address", ":9001"] + mc: + depends_on: + - minio + image: minio/mc + container_name: mc + networks: + iceberg_net: + environment: + - AWS_ACCESS_KEY_ID=admin + - AWS_SECRET_ACCESS_KEY=password + - AWS_REGION=us-east-1 + entrypoint: > + /bin/sh -c " + until (/usr/bin/mc config host add minio http://minio:9000 admin password) do echo '...waiting...' && sleep 1; done; + /usr/bin/mc rm -r --force minio/warehouse; + /usr/bin/mc mb minio/warehouse; + /usr/bin/mc policy set public minio/warehouse; + tail -f /dev/null + " +networks: + iceberg_net: + +``` + +Next, start up the docker containers with this command: +```sh +docker-compose up +``` + +You can then run any of the following commands to start a Spark session. + +=== "SparkSQL" + + ``` sh + docker exec -it spark-iceberg spark-sql + ``` + +=== "Spark-Shell" + + ``` sh + docker exec -it spark-iceberg spark-shell + ``` + +=== "PySpark" + + ``` sh + docker exec -it spark-iceberg pyspark + ``` + +!!! note + + You can also launch a notebook server by running `docker exec -it spark-iceberg notebook`. + The notebook server will be available at [http://localhost:8888](http://localhost:8888) + +### Creating a table + +To create your first Iceberg table in Spark, run a [`CREATE TABLE`](docs/latest/spark-ddl.md#create-table) command. Let's create a table +using `demo.nyc.taxis` where `demo` is the catalog name, `nyc` is the database name, and `taxis` is the table name. + + +=== "SparkSQL" + + ```sql + CREATE TABLE demo.nyc.taxis + ( + vendor_id bigint, + trip_id bigint, + trip_distance float, + fare_amount double, + store_and_fwd_flag string + ) + PARTITIONED BY (vendor_id); + ``` + +=== "Spark-Shell" + + ```scala + import org.apache.spark.sql.types._ + import org.apache.spark.sql.Row + val schema = StructType( Array( + StructField("vendor_id", LongType,true), + StructField("trip_id", LongType,true), + StructField("trip_distance", FloatType,true), + StructField("fare_amount", DoubleType,true), + StructField("store_and_fwd_flag", StringType,true) + )) + val df = spark.createDataFrame(spark.sparkContext.emptyRDD[Row],schema) + df.writeTo("demo.nyc.taxis").create() + ``` + +=== "PySpark" + + ```py + from pyspark.sql.types import DoubleType, FloatType, LongType, StructType,StructField, StringType + schema = StructType([ + StructField("vendor_id", LongType(), True), + StructField("trip_id", LongType(), True), + StructField("trip_distance", FloatType(), True), + StructField("fare_amount", DoubleType(), True), + StructField("store_and_fwd_flag", StringType(), True) + ]) + + df = spark.createDataFrame([], schema) + df.writeTo("demo.nyc.taxis").create() + ``` + + +Iceberg catalogs support the full range of SQL DDL commands, including: + +* [`CREATE TABLE ... PARTITIONED BY`](docs/latest/spark-ddl.md#create-table) +* [`CREATE TABLE ... AS SELECT`](docs/latest/spark-ddl.md#create-table--as-select) +* [`ALTER TABLE`](docs/latest/spark-ddl.md#alter-table) +* [`DROP TABLE`](docs/latest/spark-ddl.md#drop-table) + +### Writing Data to a Table + +Once your table is created, you can insert records. + +=== "SparkSQL" + + ```sql + INSERT INTO demo.nyc.taxis + VALUES (1, 1000371, 1.8, 15.32, 'N'), (2, 1000372, 2.5, 22.15, 'N'), (2, 1000373, 0.9, 9.01, 'N'), (1, 1000374, 8.4, 42.13, 'Y'); + ``` + +=== "Spark-Shell" + + ```scala + import org.apache.spark.sql.Row + + val schema = spark.table("demo.nyc.taxis").schema + val data = Seq( + Row(1: Long, 1000371: Long, 1.8f: Float, 15.32: Double, "N": String), + Row(2: Long, 1000372: Long, 2.5f: Float, 22.15: Double, "N": String), + Row(2: Long, 1000373: Long, 0.9f: Float, 9.01: Double, "N": String), + Row(1: Long, 1000374: Long, 8.4f: Float, 42.13: Double, "Y": String) + ) + val df = spark.createDataFrame(spark.sparkContext.parallelize(data), schema) + df.writeTo("demo.nyc.taxis").append() + ``` + +=== "PySpark" + + ```py + schema = spark.table("demo.nyc.taxis").schema + data = [ + (1, 1000371, 1.8, 15.32, "N"), + (2, 1000372, 2.5, 22.15, "N"), + (2, 1000373, 0.9, 9.01, "N"), + (1, 1000374, 8.4, 42.13, "Y") + ] + df = spark.createDataFrame(data, schema) + df.writeTo("demo.nyc.taxis").append() + ``` + +### Reading Data from a Table + +To read a table, simply use the Iceberg table's name. + + +=== "SparkSQL" + + ```sql + SELECT * FROM demo.nyc.taxis; + ``` + +=== "Spark-Shell" + + ```scala + val df = spark.table("demo.nyc.taxis").show() + ``` + +=== "PySpark" + + ```py + df = spark.table("demo.nyc.taxis").show() + ``` + +### Adding A Catalog + +Iceberg has several catalog back-ends that can be used to track tables, like JDBC, Hive MetaStore and Glue. +Catalogs are configured using properties under `spark.sql.catalog.(catalog_name)`. In this guide, +we use JDBC, but you can follow these instructions to configure other catalog types. To learn more, check out +the [Catalog](docs/latest/spark-configuration.md#catalogs) page in the Spark section. + +This configuration creates a path-based catalog named `local` for tables under `$PWD/warehouse` and adds support for Iceberg tables to Spark's built-in catalog. + +=== "CLI" + + ```sh + spark-sql --packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }}\ + --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \ + --conf spark.sql.catalog.spark_catalog=org.apache.iceberg.spark.SparkSessionCatalog \ + --conf spark.sql.catalog.spark_catalog.type=hive \ + --conf spark.sql.catalog.local=org.apache.iceberg.spark.SparkCatalog \ + --conf spark.sql.catalog.local.type=hadoop \ + --conf spark.sql.catalog.local.warehouse=$PWD/warehouse \ + --conf spark.sql.defaultCatalog=local + ``` + +=== "spark-defaults.conf" + + ```sh + spark.jars.packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }} + spark.sql.extensions org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions + spark.sql.catalog.spark_catalog org.apache.iceberg.spark.SparkSessionCatalog + spark.sql.catalog.spark_catalog.type hive + spark.sql.catalog.local org.apache.iceberg.spark.SparkCatalog + spark.sql.catalog.local.type hadoop + spark.sql.catalog.local.warehouse $PWD/warehouse + spark.sql.defaultCatalog local + ``` + +!!! note + If your Iceberg catalog is not set as the default catalog, you will have to switch to it by executing `USE local;` + +### Next steps + +#### Adding Iceberg to Spark + +If you already have a Spark environment, you can add Iceberg, using the `--packages` option. + +=== "SparkSQL" + + ```sh + spark-sql --packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }} + ``` + +=== "Spark-Shell" + + ```sh + spark-shell --packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }} + ``` + +=== "PySpark" + + ```sh + pyspark --packages org.apache.iceberg:iceberg-spark-runtime-3.2_2.12:{{ icebergVersion }} + ``` + +!!! note + If you want to include Iceberg in your Spark installation, add the Iceberg Spark runtime to Spark's `jars` folder. + You can download the runtime by visiting to the [Releases](releases.md) page. + +[spark-runtime-jar]: https://search.maven.org/remotecontent?filepath=org/apache/iceberg/iceberg-spark-runtime-3.2_2.12/{{ icebergVersion }}/iceberg-spark-runtime-3.2_2.12-{{ icebergVersion }}.jar + +#### Learn More + +Now that you're up an running with Iceberg and Spark, check out the [Iceberg-Spark docs](docs/latest/spark-ddl.md) to learn more! diff --git a/site/docs/spec.md b/site/docs/spec.md new file mode 100644 index 000000000000..e1c1ed1b49c0 --- /dev/null +++ b/site/docs/spec.md @@ -0,0 +1,1259 @@ +--- +title: "Spec" +url: spec +toc: true +disableSidebar: true +--- + + +# Iceberg Table Spec + +This is a specification for the Iceberg table format that is designed to manage a large, slow-changing collection of files in a distributed file system or key-value store as a table. + +## Format Versioning + +Versions 1 and 2 of the Iceberg spec are complete and adopted by the community. + +The format version number is incremented when new features are added that will break forward-compatibility---that is, when older readers would not read newer table features correctly. Tables may continue to be written with an older version of the spec to ensure compatibility by not using features that are not yet implemented by processing engines. + +#### Version 1: Analytic Data Tables + +Version 1 of the Iceberg spec defines how to manage large analytic tables using immutable file formats: Parquet, Avro, and ORC. + +All version 1 data and metadata files are valid after upgrading a table to version 2. [Appendix E](#version-2) documents how to default version 2 fields when reading version 1 metadata. + +#### Version 2: Row-level Deletes + +Version 2 of the Iceberg spec adds row-level updates and deletes for analytic tables with immutable files. + +The primary change in version 2 adds delete files to encode rows that are deleted in existing data files. This version can be used to delete or replace individual rows in immutable data files without rewriting the files. + +In addition to row-level deletes, version 2 makes some requirements stricter for writers. The full set of changes are listed in [Appendix E](#version-2). + + +## Goals + +* **Serializable isolation** -- Reads will be isolated from concurrent writes and always use a committed snapshot of a table’s data. Writes will support removing and adding files in a single operation and are never partially visible. Readers will not acquire locks. +* **Speed** -- Operations will use O(1) remote calls to plan the files for a scan and not O(n) where n grows with the size of the table, like the number of partitions or files. +* **Scale** -- Job planning will be handled primarily by clients and not bottleneck on a central metadata store. Metadata will include information needed for cost-based optimization. +* **Evolution** -- Tables will support full schema and partition spec evolution. Schema evolution supports safe column add, drop, reorder and rename, including in nested structures. +* **Dependable types** -- Tables will provide well-defined and dependable support for a core set of types. +* **Storage separation** -- Partitioning will be table configuration. Reads will be planned using predicates on data values, not partition values. Tables will support evolving partition schemes. +* **Formats** -- Underlying data file formats will support identical schema evolution rules and types. Both read-optimized and write-optimized formats will be available. + +## Overview + +![Iceberg snapshot structure](assets/images/iceberg-metadata.png) + +This table format tracks individual data files in a table instead of directories. This allows writers to create data files in-place and only adds files to the table in an explicit commit. + +Table state is maintained in metadata files. All changes to table state create a new metadata file and replace the old metadata with an atomic swap. The table metadata file tracks the table schema, partitioning config, custom properties, and snapshots of the table contents. A snapshot represents the state of a table at some time and is used to access the complete set of data files in the table. + +Data files in snapshots are tracked by one or more manifest files that contain a row for each data file in the table, the file's partition data, and its metrics. The data in a snapshot is the union of all files in its manifests. Manifest files are reused across snapshots to avoid rewriting metadata that is slow-changing. Manifests can track data files with any subset of a table and are not associated with partitions. + +The manifests that make up a snapshot are stored in a manifest list file. Each manifest list stores metadata about manifests, including partition stats and data file counts. These stats are used to avoid reading manifests that are not required for an operation. + +#### Optimistic Concurrency + +An atomic swap of one table metadata file for another provides the basis for serializable isolation. Readers use the snapshot that was current when they load the table metadata and are not affected by changes until they refresh and pick up a new metadata location. + +Writers create table metadata files optimistically, assuming that the current version will not be changed before the writer's commit. Once a writer has created an update, it commits by swapping the table’s metadata file pointer from the base version to the new version. + +If the snapshot on which an update is based is no longer current, the writer must retry the update based on the new current version. Some operations support retry by re-applying metadata changes and committing, under well-defined conditions. For example, a change that rewrites files can be applied to a new table snapshot if all of the rewritten files are still in the table. + +The conditions required by a write to successfully commit determines the isolation level. Writers can select what to validate and can make different isolation guarantees. + +#### Sequence Numbers + +The relative age of data and delete files relies on a sequence number that is assigned to every successful commit. When a snapshot is created for a commit, it is optimistically assigned the next sequence number, and it is written into the snapshot's metadata. If the commit fails and must be retried, the sequence number is reassigned and written into new snapshot metadata. + +All manifests, data files, and delete files created for a snapshot inherit the snapshot's sequence number. Manifest file metadata in the manifest list stores a manifest's sequence number. New data and metadata file entries are written with `null` in place of a sequence number, which is replaced with the manifest's sequence number at read time. When a data or delete file is written to a new manifest (as "existing"), the inherited sequence number is written to ensure it does not change after it is first inherited. + +Inheriting the sequence number from manifest metadata allows writing a new manifest once and reusing it in commit retries. To change a sequence number for a retry, only the manifest list must be rewritten -- which would be rewritten anyway with the latest set of manifests. + + +#### Row-level Deletes + +Row-level deletes are stored in delete files. + +There are two ways to encode a row-level delete: + +* [_Position deletes_](#position-delete-files) mark a row deleted by data file path and the row position in the data file +* [_Equality deletes_](#equality-delete-files) mark a row deleted by one or more column values, like `id = 5` + +Like data files, delete files are tracked by partition. In general, a delete file must be applied to older data files with the same partition; see [Scan Planning](#scan-planning) for details. Column metrics can be used to determine whether a delete file's rows overlap the contents of a data file or a scan range. + + +#### File System Operations + +Iceberg only requires that file systems support the following operations: + +* **In-place write** -- Files are not moved or altered once they are written. +* **Seekable reads** -- Data file formats require seek support. +* **Deletes** -- Tables delete files that are no longer used. + +These requirements are compatible with object stores, like S3. + +Tables do not require random-access writes. Once written, data and metadata files are immutable until they are deleted. + +Tables do not require rename, except for tables that use atomic rename to implement the commit operation for new metadata files. + + +## Specification + +#### Terms + +* **Schema** -- Names and types of fields in a table. +* **Partition spec** -- A definition of how partition values are derived from data fields. +* **Snapshot** -- The state of a table at some point in time, including the set of all data files. +* **Manifest list** -- A file that lists manifest files; one per snapshot. +* **Manifest** -- A file that lists data or delete files; a subset of a snapshot. +* **Data file** -- A file that contains rows of a table. +* **Delete file** -- A file that encodes rows of a table that are deleted by position or data values. + +#### Writer requirements + +Some tables in this spec have columns that specify requirements for v1 and v2 tables. These requirements are intended for writers when adding metadata files to a table with the given version. + +| Requirement | Write behavior | +|-------------|----------------| +| (blank) | The field should be omitted | +| _optional_ | The field can be written | +| _required_ | The field must be written | + +Readers should be more permissive because v1 metadata files are allowed in v2 tables so that tables can be upgraded to v2 without rewriting the metadata tree. For manifest list and manifest files, this table shows the expected v2 read behavior: + +| v1 | v2 | v2 read behavior | +|------------|------------|------------------| +| | _optional_ | Read the field as _optional_ | +| | _required_ | Read the field as _optional_; it may be missing in v1 files | +| _optional_ | | Ignore the field | +| _optional_ | _optional_ | Read the field as _optional_ | +| _optional_ | _required_ | Read the field as _optional_; it may be missing in v1 files | +| _required_ | | Ignore the field | +| _required_ | _optional_ | Read the field as _optional_ | +| _required_ | _required_ | Fill in a default or throw an exception if the field is missing | + +Readers may be more strict for metadata JSON files because the JSON files are not reused and will always match the table version. Required v2 fields that were not present in v1 or optional in v1 may be handled as required fields. For example, a v2 table that is missing `last-sequence-number` can throw an exception. + +### Schemas and Data Types + +A table's **schema** is a list of named columns. All data types are either primitives or nested types, which are maps, lists, or structs. A table schema is also a struct type. + +For the representations of these types in Avro, ORC, and Parquet file formats, see Appendix A. + +#### Nested Types + +A **`struct`** is a tuple of typed values. Each field in the tuple is named and has an integer id that is unique in the table schema. Each field can be either optional or required, meaning that values can (or cannot) be null. Fields may be any type. Fields may have an optional comment or doc string. Fields can have [default values](#default-values). + +A **`list`** is a collection of values with some element type. The element field has an integer id that is unique in the table schema. Elements can be either optional or required. Element types may be any type. + +A **`map`** is a collection of key-value pairs with a key type and a value type. Both the key field and value field each have an integer id that is unique in the table schema. Map keys are required and map values can be either optional or required. Both map keys and map values may be any type, including nested types. + +#### Primitive Types + +| Primitive type | Description | Requirements | +|--------------------|--------------------------------------------------------------------------|--------------------------------------------------| +| **`boolean`** | True or false | | +| **`int`** | 32-bit signed integers | Can promote to `long` | +| **`long`** | 64-bit signed integers | | +| **`float`** | [32-bit IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) floating point | Can promote to double | +| **`double`** | [64-bit IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) floating point | | +| **`decimal(P,S)`** | Fixed-point decimal; precision P, scale S | Scale is fixed [1], precision must be 38 or less | +| **`date`** | Calendar date without timezone or time | | +| **`time`** | Time of day without date, timezone | Microsecond precision [2] | +| **`timestamp`** | Timestamp without timezone | Microsecond precision [2] | +| **`timestamptz`** | Timestamp with timezone | Stored as UTC [2] | +| **`string`** | Arbitrary-length character sequences | Encoded with UTF-8 [3] | +| **`uuid`** | Universally unique identifiers | Should use 16-byte fixed | +| **`fixed(L)`** | Fixed-length byte array of length L | | +| **`binary`** | Arbitrary-length byte array | | + +Notes: + +1. Decimal scale is fixed and cannot be changed by schema evolution. Precision can only be widened. +2. All time and timestamp values are stored with microsecond precision. + - Timestamps _with time zone_ represent a point in time: values are stored as UTC and do not retain a source time zone (`2017-11-16 17:10:34 PST` is stored/retrieved as `2017-11-17 01:10:34 UTC` and these values are considered identical). + - Timestamps _without time zone_ represent a date and time of day regardless of zone: the time value is independent of zone adjustments (`2017-11-16 17:10:34` is always retrieved as `2017-11-16 17:10:34`). Timestamp values are stored as a long that encodes microseconds from the unix epoch. +3. Character strings must be stored as UTF-8 encoded byte arrays. + +For details on how to serialize a schema to JSON, see Appendix C. + + +#### Default values + +Default values can be tracked for struct fields (both nested structs and the top-level schema's struct). There can be two defaults with a field: +- `initial-default` is used to populate the field's value for all records that were written before the field was added to the schema +- `write-default` is used to populate the field's value for any records written after the field was added to the schema, if the writer does not supply the field's value + +The `initial-default` is set only when a field is added to an existing schema. The `write-default` is initially set to the same value as `initial-default` and can be changed through schema evolution. If either default is not set for an optional field, then the default value is null for compatibility with older spec versions. + +The `initial-default` and `write-default` produce SQL default value behavior, without rewriting data files. SQL default value behavior when a field is added handles all existing rows as though the rows were written with the new field's default value. Default value changes may only affect future records and all known fields are written into data files. Omitting a known field when writing a data file is never allowed. The write default for a field must be written if a field is not supplied to a write. If the write default for a required field is not set, the writer must fail. + +Default values are attributes of fields in schemas and serialized with fields in the JSON format. See [Appendix C](#appendix-c-json-serialization). + + +#### Schema Evolution + +Schemas may be evolved by type promotion or adding, deleting, renaming, or reordering fields in structs (both nested structs and the top-level schema’s struct). + +Evolution applies changes to the table's current schema to produce a new schema that is identified by a unique schema ID, is added to the table's list of schemas, and is set as the table's current schema. + +Valid type promotions are: + +* `int` to `long` +* `float` to `double` +* `decimal(P, S)` to `decimal(P', S)` if `P' > P` -- widen the precision of decimal types. + +Any struct, including a top-level schema, can evolve through deleting fields, adding new fields, renaming existing fields, reordering existing fields, or promoting a primitive using the valid type promotions. Adding a new field assigns a new ID for that field and for any nested fields. Renaming an existing field must change the name, but not the field ID. Deleting a field removes it from the current schema. Field deletion cannot be rolled back unless the field was nullable or if the current snapshot has not changed. + +Grouping a subset of a struct’s fields into a nested struct is **not** allowed, nor is moving fields from a nested struct into its immediate parent struct (`struct ↔ struct>`). Evolving primitive types to structs is **not** allowed, nor is evolving a single-field struct to a primitive (`map ↔ map>`). + +Struct evolution requires the following rules for default values: +* The `initial-default` must be set when a field is added and cannot change +* The `write-default` must be set when a field is added and may change +* When a required field is added, both defaults must be set to a non-null value +* When an optional field is added, the defaults may be null and should be explicitly set +* When a new field is added to a struct with a default value, updating the struct's default is optional +* If a field value is missing from a struct's `initial-default`, the field's `initial-default` must be used for the field +* If a field value is missing from a struct's `write-default`, the field's `write-default` must be used for the field + + +#### Column Projection + +Columns in Iceberg data files are selected by field id. The table schema's column names and order may change after a data file is written, and projection must be done using field ids. If a field id is missing from a data file, its value for each row should be `null`. + +For example, a file may be written with schema `1: a int, 2: b string, 3: c double` and read using projection schema `3: measurement, 2: name, 4: a`. This must select file columns `c` (renamed to `measurement`), `b` (now called `name`), and a column of `null` values called `a`; in that order. + +Tables may also define a property `schema.name-mapping.default` with a JSON name mapping containing a list of field mapping objects. These mappings provide fallback field ids to be used when a data file does not contain field id information. Each object should contain + +* `names`: A required list of 0 or more names for a field. +* `field-id`: An optional Iceberg field ID used when a field's name is present in `names` +* `fields`: An optional list of field mappings for child field of structs, maps, and lists. + +Field mapping fields are constrained by the following rules: + +* A name may contain `.` but this refers to a literal name, not a nested field. For example, `a.b` refers to a field named `a.b`, not child field `b` of field `a`. +* Each child field should be defined with their own field mapping under `fields`. +* Multiple values for `names` may be mapped to a single field ID to support cases where a field may have different names in different data files. For example, all Avro field aliases should be listed in `names`. +* Fields which exist only in the Iceberg schema and not in imported data files may use an empty `names` list. +* Fields that exist in imported files but not in the Iceberg schema may omit `field-id`. +* List types should contain a mapping in `fields` for `element`. +* Map types should contain mappings in `fields` for `key` and `value`. +* Struct types should contain mappings in `fields` for their child fields. + +For details on serialization, see [Appendix C](#name-mapping-serialization). + +#### Identifier Field IDs + +A schema can optionally track the set of primitive fields that identify rows in a table, using the property `identifier-field-ids` (see JSON encoding in Appendix C). + +Two rows are the "same"---that is, the rows represent the same entity---if the identifier fields are equal. However, uniqueness of rows by this identifier is not guaranteed or required by Iceberg and it is the responsibility of processing engines or data providers to enforce. + +Identifier fields may be nested in structs but cannot be nested within maps or lists. Float, double, and optional fields cannot be used as identifier fields and a nested field cannot be used as an identifier field if it is nested in an optional struct, to avoid null values in identifiers. + + +#### Reserved Field IDs + +Iceberg tables must not use field ids greater than 2147483447 (`Integer.MAX_VALUE - 200`). This id range is reserved for metadata columns that can be used in user data schemas, like the `_file` column that holds the file path in which a row was stored. + +The set of metadata columns is: + +| Field id, name | Type | Description | +|-----------------------------|---------------|-------------| +| **`2147483646 _file`** | `string` | Path of the file in which a row is stored | +| **`2147483645 _pos`** | `long` | Ordinal position of a row in the source data file | +| **`2147483644 _deleted`** | `boolean` | Whether the row has been deleted | +| **`2147483643 _spec_id`** | `int` | Spec ID used to track the file containing a row | +| **`2147483642 _partition`** | `struct` | Partition to which a row belongs | +| **`2147483546 file_path`** | `string` | Path of a file, used in position-based delete files | +| **`2147483545 pos`** | `long` | Ordinal position of a row, used in position-based delete files | +| **`2147483544 row`** | `struct<...>` | Deleted row values, used in position-based delete files | + + +### Partitioning + +Data files are stored in manifests with a tuple of partition values that are used in scans to filter out files that cannot contain records that match the scan’s filter predicate. Partition values for a data file must be the same for all records stored in the data file. (Manifests store data files from any partition, as long as the partition spec is the same for the data files.) + +Tables are configured with a **partition spec** that defines how to produce a tuple of partition values from a record. A partition spec has a list of fields that consist of: + +* A **source column id** from the table’s schema +* A **partition field id** that is used to identify a partition field and is unique within a partition spec. In v2 table metadata, it is unique across all partition specs. +* A **transform** that is applied to the source column to produce a partition value +* A **partition name** + +The source column, selected by id, must be a primitive type and cannot be contained in a map or list, but may be nested in a struct. For details on how to serialize a partition spec to JSON, see Appendix C. + +Partition specs capture the transform from table data to partition values. This is used to transform predicates to partition predicates, in addition to transforming data values. Deriving partition predicates from column predicates on the table data is used to separate the logical queries from physical storage: the partitioning can change and the correct partition filters are always derived from column predicates. This simplifies queries because users don’t have to supply both logical predicates and partition predicates. For more information, see Scan Planning below. + + +#### Partition Transforms + +| Transform name | Description | Source types | Result type | +|-------------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|-------------| +| **`identity`** | Source value, unmodified | Any | Source type | +| **`bucket[N]`** | Hash of value, mod `N` (see below) | `int`, `long`, `decimal`, `date`, `time`, `timestamp`, `timestamptz`, `string`, `uuid`, `fixed`, `binary` | `int` | +| **`truncate[W]`** | Value truncated to width `W` (see below) | `int`, `long`, `decimal`, `string` | Source type | +| **`year`** | Extract a date or timestamp year, as years from 1970 | `date`, `timestamp`, `timestamptz` | `int` | +| **`month`** | Extract a date or timestamp month, as months from 1970-01-01 | `date`, `timestamp`, `timestamptz` | `int` | +| **`day`** | Extract a date or timestamp day, as days from 1970-01-01 | `date`, `timestamp`, `timestamptz` | `int` | +| **`hour`** | Extract a timestamp hour, as hours from 1970-01-01 00:00:00 | `timestamp`, `timestamptz` | `int` | +| **`void`** | Always produces `null` | Any | Source type or `int` | + +All transforms must return `null` for a `null` input value. + +The `void` transform may be used to replace the transform in an existing partition field so that the field is effectively dropped in v1 tables. See partition evolution below. + + +#### Bucket Transform Details + +Bucket partition transforms use a 32-bit hash of the source value. The 32-bit hash implementation is the 32-bit Murmur3 hash, x86 variant, seeded with 0. + +Transforms are parameterized by a number of buckets [1], `N`. The hash mod `N` must produce a positive value by first discarding the sign bit of the hash value. In pseudo-code, the function is: + +``` + def bucket_N(x) = (murmur3_x86_32_hash(x) & Integer.MAX_VALUE) % N +``` + +Notes: + +1. Changing the number of buckets as a table grows is possible by evolving the partition spec. + +For hash function details by type, see Appendix B. + + +#### Truncate Transform Details + +| **Type** | **Config** | **Truncate specification** | **Examples** | +|---------------|-----------------------|------------------------------------------------------------------|----------------------------------| +| **`int`** | `W`, width | `v - (v % W)` remainders must be positive [1] | `W=10`: `1` → `0`, `-1` → `-10` | +| **`long`** | `W`, width | `v - (v % W)` remainders must be positive [1] | `W=10`: `1` → `0`, `-1` → `-10` | +| **`decimal`** | `W`, width (no scale) | `scaled_W = decimal(W, scale(v))` `v - (v % scaled_W)` [1, 2] | `W=50`, `s=2`: `10.65` → `10.50` | +| **`string`** | `L`, length | Substring of length `L`: `v.substring(0, L)` [3] | `L=3`: `iceberg` → `ice` | + +Notes: + +1. The remainder, `v % W`, must be positive. For languages where `%` can produce negative values, the correct truncate function is: `v - (((v % W) + W) % W)` +2. The width, `W`, used to truncate decimal values is applied using the scale of the decimal column to avoid additional (and potentially conflicting) parameters. +3. Strings are truncated to a valid UTF-8 string with no more than `L` code points. + + +#### Partition Evolution + +Table partitioning can be evolved by adding, removing, renaming, or reordering partition spec fields. + +Changing a partition spec produces a new spec identified by a unique spec ID that is added to the table's list of partition specs and may be set as the table's default spec. + +When evolving a spec, changes should not cause partition field IDs to change because the partition field IDs are used as the partition tuple field IDs in manifest files. + +In v2, partition field IDs must be explicitly tracked for each partition field. New IDs are assigned based on the last assigned partition ID in table metadata. + +In v1, partition field IDs were not tracked, but were assigned sequentially starting at 1000 in the reference implementation. This assignment caused problems when reading metadata tables based on manifest files from multiple specs because partition fields with the same ID may contain different data types. For compatibility with old versions, the following rules are recommended for partition evolution in v1 tables: + +1. Do not reorder partition fields +2. Do not drop partition fields; instead replace the field's transform with the `void` transform +3. Only add partition fields at the end of the previous partition spec + + +### Sorting + +Users can sort their data within partitions by columns to gain performance. The information on how the data is sorted can be declared per data or delete file, by a **sort order**. + +A sort order is defined by a sort order id and a list of sort fields. The order of the sort fields within the list defines the order in which the sort is applied to the data. Each sort field consists of: + +* A **source column id** from the table's schema +* A **transform** that is used to produce values to be sorted on from the source column. This is the same transform as described in [partition transforms](#partition-transforms). +* A **sort direction**, that can only be either `asc` or `desc` +* A **null order** that describes the order of null values when sorted. Can only be either `nulls-first` or `nulls-last` + +Order id `0` is reserved for the unsorted order. + +Sorting floating-point numbers should produce the following behavior: `-NaN` < `-Infinity` < `-value` < `-0` < `0` < `value` < `Infinity` < `NaN`. This aligns with the implementation of Java floating-point types comparisons. + +A data or delete file is associated with a sort order by the sort order's id within [a manifest](#manifests). Therefore, the table must declare all the sort orders for lookup. A table could also be configured with a default sort order id, indicating how the new data should be sorted by default. Writers should use this default sort order to sort the data on write, but are not required to if the default order is prohibitively expensive, as it would be for streaming writes. + + +### Manifests + +A manifest is an immutable Avro file that lists data files or delete files, along with each file’s partition data tuple, metrics, and tracking information. One or more manifest files are used to store a [snapshot](#snapshots), which tracks all of the files in a table at some point in time. Manifests are tracked by a [manifest list](#manifest-lists) for each table snapshot. + +A manifest is a valid Iceberg data file: files must use valid Iceberg formats, schemas, and column projection. + +A manifest may store either data files or delete files, but not both because manifests that contain delete files are scanned first during job planning. Whether a manifest is a data manifest or a delete manifest is stored in manifest metadata. + +A manifest stores files for a single partition spec. When a table’s partition spec changes, old files remain in the older manifest and newer files are written to a new manifest. This is required because a manifest file’s schema is based on its partition spec (see below). The partition spec of each manifest is also used to transform predicates on the table's data rows into predicates on partition values that are used during job planning to select files from a manifest. + +A manifest file must store the partition spec and other metadata as properties in the Avro file's key-value metadata: + +| v1 | v2 | Key | Value | +|------------|------------|---------------------|------------------------------------------------------------------------------| +| _required_ | _required_ | `schema` | JSON representation of the table schema at the time the manifest was written | +| _optional_ | _required_ | `schema-id` | ID of the schema used to write the manifest as a string | +| _required_ | _required_ | `partition-spec` | JSON fields representation of the partition spec used to write the manifest | +| _optional_ | _required_ | `partition-spec-id` | ID of the partition spec used to write the manifest as a string | +| _optional_ | _required_ | `format-version` | Table format version number of the manifest as a string | +| | _required_ | `content` | Type of content files tracked by the manifest: "data" or "deletes" | + +The schema of a manifest file is a struct called `manifest_entry` with the following fields: + +| v1 | v2 | Field id, name | Type | Description | +| ---------- | ---------- |-------------------------------|-----------------------------------------------------------|-------------| +| _required_ | _required_ | **`0 status`** | `int` with meaning: `0: EXISTING` `1: ADDED` `2: DELETED` | Used to track additions and deletions. Deletes are informational only and not used in scans. | +| _required_ | _optional_ | **`1 snapshot_id`** | `long` | Snapshot id where the file was added, or deleted if status is 2. Inherited when null. | +| | _optional_ | **`3 sequence_number`** | `long` | Data sequence number of the file. Inherited when null and status is 1 (added). | +| | _optional_ | **`4 file_sequence_number`** | `long` | File sequence number indicating when the file was added. Inherited when null and status is 1 (added). | +| _required_ | _required_ | **`2 data_file`** | `data_file` `struct` (see below) | File path, partition tuple, metrics, ... | + +`data_file` is a struct with the following fields: + +| v1 | v2 | Field id, name | Type | Description | +| ---------- | ---------- |-----------------------------------|------------------------------|-------------| +| | _required_ | **`134 content`** | `int` with meaning: `0: DATA`, `1: POSITION DELETES`, `2: EQUALITY DELETES` | Type of content stored by the data file: data, equality deletes, or position deletes (all v1 files are data files) | +| _required_ | _required_ | **`100 file_path`** | `string` | Full URI for the file with FS scheme | +| _required_ | _required_ | **`101 file_format`** | `string` | String file format name, avro, orc or parquet | +| _required_ | _required_ | **`102 partition`** | `struct<...>` | Partition data tuple, schema based on the partition spec output using partition field ids for the struct field ids | +| _required_ | _required_ | **`103 record_count`** | `long` | Number of records in this file | +| _required_ | _required_ | **`104 file_size_in_bytes`** | `long` | Total file size in bytes | +| _required_ | | ~~**`105 block_size_in_bytes`**~~ | `long` | **Deprecated. Always write a default in v1. Do not write in v2.** | +| _optional_ | | ~~**`106 file_ordinal`**~~ | `int` | **Deprecated. Do not write.** | +| _optional_ | | ~~**`107 sort_columns`**~~ | `list<112: int>` | **Deprecated. Do not write.** | +| _optional_ | _optional_ | **`108 column_sizes`** | `map<117: int, 118: long>` | Map from column id to the total size on disk of all regions that store the column. Does not include bytes necessary to read other columns, like footers. Leave null for row-oriented formats (Avro) | +| _optional_ | _optional_ | **`109 value_counts`** | `map<119: int, 120: long>` | Map from column id to number of values in the column (including null and NaN values) | +| _optional_ | _optional_ | **`110 null_value_counts`** | `map<121: int, 122: long>` | Map from column id to number of null values in the column | +| _optional_ | _optional_ | **`137 nan_value_counts`** | `map<138: int, 139: long>` | Map from column id to number of NaN values in the column | +| _optional_ | _optional_ | **`111 distinct_counts`** | `map<123: int, 124: long>` | Map from column id to number of distinct values in the column; distinct counts must be derived using values in the file by counting or using sketches, but not using methods like merging existing distinct counts | +| _optional_ | _optional_ | **`125 lower_bounds`** | `map<126: int, 127: binary>` | Map from column id to lower bound in the column serialized as binary [1]. Each value must be less than or equal to all non-null, non-NaN values in the column for the file [2] | +| _optional_ | _optional_ | **`128 upper_bounds`** | `map<129: int, 130: binary>` | Map from column id to upper bound in the column serialized as binary [1]. Each value must be greater than or equal to all non-null, non-Nan values in the column for the file [2] | +| _optional_ | _optional_ | **`131 key_metadata`** | `binary` | Implementation-specific key metadata for encryption | +| _optional_ | _optional_ | **`132 split_offsets`** | `list<133: long>` | Split offsets for the data file. For example, all row group offsets in a Parquet file. Must be sorted ascending | +| | _optional_ | **`135 equality_ids`** | `list<136: int>` | Field ids used to determine row equality in equality delete files. Required when `content=2` and should be null otherwise. Fields with ids listed in this column must be present in the delete file | +| _optional_ | _optional_ | **`140 sort_order_id`** | `int` | ID representing sort order for this file [3]. | + +Notes: + +1. Single-value serialization for lower and upper bounds is detailed in Appendix D. +2. For `float` and `double`, the value `-0.0` must precede `+0.0`, as in the IEEE 754 `totalOrder` predicate. NaNs are not permitted as lower or upper bounds. +3. If sort order ID is missing or unknown, then the order is assumed to be unsorted. Only data files and equality delete files should be written with a non-null order id. [Position deletes](#position-delete-files) are required to be sorted by file and position, not a table order, and should set sort order id to null. Readers must ignore sort order id for position delete files. +4. The following field ids are reserved on `data_file`: 141. + +The `partition` struct stores the tuple of partition values for each file. Its type is derived from the partition fields of the partition spec used to write the manifest file. In v2, the partition struct's field ids must match the ids from the partition spec. + +The column metrics maps are used when filtering to select both data and delete files. For delete files, the metrics must store bounds and counts for all deleted rows, or must be omitted. Storing metrics for deleted rows ensures that the values can be used during job planning to find delete files that must be merged during a scan. + + +#### Manifest Entry Fields + +The manifest entry fields are used to keep track of the snapshot in which files were added or logically deleted. The `data_file` struct is nested inside of the manifest entry so that it can be easily passed to job planning without the manifest entry fields. + +When a file is added to the dataset, its manifest entry should store the snapshot ID in which the file was added and set status to 1 (added). + +When a file is replaced or deleted from the dataset, its manifest entry fields store the snapshot ID in which the file was deleted and status 2 (deleted). The file may be deleted from the file system when the snapshot in which it was deleted is garbage collected, assuming that older snapshots have also been garbage collected [1]. + +Iceberg v2 adds data and file sequence numbers to the entry and makes the snapshot ID optional. Values for these fields are inherited from manifest metadata when `null`. That is, if the field is `null` for an entry, then the entry must inherit its value from the manifest file's metadata, stored in the manifest list. +The `sequence_number` field represents the data sequence number and must never change after a file is added to the dataset. The data sequence number represents a relative age of the file content and should be used for planning which delete files apply to a data file. +The `file_sequence_number` field represents the sequence number of the snapshot that added the file and must also remain unchanged upon assigning at commit. The file sequence number can't be used for pruning delete files as the data within the file may have an older data sequence number. +The data and file sequence numbers are inherited only if the entry status is 1 (added). If the entry status is 0 (existing) or 2 (deleted), the entry must include both sequence numbers explicitly. + +Notes: + +1. Technically, data files can be deleted when the last snapshot that contains the file as “live” data is garbage collected. But this is harder to detect and requires finding the diff of multiple snapshots. It is easier to track what files are deleted in a snapshot and delete them when that snapshot expires. It is not recommended to add a deleted file back to a table. Adding a deleted file can lead to edge cases where incremental deletes can break table snapshots. +2. Manifest list files are required in v2, so that the `sequence_number` and `snapshot_id` to inherit are always available. + +#### Sequence Number Inheritance + +Manifests track the sequence number when a data or delete file was added to the table. + +When adding a new file, its data and file sequence numbers are set to `null` because the snapshot's sequence number is not assigned until the snapshot is successfully committed. When reading, sequence numbers are inherited by replacing `null` with the manifest's sequence number from the manifest list. +It is also possible to add a new file with data that logically belongs to an older sequence number. In that case, the data sequence number must be provided explicitly and not inherited. However, the file sequence number must be always assigned when the snapshot is successfully committed. + +When writing an existing file to a new manifest or marking an existing file as deleted, the data and file sequence numbers must be non-null and set to the original values that were either inherited or provided at the commit time. + +Inheriting sequence numbers through the metadata tree allows writing a new manifest without a known sequence number, so that a manifest can be written once and reused in commit retries. To change a sequence number for a retry, only the manifest list must be rewritten. + +When reading v1 manifests with no sequence number column, sequence numbers for all files must default to 0. + + +### Snapshots + +A snapshot consists of the following fields: + +| v1 | v2 | Field | Description | +| ---------- | ---------- | ------------------------ | ----------- | +| _required_ | _required_ | **`snapshot-id`** | A unique long ID | +| _optional_ | _optional_ | **`parent-snapshot-id`** | The snapshot ID of the snapshot's parent. Omitted for any snapshot with no parent | +| | _required_ | **`sequence-number`** | A monotonically increasing long that tracks the order of changes to a table | +| _required_ | _required_ | **`timestamp-ms`** | A timestamp when the snapshot was created, used for garbage collection and table inspection | +| _optional_ | _required_ | **`manifest-list`** | The location of a manifest list for this snapshot that tracks manifest files with additional metadata | +| _optional_ | | **`manifests`** | A list of manifest file locations. Must be omitted if `manifest-list` is present | +| _optional_ | _required_ | **`summary`** | A string map that summarizes the snapshot changes, including `operation` (see below) | +| _optional_ | _optional_ | **`schema-id`** | ID of the table's current schema when the snapshot was created | + +The snapshot summary's `operation` field is used by some operations, like snapshot expiration, to skip processing certain snapshots. Possible `operation` values are: + +* `append` -- Only data files were added and no files were removed. +* `replace` -- Data and delete files were added and removed without changing table data; i.e., compaction, changing the data file format, or relocating data files. +* `overwrite` -- Data and delete files were added and removed in a logical overwrite operation. +* `delete` -- Data files were removed and their contents logically deleted and/or delete files were added to delete rows. + +Data and delete files for a snapshot can be stored in more than one manifest. This enables: + +* Appends can add a new manifest to minimize the amount of data written, instead of adding new records by rewriting and appending to an existing manifest. (This is called a “fast append”.) +* Tables can use multiple partition specs. A table’s partition configuration can evolve if, for example, its data volume changes. Each manifest uses a single partition spec, and queries do not need to change because partition filters are derived from data predicates. +* Large tables can be split across multiple manifests so that implementations can parallelize job planning or reduce the cost of rewriting a manifest. + +Manifests for a snapshot are tracked by a manifest list. + +Valid snapshots are stored as a list in table metadata. For serialization, see Appendix C. + + +#### Manifest Lists + +Snapshots are embedded in table metadata, but the list of manifests for a snapshot are stored in a separate manifest list file. + +A new manifest list is written for each attempt to commit a snapshot because the list of manifests always changes to produce a new snapshot. When a manifest list is written, the (optimistic) sequence number of the snapshot is written for all new manifest files tracked by the list. + +A manifest list includes summary metadata that can be used to avoid scanning all of the manifests in a snapshot when planning a table scan. This includes the number of added, existing, and deleted files, and a summary of values for each field of the partition spec used to write the manifest. + +A manifest list is a valid Iceberg data file: files must use valid Iceberg formats, schemas, and column projection. + +Manifest list files store `manifest_file`, a struct with the following fields: + +| v1 | v2 | Field id, name | Type | Description | +| ---------- | ---------- |--------------------------------|---------------------------------------------|-------------| +| _required_ | _required_ | **`500 manifest_path`** | `string` | Location of the manifest file | +| _required_ | _required_ | **`501 manifest_length`** | `long` | Length of the manifest file in bytes | +| _required_ | _required_ | **`502 partition_spec_id`** | `int` | ID of a partition spec used to write the manifest; must be listed in table metadata `partition-specs` | +| | _required_ | **`517 content`** | `int` with meaning: `0: data`, `1: deletes` | The type of files tracked by the manifest, either data or delete files; 0 for all v1 manifests | +| | _required_ | **`515 sequence_number`** | `long` | The sequence number when the manifest was added to the table; use 0 when reading v1 manifest lists | +| | _required_ | **`516 min_sequence_number`** | `long` | The minimum data sequence number of all live data or delete files in the manifest; use 0 when reading v1 manifest lists | +| _required_ | _required_ | **`503 added_snapshot_id`** | `long` | ID of the snapshot where the manifest file was added | +| _optional_ | _required_ | **`504 added_files_count`** | `int` | Number of entries in the manifest that have status `ADDED` (1), when `null` this is assumed to be non-zero | +| _optional_ | _required_ | **`505 existing_files_count`** | `int` | Number of entries in the manifest that have status `EXISTING` (0), when `null` this is assumed to be non-zero | +| _optional_ | _required_ | **`506 deleted_files_count`** | `int` | Number of entries in the manifest that have status `DELETED` (2), when `null` this is assumed to be non-zero | +| _optional_ | _required_ | **`512 added_rows_count`** | `long` | Number of rows in all of files in the manifest that have status `ADDED`, when `null` this is assumed to be non-zero | +| _optional_ | _required_ | **`513 existing_rows_count`** | `long` | Number of rows in all of files in the manifest that have status `EXISTING`, when `null` this is assumed to be non-zero | +| _optional_ | _required_ | **`514 deleted_rows_count`** | `long` | Number of rows in all of files in the manifest that have status `DELETED`, when `null` this is assumed to be non-zero | +| _optional_ | _optional_ | **`507 partitions`** | `list<508: field_summary>` (see below) | A list of field summaries for each partition field in the spec. Each field in the list corresponds to a field in the manifest file’s partition spec. | +| _optional_ | _optional_ | **`519 key_metadata`** | `binary` | Implementation-specific key metadata for encryption | + +`field_summary` is a struct with the following fields: + +| v1 | v2 | Field id, name | Type | Description | +| ---------- | ---------- |-------------------------|---------------|-------------| +| _required_ | _required_ | **`509 contains_null`** | `boolean` | Whether the manifest contains at least one partition with a null value for the field | +| _optional_ | _optional_ | **`518 contains_nan`** | `boolean` | Whether the manifest contains at least one partition with a NaN value for the field | +| _optional_ | _optional_ | **`510 lower_bound`** | `bytes` [1] | Lower bound for the non-null, non-NaN values in the partition field, or null if all values are null or NaN [2] | +| _optional_ | _optional_ | **`511 upper_bound`** | `bytes` [1] | Upper bound for the non-null, non-NaN values in the partition field, or null if all values are null or NaN [2] | + +Notes: + +1. Lower and upper bounds are serialized to bytes using the single-object serialization in Appendix D. The type of used to encode the value is the type of the partition field data. +2. If -0.0 is a value of the partition field, the `lower_bound` must not be +0.0, and if +0.0 is a value of the partition field, the `upper_bound` must not be -0.0. + +#### Scan Planning + +Scans are planned by reading the manifest files for the current snapshot. Deleted entries in data and delete manifests (those marked with status "DELETED") are not used in a scan. + +Manifests that contain no matching files, determined using either file counts or partition summaries, may be skipped. + +For each manifest, scan predicates, which filter data rows, are converted to partition predicates, which filter data and delete files. These partition predicates are used to select the data and delete files in the manifest. This conversion uses the partition spec used to write the manifest file. + +Scan predicates are converted to partition predicates using an _inclusive projection_: if a scan predicate matches a row, then the partition predicate must match that row’s partition. This is called _inclusive_ [1] because rows that do not match the scan predicate may be included in the scan by the partition predicate. + +For example, an `events` table with a timestamp column named `ts` that is partitioned by `ts_day=day(ts)` is queried by users with ranges over the timestamp column: `ts > X`. The inclusive projection is `ts_day >= day(X)`, which is used to select files that may have matching rows. Note that, in most cases, timestamps just before `X` will be included in the scan because the file contains rows that match the predicate and rows that do not match the predicate. + +Scan predicates are also used to filter data and delete files using column bounds and counts that are stored by field id in manifests. The same filter logic can be used for both data and delete files because both store metrics of the rows either inserted or deleted. If metrics show that a delete file has no rows that match a scan predicate, it may be ignored just as a data file would be ignored [2]. + +Data files that match the query filter must be read by the scan. + +Note that for any snapshot, all file paths marked with "ADDED" or "EXISTING" may appear at most once across all manifest files in the snapshot. If a file path appears more than once, the results of the scan are undefined. Reader implementations may raise an error in this case, but are not required to do so. + + +Delete files that match the query filter must be applied to data files at read time, limited by the scope of the delete file using the following rules. + +* A _position_ delete file must be applied to a data file when all of the following are true: + - The data file's data sequence number is _less than or equal to_ the delete file's data sequence number + - The data file's partition (both spec and partition values) is equal to the delete file's partition +* An _equality_ delete file must be applied to a data file when all of the following are true: + - The data file's data sequence number is _strictly less than_ the delete's data sequence number + - The data file's partition (both spec and partition values) is equal to the delete file's partition _or_ the delete file's partition spec is unpartitioned + +In general, deletes are applied only to data files that are older and in the same partition, except for two special cases: + +* Equality delete files stored with an unpartitioned spec are applied as global deletes. Otherwise, delete files do not apply to files in other partitions. +* Position delete files must be applied to data files from the same commit, when the data and delete file data sequence numbers are equal. This allows deleting rows that were added in the same commit. + + +Notes: + +1. An alternative, *strict projection*, creates a partition predicate that will match a file if all of the rows in the file must match the scan predicate. These projections are used to calculate the residual predicates for each file in a scan. +2. For example, if `file_a` has rows with `id` between 1 and 10 and a delete file contains rows with `id` between 1 and 4, a scan for `id = 9` may ignore the delete file because none of the deletes can match a row that will be selected. + +#### Snapshot Reference + +Iceberg tables keep track of branches and tags using snapshot references. +Tags are labels for individual snapshots. Branches are mutable named references that can be updated by committing a new snapshot as the branch's referenced snapshot using the [Commit Conflict Resolution and Retry](#commit-conflict-resolution-and-retry) procedures. + +The snapshot reference object records all the information of a reference including snapshot ID, reference type and [Snapshot Retention Policy](#snapshot-retention-policy). + +| v1 | v2 | Field name | Type | Description | +| ---------- | ---------- | ---------------------------- | --------- | ----------- | +| _required_ | _required_ | **`snapshot-id`** | `long` | A reference's snapshot ID. The tagged snapshot or latest snapshot of a branch. | +| _required_ | _required_ | **`type`** | `string` | Type of the reference, `tag` or `branch` | +| _optional_ | _optional_ | **`min-snapshots-to-keep`** | `int` | For `branch` type only, a positive number for the minimum number of snapshots to keep in a branch while expiring snapshots. Defaults to table property `history.expire.min-snapshots-to-keep`. | +| _optional_ | _optional_ | **`max-snapshot-age-ms`** | `long` | For `branch` type only, a positive number for the max age of snapshots to keep when expiring, including the latest snapshot. Defaults to table property `history.expire.max-snapshot-age-ms`. | +| _optional_ | _optional_ | **`max-ref-age-ms`** | `long` | For snapshot references except the `main` branch, a positive number for the max age of the snapshot reference to keep while expiring snapshots. Defaults to table property `history.expire.max-ref-age-ms`. The `main` branch never expires. | + +Valid snapshot references are stored as the values of the `refs` map in table metadata. For serialization, see Appendix C. + +#### Snapshot Retention Policy + +Table snapshots expire and are removed from metadata to allow removed or replaced data files to be physically deleted. +The snapshot expiration procedure removes snapshots from table metadata and applies the table's retention policy. +Retention policy can be configured both globally and on snapshot reference through properties `min-snapshots-to-keep`, `max-snapshot-age-ms` and `max-ref-age-ms`. + +When expiring snapshots, retention policies in table and snapshot references are evaluated in the following way: + +1. Start with an empty set of snapshots to retain +2. Remove any refs (other than main) where the referenced snapshot is older than `max-ref-age-ms` +3. For each branch and tag, add the referenced snapshot to the retained set +4. For each branch, add its ancestors to the retained set until: + 1. The snapshot is older than `max-snapshot-age-ms`, AND + 2. The snapshot is not one of the first `min-snapshots-to-keep` in the branch (including the branch's referenced snapshot) +5. Expire any snapshot not in the set of snapshots to retain. + +### Table Metadata + +Table metadata is stored as JSON. Each table metadata change creates a new table metadata file that is committed by an atomic operation. This operation is used to ensure that a new version of table metadata replaces the version on which it was based. This produces a linear history of table versions and ensures that concurrent writes are not lost. + +The atomic operation used to commit metadata depends on how tables are tracked and is not standardized by this spec. See the sections below for examples. + +#### Table Metadata Fields + +Table metadata consists of the following fields: + +| v1 | v2 | Field | Description | +| ---------- | ---------- | ----- | ----------- | +| _required_ | _required_ | **`format-version`** | An integer version number for the format. Currently, this can be 1 or 2 based on the spec. Implementations must throw an exception if a table's version is higher than the supported version. | +| _optional_ | _required_ | **`table-uuid`** | A UUID that identifies the table, generated when the table is created. Implementations must throw an exception if a table's UUID does not match the expected UUID after refreshing metadata. | +| _required_ | _required_ | **`location`**| The table's base location. This is used by writers to determine where to store data files, manifest files, and table metadata files. | +| | _required_ | **`last-sequence-number`**| The table's highest assigned sequence number, a monotonically increasing long that tracks the order of snapshots in a table. | +| _required_ | _required_ | **`last-updated-ms`**| Timestamp in milliseconds from the unix epoch when the table was last updated. Each table metadata file should update this field just before writing. | +| _required_ | _required_ | **`last-column-id`**| An integer; the highest assigned column ID for the table. This is used to ensure columns are always assigned an unused ID when evolving schemas. | +| _required_ | | **`schema`**| The table’s current schema. (**Deprecated**: use `schemas` and `current-schema-id` instead) | +| _optional_ | _required_ | **`schemas`**| A list of schemas, stored as objects with `schema-id`. | +| _optional_ | _required_ | **`current-schema-id`**| ID of the table's current schema. | +| _required_ | | **`partition-spec`**| The table’s current partition spec, stored as only fields. Note that this is used by writers to partition data, but is not used when reading because reads use the specs stored in manifest files. (**Deprecated**: use `partition-specs` and `default-spec-id` instead) | +| _optional_ | _required_ | **`partition-specs`**| A list of partition specs, stored as full partition spec objects. | +| _optional_ | _required_ | **`default-spec-id`**| ID of the "current" spec that writers should use by default. | +| _optional_ | _required_ | **`last-partition-id`**| An integer; the highest assigned partition field ID across all partition specs for the table. This is used to ensure partition fields are always assigned an unused ID when evolving specs. | +| _optional_ | _optional_ | **`properties`**| A string to string map of table properties. This is used to control settings that affect reading and writing and is not intended to be used for arbitrary metadata. For example, `commit.retry.num-retries` is used to control the number of commit retries. | +| _optional_ | _optional_ | **`current-snapshot-id`**| `long` ID of the current table snapshot; must be the same as the current ID of the `main` branch in `refs`. | +| _optional_ | _optional_ | **`snapshots`**| A list of valid snapshots. Valid snapshots are snapshots for which all data files exist in the file system. A data file must not be deleted from the file system until the last snapshot in which it was listed is garbage collected. | +| _optional_ | _optional_ | **`snapshot-log`**| A list (optional) of timestamp and snapshot ID pairs that encodes changes to the current snapshot for the table. Each time the current-snapshot-id is changed, a new entry should be added with the last-updated-ms and the new current-snapshot-id. When snapshots are expired from the list of valid snapshots, all entries before a snapshot that has expired should be removed. | +| _optional_ | _optional_ | **`metadata-log`**| A list (optional) of timestamp and metadata file location pairs that encodes changes to the previous metadata files for the table. Each time a new metadata file is created, a new entry of the previous metadata file location should be added to the list. Tables can be configured to remove oldest metadata log entries and keep a fixed-size log of the most recent entries after a commit. | +| _optional_ | _required_ | **`sort-orders`**| A list of sort orders, stored as full sort order objects. | +| _optional_ | _required_ | **`default-sort-order-id`**| Default sort order id of the table. Note that this could be used by writers, but is not used when reading because reads use the specs stored in manifest files. | +| | _optional_ | **`refs`** | A map of snapshot references. The map keys are the unique snapshot reference names in the table, and the map values are snapshot reference objects. There is always a `main` branch reference pointing to the `current-snapshot-id` even if the `refs` map is null. | +| _optional_ | _optional_ | **`statistics`** | A list (optional) of [table statistics](#table-statistics). | + +For serialization details, see Appendix C. + +#### Table statistics + +Table statistics files are valid [Puffin files](puffin-spec.md). Statistics are informational. A reader can choose to +ignore statistics information. Statistics support is not required to read the table correctly. A table can contain +many statistics files associated with different table snapshots. + +Statistics files metadata within `statistics` table metadata field is a struct with the following fields: + +| v1 | v2 | Field name | Type | Description | +|----|----|------------|------|-------------| +| _required_ | _required_ | **`snapshot-id`** | `string` | ID of the Iceberg table's snapshot the statistics file is associated with. | +| _required_ | _required_ | **`statistics-path`** | `string` | Path of the statistics file. See [Puffin file format](puffin-spec.md). | +| _required_ | _required_ | **`file-size-in-bytes`** | `long` | Size of the statistics file. | +| _required_ | _required_ | **`file-footer-size-in-bytes`** | `long` | Total size of the statistics file's footer (not the footer payload size). See [Puffin file format](puffin-spec.md) for footer definition. | +| _optional_ | _optional_ | **`key-metadata`** | Base64-encoded implementation-specific key metadata for encryption. | +| _required_ | _required_ | **`blob-metadata`** | `list` (see below) | A list of the blob metadata for statistics contained in the file with structure described below. | + +Blob metadata is a struct with the following fields: + +| v1 | v2 | Field name | Type | Description | +|----|----|------------|------|-------------| +| _required_ | _required_ | **`type`** | `string` | Type of the blob. Matches Blob type in the Puffin file. | +| _required_ | _required_ | **`snapshot-id`** | `long` | ID of the Iceberg table's snapshot the blob was computed from. | +| _required_ | _required_ | **`sequence-number`** | `long` | Sequence number of the Iceberg table's snapshot the blob was computed from. | +| _required_ | _required_ | **`fields`** | `list` | Ordered list of fields, given by field ID, on which the statistic was calculated. | +| _optional_ | _optional_ | **`properties`** | `map` | Additional properties associated with the statistic. Subset of Blob properties in the Puffin file. | + + +#### Commit Conflict Resolution and Retry + +When two commits happen at the same time and are based on the same version, only one commit will succeed. In most cases, the failed commit can be applied to the new current version of table metadata and retried. Updates verify the conditions under which they can be applied to a new version and retry if those conditions are met. + +* Append operations have no requirements and can always be applied. +* Replace operations must verify that the files that will be deleted are still in the table. Examples of replace operations include format changes (replace an Avro file with a Parquet file) and compactions (several files are replaced with a single file that contains the same rows). +* Delete operations must verify that specific files to delete are still in the table. Delete operations based on expressions can always be applied (e.g., where timestamp < X). +* Table schema updates and partition spec changes must validate that the schema has not changed between the base version and the current version. + + +#### File System Tables + +An atomic swap can be implemented using atomic rename in file systems that support it, like HDFS or most local file systems [1]. + +Each version of table metadata is stored in a metadata folder under the table’s base location using a file naming scheme that includes a version number, `V`: `v.metadata.json`. To commit a new metadata version, `V+1`, the writer performs the following steps: + +1. Read the current table metadata version `V`. +2. Create new table metadata based on version `V`. +3. Write the new table metadata to a unique file: `.metadata.json`. +4. Rename the unique file to the well-known file for version `V`: `v.metadata.json`. + 1. If the rename succeeds, the commit succeeded and `V+1` is the table’s current version + 2. If the rename fails, go back to step 1. + +Notes: + +1. The file system table scheme is implemented in [HadoopTableOperations](javadoc/latest/index.html?org/apache/iceberg/hadoop/HadoopTableOperations.html). + +#### Metastore Tables + +The atomic swap needed to commit new versions of table metadata can be implemented by storing a pointer in a metastore or database that is updated with a check-and-put operation [1]. The check-and-put validates that the version of the table that a write is based on is still current and then makes the new metadata from the write the current version. + +Each version of table metadata is stored in a metadata folder under the table’s base location using a naming scheme that includes a version and UUID: `-.metadata.json`. To commit a new metadata version, `V+1`, the writer performs the following steps: + +1. Create a new table metadata file based on the current metadata. +2. Write the new table metadata to a unique file: `-.metadata.json`. +3. Request that the metastore swap the table’s metadata pointer from the location of `V` to the location of `V+1`. + 1. If the swap succeeds, the commit succeeded. `V` was still the latest metadata version and the metadata file for `V+1` is now the current metadata. + 2. If the swap fails, another writer has already created `V+1`. The current writer goes back to step 1. + +Notes: + +1. The metastore table scheme is partly implemented in [BaseMetastoreTableOperations](javadoc/latest/index.html?org/apache/iceberg/BaseMetastoreTableOperations.html). + + +### Delete Formats + +This section details how to encode row-level deletes in Iceberg delete files. Row-level deletes are not supported in v1. + +Row-level delete files are valid Iceberg data files: files must use valid Iceberg formats, schemas, and column projection. It is recommended that delete files are written using the table's default file format. + +Row-level delete files are tracked by manifests, like data files. A separate set of manifests is used for delete files, but the manifest schemas are identical. + +Both position and equality deletes allow encoding deleted row values with a delete. This can be used to reconstruct a stream of changes to a table. + + +#### Position Delete Files + +Position-based delete files identify deleted rows by file and position in one or more data files, and may optionally contain the deleted row. + +A data row is deleted if there is an entry in a position delete file for the row's file and position in the data file, starting at 0. + +Position-based delete files store `file_position_delete`, a struct with the following fields: + +| Field id, name | Type | Description | +|-----------------------------|----------------------------|-------------| +| **`2147483546 file_path`** | `string` | Full URI of a data file with FS scheme. This must match the `file_path` of the target data file in a manifest entry | +| **`2147483545 pos`** | `long` | Ordinal position of a deleted row in the target data file identified by `file_path`, starting at `0` | +| **`2147483544 row`** | `required struct<...>` [1] | Deleted row values. Omit the column when not storing deleted rows. | + +1. When present in the delete file, `row` is required because all delete entries must include the row values. + +When the deleted row column is present, its schema may be any subset of the table schema and must use field ids matching the table. + +To ensure the accuracy of statistics, all delete entries must include row values, or the column must be omitted (this is why the column type is `required`). + +The rows in the delete file must be sorted by `file_path` then `pos` to optimize filtering rows while scanning. + +* Sorting by `file_path` allows filter pushdown by file in columnar storage formats. +* Sorting by `pos` allows filtering rows while scanning, to avoid keeping deletes in memory. + +#### Equality Delete Files + +Equality delete files identify deleted rows in a collection of data files by one or more column values, and may optionally contain additional columns of the deleted row. + +Equality delete files store any subset of a table's columns and use the table's field ids. The _delete columns_ are the columns of the delete file used to match data rows. Delete columns are identified by id in the delete file [metadata column `equality_ids`](#manifests). Float and double columns cannot be used as delete columns in equality delete files. + +A data row is deleted if its values are equal to all delete columns for any row in an equality delete file that applies to the row's data file (see [`Scan Planning`](#scan-planning)). + +Each row of the delete file produces one equality predicate that matches any row where the delete columns are equal. Multiple columns can be thought of as an `AND` of equality predicates. A `null` value in a delete column matches a row if the row's value is `null`, equivalent to `col IS NULL`. + +For example, a table with the following data: + +```text + 1: id | 2: category | 3: name +-------|-------------|--------- + 1 | marsupial | Koala + 2 | toy | Teddy + 3 | NULL | Grizzly + 4 | NULL | Polar +``` + +The delete `id = 3` could be written as either of the following equality delete files: + +```text +equality_ids=[1] + + 1: id +------- + 3 +``` + +```text +equality_ids=[1] + + 1: id | 2: category | 3: name +-------|-------------|--------- + 3 | NULL | Grizzly +``` + +The delete `id = 4 AND category IS NULL` could be written as the following equality delete file: + +```text +equality_ids=[1, 2] + + 1: id | 2: category | 3: name +-------|-------------|--------- + 4 | NULL | Polar +``` + +If a delete column in an equality delete file is later dropped from the table, it must still be used when applying the equality deletes. If a column was added to a table and later used as a delete column in an equality delete file, the column value is read for older data files using normal projection rules (defaults to `null`). + + +#### Delete File Stats + +Manifests hold the same statistics for delete files and data files. For delete files, the metrics describe the values that were deleted. + + +## Appendix A: Format-specific Requirements + + +### Avro + +**Data Type Mappings** + +Values should be stored in Avro using the Avro types and logical type annotations in the table below. + +Optional fields, array elements, and map values must be wrapped in an Avro `union` with `null`. This is the only union type allowed in Iceberg data files. + +Optional fields must always set the Avro field default value to null. + +Maps with non-string keys must use an array representation with the `map` logical type. The array representation or Avro’s map type may be used for maps with string keys. + +|Type|Avro type|Notes| +|--- |--- |--- | +|**`boolean`**|`boolean`|| +|**`int`**|`int`|| +|**`long`**|`long`|| +|**`float`**|`float`|| +|**`double`**|`double`|| +|**`decimal(P,S)`**|`{ "type": "fixed",`
  `"size": minBytesRequired(P),`
  `"logicalType": "decimal",`
  `"precision": P,`
  `"scale": S }`|Stored as fixed using the minimum number of bytes for the given precision.| +|**`date`**|`{ "type": "int",`
  `"logicalType": "date" }`|Stores days from the 1970-01-01.| +|**`time`**|`{ "type": "long",`
  `"logicalType": "time-micros" }`|Stores microseconds from midnight.| +|**`timestamp`**|`{ "type": "long",`
  `"logicalType": "timestamp-micros",`
  `"adjust-to-utc": false }`|Stores microseconds from 1970-01-01 00:00:00.000000.| +|**`timestamptz`**|`{ "type": "long",`
  `"logicalType": "timestamp-micros",`
  `"adjust-to-utc": true }`|Stores microseconds from 1970-01-01 00:00:00.000000 UTC.| +|**`string`**|`string`|| +|**`uuid`**|`{ "type": "fixed",`
  `"size": 16,`
  `"logicalType": "uuid" }`|| +|**`fixed(L)`**|`{ "type": "fixed",`
  `"size": L }`|| +|**`binary`**|`bytes`|| +|**`struct`**|`record`|| +|**`list`**|`array`|| +|**`map`**|`array` of key-value records, or `map` when keys are strings (optional).|Array storage must use logical type name `map` and must store elements that are 2-field records. The first field is a non-null key and the second field is the value.| + + +**Field IDs** + +Iceberg struct, list, and map types identify nested types by ID. When writing data to Avro files, these IDs must be stored in the Avro schema to support ID-based column pruning. + +IDs are stored as JSON integers in the following locations: + +|ID|Avro schema location|Property|Example| +|--- |--- |--- |--- | +|**Struct field**|Record field object|`field-id`|`{ "type": "record", ...`
  `"fields": [`
    `{ "name": "l",`
      `"type": ["null", "long"],`
      `"default": null,`
      `"field-id": 8 }`
  `] }`| +|**List element**|Array schema object|`element-id`|`{ "type": "array",`
  `"items": "int",`
  `"element-id": 9 }`| +|**String map key**|Map schema object|`key-id`|`{ "type": "map",`
  `"values": "int",`
  `"key-id": 10,`
  `"value-id": 11 }`| +|**String map value**|Map schema object|`value-id`|| +|**Map key, value**|Key, value fields in the element record.|`field-id`|`{ "type": "array",`
  `"logicalType": "map",`
  `"items": {`
    `"type": "record",`
    `"name": "k12_v13",`
    `"fields": [`
      `{ "name": "key",`
        `"type": "int",`
        `"field-id": 12 },`
      `{ "name": "value",`
        `"type": "string",`
        `"field-id": 13 }`
    `] } }`| + +Note that the string map case is for maps where the key type is a string. Using Avro’s map type in this case is optional. Maps with string keys may be stored as arrays. + + +### Parquet + +**Data Type Mappings** + +Values should be stored in Parquet using the types and logical type annotations in the table below. Column IDs are required. + +Lists must use the [3-level representation](https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#lists). + +| Type | Parquet physical type | Logical type | Notes | +|--------------------|--------------------------------------------------------------------|---------------------------------------------|----------------------------------------------------------------| +| **`boolean`** | `boolean` | | | +| **`int`** | `int` | | | +| **`long`** | `long` | | | +| **`float`** | `float` | | | +| **`double`** | `double` | | | +| **`decimal(P,S)`** | `P <= 9`: `int32`,
`P <= 18`: `int64`,
`fixed` otherwise | `DECIMAL(P,S)` | Fixed must use the minimum number of bytes that can store `P`. | +| **`date`** | `int32` | `DATE` | Stores days from the 1970-01-01. | +| **`time`** | `int64` | `TIME_MICROS` with `adjustToUtc=false` | Stores microseconds from midnight. | +| **`timestamp`** | `int64` | `TIMESTAMP_MICROS` with `adjustToUtc=false` | Stores microseconds from 1970-01-01 00:00:00.000000. | +| **`timestamptz`** | `int64` | `TIMESTAMP_MICROS` with `adjustToUtc=true` | Stores microseconds from 1970-01-01 00:00:00.000000 UTC. | +| **`string`** | `binary` | `UTF8` | Encoding must be UTF-8. | +| **`uuid`** | `fixed_len_byte_array[16]` | `UUID` | | +| **`fixed(L)`** | `fixed_len_byte_array[L]` | | | +| **`binary`** | `binary` | | | +| **`struct`** | `group` | | | +| **`list`** | `3-level list` | `LIST` | See Parquet docs for 3-level representation. | +| **`map`** | `3-level map` | `MAP` | See Parquet docs for 3-level representation. | + + +### ORC + +**Data Type Mappings** + +| Type | ORC type | ORC type attributes | Notes | +|--------------------|---------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------| +| **`boolean`** | `boolean` | | | +| **`int`** | `int` | | ORC `tinyint` and `smallint` would also map to **`int`**. | +| **`long`** | `long` | | | +| **`float`** | `float` | | | +| **`double`** | `double` | | | +| **`decimal(P,S)`** | `decimal` | | | +| **`date`** | `date` | | | +| **`time`** | `long` | `iceberg.long-type`=`TIME` | Stores microseconds from midnight. | +| **`timestamp`** | `timestamp` | | [1] | +| **`timestamptz`** | `timestamp_instant` | | [1] | +| **`string`** | `string` | | ORC `varchar` and `char` would also map to **`string`**. | +| **`uuid`** | `binary` | `iceberg.binary-type`=`UUID` | | +| **`fixed(L)`** | `binary` | `iceberg.binary-type`=`FIXED` & `iceberg.length`=`L` | The length would not be checked by the ORC reader and should be checked by the adapter. | +| **`binary`** | `binary` | | | +| **`struct`** | `struct` | | | +| **`list`** | `array` | | | +| **`map`** | `map` | | | + +Notes: + +1. ORC's [TimestampColumnVector](https://orc.apache.org/api/hive-storage-api/org/apache/hadoop/hive/ql/exec/vector/TimestampColumnVector.html) consists of a time field (milliseconds since epoch) and a nanos field (nanoseconds within the second). Hence the milliseconds within the second are reported twice; once in the time field and again in the nanos field. The read adapter should only use milliseconds within the second from one of these fields. The write adapter should also report milliseconds within the second twice; once in the time field and again in the nanos field. ORC writer is expected to correctly consider millis information from one of the fields. More details at https://issues.apache.org/jira/browse/ORC-546 + +One of the interesting challenges with this is how to map Iceberg’s schema evolution (id based) on to ORC’s (name based). In theory, we could use Iceberg’s column ids as the column and field names, but that would be inconvenient. + +The column IDs must be stored in ORC type attributes using the key `iceberg.id`, and `iceberg.required` to store `"true"` if the Iceberg column is required, otherwise it will be optional. + +Iceberg would build the desired reader schema with their schema evolution rules and pass that down to the ORC reader, which would then use its schema evolution to map that to the writer’s schema. Basically, Iceberg would need to change the names of columns and fields to get the desired mapping. + +|Iceberg writer|ORC writer|Iceberg reader|ORC reader| +|--- |--- |--- |--- | +|`struct`|`struct`|`struct`|`struct`| +|`struct>`|`struct>`|`struct>`|`struct>`| + +## Appendix B: 32-bit Hash Requirements + +The 32-bit hash implementation is 32-bit Murmur3 hash, x86 variant, seeded with 0. + +| Primitive type | Hash specification | Test value | +|--------------------|-------------------------------------------|--------------------------------------------| +| **`int`** | `hashLong(long(v))` [1] | `34` → `2017239379` | +| **`long`** | `hashBytes(littleEndianBytes(v))` | `34L` → `2017239379` | +| **`decimal(P,S)`** | `hashBytes(minBigEndian(unscaled(v)))`[2] | `14.20` → `-500754589` | +| **`date`** | `hashInt(daysFromUnixEpoch(v))` | `2017-11-16` → `-653330422` | +| **`time`** | `hashLong(microsecsFromMidnight(v))` | `22:31:08` → `-662762989` | +| **`timestamp`** | `hashLong(microsecsFromUnixEpoch(v))` | `2017-11-16T22:31:08` → `-2047944441` | +| **`timestamptz`** | `hashLong(microsecsFromUnixEpoch(v))` | `2017-11-16T14:31:08-08:00`→ `-2047944441` | +| **`string`** | `hashBytes(utf8Bytes(v))` | `iceberg` → `1210000089` | +| **`uuid`** | `hashBytes(uuidBytes(v))` [3] | `f79c3e09-677c-4bbd-a479-3f349cb785e7` → `1488055340` | +| **`fixed(L)`** | `hashBytes(v)` | `00 01 02 03` → `-188683207` | +| **`binary`** | `hashBytes(v)` | `00 01 02 03` → `-188683207` | + +The types below are not currently valid for bucketing, and so are not hashed. However, if that changes and a hash value is needed, the following table shall apply: + +| Primitive type | Hash specification | Test value | +|--------------------|-------------------------------------------|--------------------------------------------| +| **`boolean`** | `false: hashInt(0)`, `true: hashInt(1)` | `true` → `1392991556` | +| **`float`** | `hashLong(doubleToLongBits(double(v))` [4]| `1.0F` → `-142385009`, `0.0F` → `1669671676`, `-0.0F` → `1669671676` | +| **`double`** | `hashLong(doubleToLongBits(v))` [4]| `1.0D` → `-142385009`, `0.0D` → `1669671676`, `-0.0D` → `1669671676` | + +Notes: + +1. Integer and long hash results must be identical for all integer values. This ensures that schema evolution does not change bucket partition values if integer types are promoted. +2. Decimal values are hashed using the minimum number of bytes required to hold the unscaled value as a two’s complement big-endian; this representation does not include padding bytes required for storage in a fixed-length array. +Hash results are not dependent on decimal scale, which is part of the type, not the data value. +3. UUIDs are encoded using big endian. The test UUID for the example above is: `f79c3e09-677c-4bbd-a479-3f349cb785e7`. This UUID encoded as a byte array is: +`F7 9C 3E 09 67 7C 4B BD A4 79 3F 34 9C B7 85 E7` +4. `doubleToLongBits` must give the IEEE 754 compliant bit representation of the double value. All `NaN` bit patterns must be canonicalized to `0x7ff8000000000000L`. Negative zero (`-0.0`) must be canonicalized to positive zero (`0.0`). Float hash values are the result of hashing the float cast to double to ensure that schema evolution does not change hash values if float types are promoted. + +## Appendix C: JSON serialization + + +### Schemas + +Schemas are serialized as a JSON object with the same fields as a struct in the table below, and the following additional fields: + +| v1 | v2 |Field|JSON representation|Example| +| ---------- | ---------- |--- |--- |--- | +| _optional_ | _required_ |**`schema-id`**|`JSON int`|`0`| +| _optional_ | _optional_ |**`identifier-field-ids`**|`JSON list of ints`|`[1, 2]`| + +Types are serialized according to this table: + +|Type|JSON representation|Example| +|--- |--- |--- | +|**`boolean`**|`JSON string: "boolean"`|`"boolean"`| +|**`int`**|`JSON string: "int"`|`"int"`| +|**`long`**|`JSON string: "long"`|`"long"`| +|**`float`**|`JSON string: "float"`|`"float"`| +|**`double`**|`JSON string: "double"`|`"double"`| +|**`date`**|`JSON string: "date"`|`"date"`| +|**`time`**|`JSON string: "time"`|`"time"`| +|**`timestamp without zone`**|`JSON string: "timestamp"`|`"timestamp"`| +|**`timestamp with zone`**|`JSON string: "timestamptz"`|`"timestamptz"`| +|**`string`**|`JSON string: "string"`|`"string"`| +|**`uuid`**|`JSON string: "uuid"`|`"uuid"`| +|**`fixed(L)`**|`JSON string: "fixed[]"`|`"fixed[16]"`| +|**`binary`**|`JSON string: "binary"`|`"binary"`| +|**`decimal(P, S)`**|`JSON string: "decimal(

,)"`|`"decimal(9,2)"`,
`"decimal(9, 2)"`| +|**`struct`**|`JSON object: {`
  `"type": "struct",`
  `"fields": [ {`
    `"id": ,`
    `"name": ,`
    `"required": ,`
    `"type": ,`
    `"doc": ,`
    `"initial-default": ,`
    `"write-default": `
    `}, ...`
  `] }`|`{`
  `"type": "struct",`
  `"fields": [ {`
    `"id": 1,`
    `"name": "id",`
    `"required": true,`
    `"type": "uuid",`
    `"initial-default": "0db3e2a8-9d1d-42b9-aa7b-74ebe558dceb",`
    `"write-default": "ec5911be-b0a7-458c-8438-c9a3e53cffae"`
  `}, {`
    `"id": 2,`
    `"name": "data",`
    `"required": false,`
    `"type": {`
      `"type": "list",`
      `...`
    `}`
  `} ]`
`}`| +|**`list`**|`JSON object: {`
  `"type": "list",`
  `"element-id": ,`
  `"element-required": `
  `"element": `
`}`|`{`
  `"type": "list",`
  `"element-id": 3,`
  `"element-required": true,`
  `"element": "string"`
`}`| +|**`map`**|`JSON object: {`
  `"type": "map",`
  `"key-id": ,`
  `"key": ,`
  `"value-id": ,`
  `"value-required": `
  `"value": `
`}`|`{`
  `"type": "map",`
  `"key-id": 4,`
  `"key": "string",`
  `"value-id": 5,`
  `"value-required": false,`
  `"value": "double"`
`}`| + +Note that default values are serialized using the JSON single-value serialization in [Appendix D](#appendix-d-single-value-serialization). + + +### Partition Specs + +Partition specs are serialized as a JSON object with the following fields: + +|Field|JSON representation|Example| +|--- |--- |--- | +|**`spec-id`**|`JSON int`|`0`| +|**`fields`**|`JSON list: [`
  `,`
  `...`
`]`|`[ {`
  `"source-id": 4,`
  `"field-id": 1000,`
  `"name": "ts_day",`
  `"transform": "day"`
`}, {`
  `"source-id": 1,`
  `"field-id": 1001,`
  `"name": "id_bucket",`
  `"transform": "bucket[16]"`
`} ]`| + +Each partition field in the fields list is stored as an object. See the table for more detail: + +|Transform or Field|JSON representation|Example| +|--- |--- |--- | +|**`identity`**|`JSON string: "identity"`|`"identity"`| +|**`bucket[N]`**|`JSON string: "bucket[]"`|`"bucket[16]"`| +|**`truncate[W]`**|`JSON string: "truncate[]"`|`"truncate[20]"`| +|**`year`**|`JSON string: "year"`|`"year"`| +|**`month`**|`JSON string: "month"`|`"month"`| +|**`day`**|`JSON string: "day"`|`"day"`| +|**`hour`**|`JSON string: "hour"`|`"hour"`| +|**`Partition Field`**|`JSON object: {`
  `"source-id": ,`
  `"field-id": ,`
  `"name": ,`
  `"transform": `
`}`|`{`
  `"source-id": 1,`
  `"field-id": 1000,`
  `"name": "id_bucket",`
  `"transform": "bucket[16]"`
`}`| + +In some cases partition specs are stored using only the field list instead of the object format that includes the spec ID, like the deprecated `partition-spec` field in table metadata. The object format should be used unless otherwise noted in this spec. + +The `field-id` property was added for each partition field in v2. In v1, the reference implementation assigned field ids sequentially in each spec starting at 1,000. See Partition Evolution for more details. + +### Sort Orders + +Sort orders are serialized as a list of JSON object, each of which contains the following fields: + +|Field|JSON representation|Example| +|--- |--- |--- | +|**`order-id`**|`JSON int`|`1`| +|**`fields`**|`JSON list: [`
  `,`
  `...`
`]`|`[ {`
  ` "transform": "identity",`
  ` "source-id": 2,`
  ` "direction": "asc",`
  ` "null-order": "nulls-first"`
  `}, {`
  ` "transform": "bucket[4]",`
  ` "source-id": 3,`
  ` "direction": "desc",`
  ` "null-order": "nulls-last"`
`} ]`| + +Each sort field in the fields list is stored as an object with the following properties: + +|Field|JSON representation|Example| +|--- |--- |--- | +|**`Sort Field`**|`JSON object: {`
  `"transform": ,`
  `"source-id": ,`
  `"direction": ,`
  `"null-order": `
`}`|`{`
  ` "transform": "bucket[4]",`
  ` "source-id": 3,`
  ` "direction": "desc",`
  ` "null-order": "nulls-last"`
`}`| + +The following table describes the possible values for the some of the field within sort field: + +|Field|JSON representation|Possible values| +|--- |--- |--- | +|**`direction`**|`JSON string`|`"asc", "desc"`| +|**`null-order`**|`JSON string`|`"nulls-first", "nulls-last"`| + + +### Table Metadata and Snapshots + +Table metadata is serialized as a JSON object according to the following table. Snapshots are not serialized separately. Instead, they are stored in the table metadata JSON. + +|Metadata field|JSON representation|Example| +|--- |--- |--- | +|**`format-version`**|`JSON int`|`1`| +|**`table-uuid`**|`JSON string`|`"fb072c92-a02b-11e9-ae9c-1bb7bc9eca94"`| +|**`location`**|`JSON string`|`"s3://b/wh/data.db/table"`| +|**`last-updated-ms`**|`JSON long`|`1515100955770`| +|**`last-column-id`**|`JSON int`|`22`| +|**`schema`**|`JSON schema (object)`|`See above, read schemas instead`| +|**`schemas`**|`JSON schemas (list of objects)`|`See above`| +|**`current-schema-id`**|`JSON int`|`0`| +|**`partition-spec`**|`JSON partition fields (list)`|`See above, read partition-specs instead`| +|**`partition-specs`**|`JSON partition specs (list of objects)`|`See above`| +|**`default-spec-id`**|`JSON int`|`0`| +|**`last-partition-id`**|`JSON int`|`1000`| +|**`properties`**|`JSON object: {`
  `"": "",`
  `...`
`}`|`{`
  `"write.format.default": "avro",`
  `"commit.retry.num-retries": "4"`
`}`| +|**`current-snapshot-id`**|`JSON long`|`3051729675574597004`| +|**`snapshots`**|`JSON list of objects: [ {`
  `"snapshot-id": ,`
  `"timestamp-ms": ,`
  `"summary": {`
    `"operation": ,`
    `... },`
  `"manifest-list": "",`
  `"schema-id": ""`
  `},`
  `...`
`]`|`[ {`
  `"snapshot-id": 3051729675574597004,`
  `"timestamp-ms": 1515100955770,`
  `"summary": {`
    `"operation": "append"`
  `},`
  `"manifest-list": "s3://b/wh/.../s1.avro"`
  `"schema-id": 0`
`} ]`| +|**`snapshot-log`**|`JSON list of objects: [`
  `{`
  `"snapshot-id": ,`
  `"timestamp-ms": `
  `},`
  `...`
`]`|`[ {`
  `"snapshot-id": 30517296...,`
  `"timestamp-ms": 1515100...`
`} ]`| +|**`metadata-log`**|`JSON list of objects: [`
  `{`
  `"metadata-file": ,`
  `"timestamp-ms": `
  `},`
  `...`
`]`|`[ {`
  `"metadata-file": "s3://bucket/.../v1.json",`
  `"timestamp-ms": 1515100...`
`} ]` | +|**`sort-orders`**|`JSON sort orders (list of sort field object)`|`See above`| +|**`default-sort-order-id`**|`JSON int`|`0`| +|**`refs`**|`JSON map with string key and object value:`
`{`
  `"": {`
  `"snapshot-id": ,`
  `"type": ,`
  `"max-ref-age-ms": ,`
  `...`
  `}`
  `...`
`}`|`{`
  `"test": {`
  `"snapshot-id": 123456789000,`
  `"type": "tag",`
  `"max-ref-age-ms": 10000000`
  `}`
`}`| + +### Name Mapping Serialization + +Name mapping is serialized as a list of field mapping JSON Objects which are serialized as follows + +|Field mapping field|JSON representation|Example| +|--- |--- |--- | +|**`names`**|`JSON list of strings`|`["latitude", "lat"]`| +|**`field_id`**|`JSON int`|`1`| +|**`fields`**|`JSON field mappings (list of objects)`|`[{ `
  `"field-id": 4,`
  `"names": ["latitude", "lat"]`
`}, {`
  `"field-id": 5,`
  `"names": ["longitude", "long"]`
`}]`| + +Example +```json +[ { "field-id": 1, "names": ["id", "record_id"] }, + { "field-id": 2, "names": ["data"] }, + { "field-id": 3, "names": ["location"], "fields": [ + { "field-id": 4, "names": ["latitude", "lat"] }, + { "field-id": 5, "names": ["longitude", "long"] } + ] } ] +``` + + +## Appendix D: Single-value serialization + +### Binary single-value serialization + +This serialization scheme is for storing single values as individual binary values in the lower and upper bounds maps of manifest files. + +| Type | Binary serialization | +|------------------------------|--------------------------------------------------------------------------------------------------------------| +| **`boolean`** | `0x00` for false, non-zero byte for true | +| **`int`** | Stored as 4-byte little-endian | +| **`long`** | Stored as 8-byte little-endian | +| **`float`** | Stored as 4-byte little-endian | +| **`double`** | Stored as 8-byte little-endian | +| **`date`** | Stores days from the 1970-01-01 in an 4-byte little-endian int | +| **`time`** | Stores microseconds from midnight in an 8-byte little-endian long | +| **`timestamp without zone`** | Stores microseconds from 1970-01-01 00:00:00.000000 in an 8-byte little-endian long | +| **`timestamp with zone`** | Stores microseconds from 1970-01-01 00:00:00.000000 UTC in an 8-byte little-endian long | +| **`string`** | UTF-8 bytes (without length) | +| **`uuid`** | 16-byte big-endian value, see example in Appendix B | +| **`fixed(L)`** | Binary value | +| **`binary`** | Binary value (without length) | +| **`decimal(P, S)`** | Stores unscaled value as two’s-complement big-endian binary, using the minimum number of bytes for the value | +| **`struct`** | Not supported | +| **`list`** | Not supported | +| **`map`** | Not supported | + +### JSON single-value serialization + + Single values are serialized as JSON by type according to the following table: + +| Type | JSON representation | Example | Description | +| ------------------ | ----------------------------------------- | ------------------------------------------ | -- | +| **`boolean`** | **`JSON boolean`** | `true` | | +| **`int`** | **`JSON int`** | `34` | | +| **`long`** | **`JSON long`** | `34` | | +| **`float`** | **`JSON number`** | `1.0` | | +| **`double`** | **`JSON number`** | `1.0` | | +| **`decimal(P,S)`** | **`JSON string`** | `"14.20"`, `"2E+20"` | Stores the string representation of the decimal value, specifically, for values with a positive scale, the number of digits to the right of the decimal point is used to indicate scale, for values with a negative scale, the scientific notation is used and the exponent must equal the negated scale | +| **`date`** | **`JSON string`** | `"2017-11-16"` | Stores ISO-8601 standard date | +| **`time`** | **`JSON string`** | `"22:31:08.123456"` | Stores ISO-8601 standard time with microsecond precision | +| **`timestamp`** | **`JSON string`** | `"2017-11-16T22:31:08.123456"` | Stores ISO-8601 standard timestamp with microsecond precision; must not include a zone offset | +| **`timestamptz`** | **`JSON string`** | `"2017-11-16T22:31:08.123456+00:00"` | Stores ISO-8601 standard timestamp with microsecond precision; must include a zone offset and it must be '+00:00' | +| **`string`** | **`JSON string`** | `"iceberg"` | | +| **`uuid`** | **`JSON string`** | `"f79c3e09-677c-4bbd-a479-3f349cb785e7"` | Stores the lowercase uuid string | +| **`fixed(L)`** | **`JSON string`** | `"000102ff"` | Stored as a hexadecimal string | +| **`binary`** | **`JSON string`** | `"000102ff"` | Stored as a hexadecimal string | +| **`struct`** | **`JSON object by field ID`** | `{"1": 1, "2": "bar"}` | Stores struct fields using the field ID as the JSON field name; field values are stored using this JSON single-value format | +| **`list`** | **`JSON array of values`** | `[1, 2, 3]` | Stores a JSON array of values that are serialized using this JSON single-value format | +| **`map`** | **`JSON object of key and value arrays`** | `{ "keys": ["a", "b"], "values": [1, 2] }` | Stores arrays of keys and values; individual keys and values are serialized using this JSON single-value format | + + +## Appendix E: Format version changes + +### Version 3 + +Default values are added to struct fields in v3. +* The `write-default` is a forward-compatible change because it is only used at write time. Old writers will fail because the field is missing. +* Tables with `initial-default` will be read correctly by older readers if `initial-default` is always null for optional fields. Otherwise, old readers will default optional columns with null. Old readers will fail to read required fields which are populated by `initial-default` because that default is not supported. + +### Version 2 + +Writing v1 metadata: + +* Table metadata field `last-sequence-number` should not be written +* Snapshot field `sequence-number` should not be written +* Manifest list field `sequence-number` should not be written +* Manifest list field `min-sequence-number` should not be written +* Manifest list field `content` must be 0 (data) or omitted +* Manifest entry field `sequence_number` should not be written +* Manifest entry field `file_sequence_number` should not be written +* Data file field `content` must be 0 (data) or omitted + +Reading v1 metadata for v2: + +* Table metadata field `last-sequence-number` must default to 0 +* Snapshot field `sequence-number` must default to 0 +* Manifest list field `sequence-number` must default to 0 +* Manifest list field `min-sequence-number` must default to 0 +* Manifest list field `content` must default to 0 (data) +* Manifest entry field `sequence_number` must default to 0 +* Manifest entry field `file_sequence_number` must default to 0 +* Data file field `content` must default to 0 (data) + +Writing v2 metadata: + +* Table metadata JSON: + * `last-sequence-number` was added and is required; default to 0 when reading v1 metadata + * `table-uuid` is now required + * `current-schema-id` is now required + * `schemas` is now required + * `partition-specs` is now required + * `default-spec-id` is now required + * `last-partition-id` is now required + * `sort-orders` is now required + * `default-sort-order-id` is now required + * `schema` is no longer required and should be omitted; use `schemas` and `current-schema-id` instead + * `partition-spec` is no longer required and should be omitted; use `partition-specs` and `default-spec-id` instead +* Snapshot JSON: + * `sequence-number` was added and is required; default to 0 when reading v1 metadata + * `manifest-list` is now required + * `manifests` is no longer required and should be omitted; always use `manifest-list` instead +* Manifest list `manifest_file`: + * `content` was added and is required; 0=data, 1=deletes; default to 0 when reading v1 manifest lists + * `sequence_number` was added and is required + * `min_sequence_number` was added and is required + * `added_files_count` is now required + * `existing_files_count` is now required + * `deleted_files_count` is now required + * `added_rows_count` is now required + * `existing_rows_count` is now required + * `deleted_rows_count` is now required +* Manifest key-value metadata: + * `schema-id` is now required + * `partition-spec-id` is now required + * `format-version` is now required + * `content` was added and is required (must be "data" or "deletes") +* Manifest `manifest_entry`: + * `snapshot_id` is now optional to support inheritance + * `sequence_number` was added and is optional, to support inheritance + * `file_sequence_number` was added and is optional, to support inheritance +* Manifest `data_file`: + * `content` was added and is required; 0=data, 1=position deletes, 2=equality deletes; default to 0 when reading v1 manifests + * `equality_ids` was added, to be used for equality deletes only + * `block_size_in_bytes` was removed (breaks v1 reader compatibility) + * `file_ordinal` was removed + * `sort_columns` was removed + +Note that these requirements apply when writing data to a v2 table. Tables that are upgraded from v1 may contain metadata that does not follow these requirements. Implementations should remain backward-compatible with v1 metadata requirements. diff --git a/site/docs/talks.md b/site/docs/talks.md new file mode 100644 index 000000000000..52fcdef089d0 --- /dev/null +++ b/site/docs/talks.md @@ -0,0 +1,71 @@ +--- +title: "Talks" +--- + + +## Iceberg Talks + +Here is a list of talks and other videos related to Iceberg. + +### [Eliminating Shuffles in DELETE, UPDATE, MERGE](https://www.youtube.com/watch?v=AIZjy6_K0ws) +**Date**: July 27, 2023, **Authors**: Anton Okolnychyi, Chao Sun + +### [Write Distribution Modes in Apache Iceberg](https://www.youtube.com/watch?v=4bOCDP-rhuM) +**Date**: March 15, 2023, **Author**: Russell Spitzer + +### [Technical Evolution of Apache Iceberg](https://www.youtube.com/watch?v=CHs9_h9VLCs) +**Date**: March 15, 2023, **Author**: Anton Okolnychyi + +### [Iceberg's Best Secret Exploring Metadata Tables](https://www.youtube.com/watch?v=s5eKriX6_EU) +**Date**: January 12, 2023, **Author**: Szehon Ho + +### [Data architecture in 2022](https://www.youtube.com/watch?v=1oXmBbB77ak) +**Date**: May 5, 2022, **Authors**: Ryan Blue + +### [Why You Shouldn’t Care About Iceberg | Tabular](https://www.youtube.com/watch?v=_GW3GYZK66U) +**Date**: March 24, 2022, **Authors**: Ryan Blue + +### [Managing Data Files in Apache Iceberg](https://www.dremio.com/resources/webinars/managing-data-files-in-apache-iceberg/) +**Date**: March 2, 2022, **Author**: Russell Spitzer + +### [Tuning Row-Level Operations in Apache Iceberg](https://www.dremio.com/resources/webinars/tuning-row-level-operations-in-apache-iceberg/) +**Date**: March 2, 2022, **Author**: Anton Okolnychyi + +### [Multi Dimensional Clustering with Z Ordering](https://www.youtube.com/watch?v=YLVkITvF6KU) +**Date**: December 6, 2021, **Author**: Russell Spitzer + +### [Expert Roundtable: The Future of Metadata After Hive Metastore](https://www.youtube.com/watch?v=7_Pt1g2x-XE) +**Date**: November 15, 2021, **Authors**: Lior Ebel, Seshu Adunuthula, Ryan Blue & Oz Katz + +### [Presto and Apache Iceberg: Building out Modern Open Data Lakes](https://www.youtube.com/watch?v=OJQHVPChYHw) +**Date**: November 10, 2021, **Authors**: Daniel Weeks, Chunxu Tang + +### [Iceberg Case Studies](https://www.youtube.com/watch?v=Al8feI9QEBc) +**Date**: September 29, 2021, **Authors**: Ryan Blue + +### [Deep Dive into Iceberg SQL Extensions](https://www.dremio.com/resources/webinars/deep-dive-into-iceberg-sql-extensions/) +**Date**: July 13, 2021, **Author**: Anton Okolnychyi + +### [Building efficient and reliable data lakes with Apache Iceberg](https://www.youtube.com/watch?v=QNmSXMQ-gY4) +**Date**: October 21, 2020, **Authors**: Anton Okolnychyi, Vishwa Lakkundi + +### [Spark and Iceberg at Apple's Scale - Leveraging differential files for efficient upserts and deletes](https://www.youtube.com/watch?v=IzkSGKoUxcQ) +**Date**: October 21, 2020, **Authors**: Anton Okolnychyi, Vishwa Lakkundi + +### [Apache Iceberg - A Table Format for Huge Analytic Datasets](https://www.youtube.com/watch?v=mf8Hb0coI6o) +**Date**: October 21, 2020, **Author**: Ryan Blue diff --git a/site/docs/terms.md b/site/docs/terms.md new file mode 100644 index 000000000000..1ab2e5b30d9d --- /dev/null +++ b/site/docs/terms.md @@ -0,0 +1,61 @@ +--- +title: "Terms" +--- + + +# Terms + +### Snapshot + +A **snapshot** is the state of a table at some time. + +Each snapshot lists all of the data files that make up the table's contents at the time of the snapshot. Data files are stored across multiple [manifest](#manifest-file) files, and the manifests for a snapshot are listed in a single [manifest list](#manifest-list) file. + +### Manifest list + +A **manifest list** is a metadata file that lists the [manifests](#manifest-file) that make up a table snapshot. + +Each manifest file in the manifest list is stored with information about its contents, like partition value ranges, used to speed up metadata operations. + +### Manifest file + +A **manifest file** is a metadata file that lists a subset of data files that make up a snapshot. + +Each data file in a manifest is stored with a [partition tuple](#partition-tuple), column-level stats, and summary information used to prune splits during [scan planning](docs/latest/performance.md#scan-planning). + +### Partition spec + +A **partition spec** is a description of how to [partition](docs/latest/partitioning.md) data in a table. + +A spec consists of a list of source columns and transforms. A transform produces a partition value from a source value. For example, `date(ts)` produces the date associated with a timestamp column named `ts`. + +### Partition tuple + +A **partition tuple** is a tuple or struct of partition data stored with each data file. + +All values in a partition tuple are the same for all rows stored in a data file. Partition tuples are produced by transforming values from row data using a partition spec. + +Iceberg stores partition values unmodified, unlike Hive tables that convert values to and from strings in file system paths and keys. + +### Snapshot log (history table) + +The **snapshot log** is a metadata log of how the table's current snapshot has changed over time. + +The log is a list of timestamp and ID pairs: when the current snapshot changed and the snapshot ID the current snapshot was changed to. + +The snapshot log is stored in [table metadata as `snapshot-log`](spec.md#table-metadata-fields). diff --git a/site/docs/vendors.md b/site/docs/vendors.md new file mode 100644 index 000000000000..14554a37b246 --- /dev/null +++ b/site/docs/vendors.md @@ -0,0 +1,62 @@ +--- +title: "Vendors" +--- + + +## Vendors Supporting Iceberg Tables + +This page contains some of the vendors who are shipping and supporting Apache Iceberg in their products + +### [CelerData](https://celerdata.com) + +CelerData provides commercial offerings for [StarRocks](https://www.starrocks.io/), a distributed MPP SQL engine for enterprise analytics on Iceberg. With its fully vectorized technology, local caching, and intelligent materialized view, StarRocks delivers sub-second query latency for both batch and real-time analytics. CelerData offers both an [enterprise deployment](https://celerdata.com/celerdata-enterprise) and a [cloud service](https://celerdata.com/celerdata-cloud) to help customers use StarRocks more smoothly. Learn more about how to query Iceberg with StarRocks [here](https://docs.starrocks.io/en-us/latest/data_source/catalog/iceberg_catalog). + +### [Cloudera](http://cloudera.com) + +Cloudera Data Platform integrates Apache Iceberg to the following components: +* Apache Hive, Apache Impala, and Apache Spark to query Apache Iceberg tables +* Cloudera Data Warehouse service providing access to Apache Iceberg tables through Apache Hive and Apache Impala +* Cloudera Data Engineering service providing access to Apache Iceberg tables through Apache Spark +* The CDP Shared Data Experience (SDX) provides compliance and self-service data access for Apache Iceberg tables +* Hive metastore, which plays a lightweight role in providing the Iceberg Catalog +* Data Visualization to visualize data stored in Apache Iceberg + +https://docs.cloudera.com/cdp-public-cloud/cloud/cdp-iceberg/topics/iceberg-in-cdp.html + +### [Dremio](https://www.dremio.com/) + +With Dremio, an organization can easily build and manage a data lakehouse in which data is stored in open formats like Apache Iceberg and can be processed with Dremio’s interactive SQL query engine and non-Dremio processing engines. [Dremio Cloud](https://www.dremio.com/get-started/) provides these capabilities in a fully managed offering. + +* [Dremio Sonar](https://www.dremio.com/platform/sonar/) is a lakehouse query engine that provides interactive performance and DML on Apache Iceberg, as well as other formats and data sources. +* [Dremio Arctic](https://www.dremio.com/platform/arctic/) is a lakehouse catalog and optimization service for Apache Iceberg. Arctic automatically optimizes tables in the background to ensure high-performance access for any engine. Arctic also simplifies experimentation, data engineering, and data governance by providing Git concepts like branches and tags on Apache Iceberg tables. + +### [IOMETE](https://iomete.com/) + +IOMETE is a fully-managed ready to use, batteries included Data Platform. IOMETE optimizes clustering, compaction, and access control to Apache Iceberg tables. Customer data remains on customer's account to prevent vendor lock-in. The core of IOMETE platform is a serverless Lakehouse that leverages Apache Iceberg as its core table format. IOMETE platform also includes Serverless Spark, an SQL Editor, A Data Catalog, and granular data access control. IOMETE supports Hybrid-multi-cloud setups. + +### [Snowflake](http://snowflake.com/) + +[Snowflake](https://www.snowflake.com/data-cloud/) is a single, cross-cloud platform that enables every organization to mobilize their data with Snowflake’s Data Cloud. Snowflake supports Apache Iceberg by offering [native support for Iceberg Tables](https://www.snowflake.com/blog/iceberg-tables-powering-open-standards-with-snowflake-innovations/) for full DML as well as connectors to [External Tables](https://www.snowflake.com/blog/expanding-the-data-cloud-with-apache-iceberg/) for read-only access. + +### [Starburst](http://starburst.io) + +Starburst is a commercial offering for the [Trino query engine](https://trino.io). Trino is a distributed MPP SQL query engine that can query data in Iceberg at interactive speeds. Trino also enables you to join Iceberg tables with an [array of other systems](https://trino.io/docs/current/connector.html). Starburst offers both an [enterprise deployment](https://www.starburst.io/platform/starburst-enterprise/) and a [fully managed service](https://www.starburst.io/platform/starburst-galaxy/) to make managing and scaling Trino a flawless experience. Starburst also provides customer support and houses many of the original contributors to the open-source project that know Trino best. Learn more about [the Starburst Iceberg connector](https://docs.starburst.io/latest/connector/iceberg.html). + +### [Tabular](https://tabular.io) + +[Tabular](https://tabular.io/product/) is a managed warehouse and automation platform. Tabular offers a central store for analytic data that can be used with any query engine or processing framework that supports Iceberg. Tabular warehouses add role-based access control and automatic optimization, clustering, and compaction to Iceberg tables. diff --git a/site/docs/view-spec.md b/site/docs/view-spec.md new file mode 100644 index 000000000000..1a82c1ec9e42 --- /dev/null +++ b/site/docs/view-spec.md @@ -0,0 +1,329 @@ +--- +title: "View Spec" +--- + + +# Iceberg View Spec + +## Background and Motivation + +Most compute engines (e.g. Trino and Apache Spark) support views. A view is a logical table that can be referenced by future queries. Views do not contain any data. Instead, the query stored by the view is executed every time the view is referenced by another query. + +Each compute engine stores the metadata of the view in its proprietary format in the metastore of choice. Thus, views created from one engine can not be read or altered easily from another engine even when engines share the metastore as well as the storage system. This document standardizes the view metadata for ease of sharing the views across engines. + +## Goals + +* A common metadata format for view metadata, similar to how Iceberg supports a common table format for tables. + +## Overview + +View metadata storage mirrors how Iceberg table metadata is stored and retrieved. View metadata is maintained in metadata files. All changes to view state create a new view metadata file and completely replace the old metadata using an atomic swap. Like Iceberg tables, this atomic swap is delegated to the metastore that tracks tables and/or views by name. The view metadata file tracks the view schema, custom properties, current and past versions, as well as other metadata. + +Each metadata file is self-sufficient. It contains the history of the last few versions of the view and can be used to roll back the view to a previous version. + +### Metadata Location + +An atomic swap of one view metadata file for another provides the basis for making atomic changes. Readers use the version of the view that was current when they loaded the view metadata and are not affected by changes until they refresh and pick up a new metadata location. + +Writers create view metadata files optimistically, assuming that the current metadata location will not be changed before the writer's commit. Once a writer has created an update, it commits by swapping the view's metadata file pointer from the base location to the new location. + +## Specification + +### Terms + +* **Schema** -- Names and types of fields in a view. +* **Version** -- The state of a view at some point in time. + +### View Metadata + +The view version metadata file has the following fields: + +| Requirement | Field name | Description | +|-------------|----------------------|-------------| +| _required_ | `format-version` | An integer version number for the view format; must be 1 | +| _required_ | `location` | The view's base location; used to create metadata file locations | +| _required_ | `current-schema-id` | ID of the current schema of the view, if known | +| _required_ | `schemas` | A list of known schemas | +| _required_ | `current-version-id` | ID of the current version of the view (`version-id`) | +| _required_ | `versions` | A list of known [versions](#versions) of the view [1] | +| _required_ | `version-log` | A list of [version log](#version-log) entries with the timestamp and `version-id` for every change to `current-version-id` | +| _optional_ | `properties` | A string to string map of view properties [2] | + +Notes: +1. The number of versions to retain is controlled by the table property: `version.history.num-entries`. +2. Properties are used for metadata such as `comment` and for settings that affect view maintenance. This is not intended to be used for arbitrary metadata. + +#### Versions + +Each version in `versions` is a struct with the following fields: + +| Requirement | Field name | Description | +|-------------|-------------------|--------------------------------------------------------------------------| +| _required_ | `version-id` | ID for the version | +| _required_ | `schema-id` | ID of the schema for the view version | +| _required_ | `timestamp-ms` | Timestamp when the version was created (ms from epoch) | +| _required_ | `summary` | A string to string map of [summary metadata](#summary) about the version | +| _required_ | `representations` | A list of [representations](#representations) for the view definition | + +#### Summary + +Summary is a string to string map of metadata about a view version. Common metadata keys are documented here. + +| Requirement | Key | Value | +|-------------|------------------|-------| +| _required_ | `operation` | Operation that caused this metadata to be created; must be `create` or `replace` | +| _optional_ | `engine-name` | Name of the engine that created the view version | +| _optional_ | `engine-version` | Version of the engine that created the view version | + +#### Representations + +View definitions can be represented in multiple ways. Representations are documented ways to express a view definition. + +A view version can have more than one representation. All representations for a version must express the same underlying definition. Engines are free to choose the representation to use. + +View versions are immutable. Once a version is created, it cannot be changed. This means that representations for a version cannot be changed. If a view definition changes (or new representations are to be added), a new version must be created. + +Each representation is an object with at least one common field, `type`, that is one of the following: +* `sql`: a SQL SELECT statement that defines the view + +Representations further define metadata for each type. + +##### SQL representation + +The SQL representation stores the view definition as a SQL SELECT, with metadata such as the SQL dialect. + +A view version can have multiple SQL representations of different dialects, but only one SQL representation per dialect. + +| Requirement | Field name | Type | Description | +|-------------|---------------------|----------------|-------------| +| _required_ | `type` | `string` | Must be `sql` | +| _required_ | `sql` | `string` | A SQL SELECT statement | +| _required_ | `dialect` | `string` | The dialect of the `sql` SELECT statement (e.g., "trino" or "spark") | +| _optional_ | `default-catalog` | `string` | Catalog name to use when a reference in the SELECT does not contain a catalog | +| _optional_ | `default-namespace` | `list` | Namespace to use when a reference in the SELECT is a single identifier | +| _optional_ | `field-aliases` | `list` | Column names optionally specified in the create statement | +| _optional_ | `field-comments` | `list` | Column descriptions (COMMENT) optionally specified in the create statement | + +For example: + +```sql +USE prod.default +``` +```sql +CREATE OR REPLACE VIEW event_agg ( + event_count COMMENT 'Count of events', + event_date) AS +SELECT + COUNT(1), CAST(event_ts AS DATE) +FROM events +GROUP BY 2 +``` + +This create statement would produce the following `sql` representation metadata: + +| Field name | Value | +|---------------------|-------| +| `type` | `"sql"` | +| `sql` | `"SELECT\n COUNT(1), CAST(event_ts AS DATE)\nFROM events\nGROUP BY 2"` | +| `dialect` | `"spark"` | +| `default-catalog` | `"prod"` | +| `default-namespace` | `["default"]` | +| `field-aliases` | `["event_count", "event_date"]` | +| `field-comments` | `["Count of events", null]` | + +If a create statement does not include column names or comments before `AS`, the fields should be omitted. + +#### Version log + +The version log tracks changes to the view's current version. This is the view's history and allows reconstructing what version of the view would have been used at some point in time. + +Note that this is not the version's creation time, which is stored in each version's metadata. A version can appear multiple times in the version log, indicating that the view definition was rolled back. + +Each entry in `version-log` is a struct with the following fields: + +| Requirement | Field name | Description | +|-------------|----------------|-------------| +| _required_ | `timestamp-ms` | Timestamp when the view's `current-version-id` was updated (ms from epoch) | +| _required_ | `version-id` | ID that `current-version-id` was set to | + +## Appendix A: An Example + +The JSON metadata file format is described using an example below. + +Imagine the following sequence of operations: + +```sql +USE prod.default +``` +```sql +CREATE OR REPLACE VIEW event_agg ( + event_count COMMENT 'Count of events', + event_date) +COMMENT 'Daily event counts' +AS +SELECT + COUNT(1), CAST(event_ts AS DATE) +FROM events +GROUP BY 2 +``` + + +The metadata JSON file created looks as follows. + +The path is intentionally similar to the path for Iceberg tables and uses a `metadata` directory. + +``` +s3://bucket/warehouse/default.db/event_agg/metadata/00001-(uuid).metadata.json +``` +``` +{ + "format-version" : 1, + "location" : "s3://bucket/warehouse/default.db/event_agg", + "current-version-id" : 1, + "properties" : { + "comment" : "Daily event counts" + }, + "versions" : [ { + "version-id" : 1, + "timestamp-ms" : 1573518431292, + "schema-id" : 1, + "summary" : { + "operation" : "create", + "engine-name" : "Spark", + "engineVersion" : "3.3.2" + }, + "representations" : [ { + "type" : "sql", + "sql" : "SELECT\n COUNT(1), CAST(event_ts AS DATE)\nFROM events\nGROUP BY 2", + "dialect" : "spark", + "default-catalog" : "prod", + "default-namespace" : [ "default" ], + "field-aliases" : ["event_count", "event_date"], + "field-comments" : ["Count of events", null] + } ] + } ], + "current-schema-id": 1, + "schemas": [ { + "schema-id": 1, + "type" : "struct", + "fields" : [ { + "id" : 1, + "name" : "col1", + "required" : false, + "type" : "int" + }, { + "id" : 2, + "name" : "col2", + "required" : false, + "type" : "date" + } ] + } ], + "version-log" : [ { + "timestamp-ms" : 1573518431292, + "version-id" : 1 + } ] +} +``` + +Each change creates a new metadata JSON file. + +```sql +USE prod.other_db; +CREATE OR REPLACE VIEW default.event_agg ( + event_count, + event_date) +AS +SELECT + COUNT(1), CAST(event_ts AS DATE) +FROM prod.default.events +GROUP BY 2 +``` + +Updating the view produces a new metadata file that completely replaces the old: + +``` +s3://bucket/warehouse/default.db/event_agg/metadata/00002-(uuid).metadata.json +``` +``` +{ + "format-version" : 1, + "location" : "s3://bucket/warehouse/default.db/event_agg", + "current-version-id" : 1, + "properties" : { + "comment" : "Daily event counts" + }, + "versions" : [ { + "version-id" : 1, + "timestamp-ms" : 1573518431292, + "schema-id" : 1, + "summary" : { + "operation" : "create", + "engine-name" : "Spark", + "engineVersion" : "3.3.2" + }, + "representations" : [ { + "type" : "sql", + "sql" : "SELECT\n COUNT(1), CAST(event_ts AS DATE)\nFROM events\nGROUP BY 2", + "dialect" : "spark", + "default-catalog" : "prod", + "default-namespace" : [ "default" ], + "field-aliases" : ["event_count", "event_date"], + "field-comments" : ["Count of events", null] + } ] + }, { + "version-id" : 2, + "timestamp-ms" : 1573518981593, + "summary" : { + "operation" : "create", + "engine-name" : "Spark", + "engineVersion" : "3.3.2" + }, + "representations" : [ { + "type" : "sql", + "sql" : "SELECT\n COUNT(1), CAST(event_ts AS DATE)\nFROM prod.default.events\nGROUP BY 2", + "dialect" : "spark", + "default-catalog" : "prod", + "default-namespace" : [ "default" ], + "field-aliases" : ["event_count", "event_date"] + } ] + } ], + "current-schema-id": 1, + "schemas": [ { + "schema-id": 1, + "type" : "struct", + "fields" : [ { + "id" : 1, + "name" : "col1", + "required" : false, + "type" : "int" + }, { + "id" : 2, + "name" : "col2", + "required" : false, + "type" : "date" + } ] + } ], + "version-log" : [ { + "timestamp-ms" : 1573518431292, + "version-id" : 1 + }, { + "timestamp-ms" : 1573518981593, + "version-id" : 2 + } ] +} +``` diff --git a/site/mkdocs.yml b/site/mkdocs.yml new file mode 100644 index 000000000000..c503cea889b4 --- /dev/null +++ b/site/mkdocs.yml @@ -0,0 +1,96 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +site_name: Apache Iceberg + +theme: + name: material + language: en + logo: assets/images/iceberg-logo-icon.png + favicon: assets/images/favicon-96x96.png + features: + - navigation.tabs + - navigation.path + - navigation.top + - navigation.tracking + - toc.follow + - search.suggest + - search.highlight + - content.tabs.link + - content.code.copy + - content.code.annotate + +plugins: + - search + - macros: + include_yaml: + - variables.yml + - monorepo + +nav: + - Quickstart: + - Spark: spark-quickstart.md + - Hive: hive-quickstart.md + - Docs: + - nightly: '!include docs/docs/nightly/mkdocs.yml' + - latest: '!include docs/docs/latest/mkdocs.yml' + - 1.3.1: '!include docs/docs/1.3.1/mkdocs.yml' +# - 1.3.0: '!include docs/docs/1.3.0/mkdocs.yml' + - Releases: releases.md + - Roadmap: roadmap.md + - Blogs: blogs.md + - Talks: talks.md + - Vendors: vendors.md + - Project: + - Join: community.md + - Spec: spec.md + - View spec: view-spec.md + - Puffin spec: puffin-spec.md + - Multi-engine support: multi-engine-support.md + - How to release: how-to-release.md + - Terms: terms.md + - ASF: + - community.md + - Sponsorship: https://www.apache.org/foundation/sponsorship.html + - Events: https://www.apache.org/events/current-event.html + - License: https://www.apache.org/licenses/ + - Security: https://www.apache.org/security/ + - Sponsors: https://www.apache.org/foundation/thanks.html + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - admonition + - pymdownx.arithmatex: + generic: true + - footnotes + - pymdownx.details + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.mark + - attr_list + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - tables + + +copyright: | + Apache Iceberg, Iceberg, Apache, the Apache feather logo, and the Apache Iceberg project logo are
either registered trademarks or trademarks of The Apache Software Foundation. Copyright © 2023
The Apache Software Foundation, Licensed under the
Apache License, Version 2.0.

diff --git a/site/requirements.txt b/site/requirements.txt new file mode 100644 index 000000000000..58e6d4982f00 --- /dev/null +++ b/site/requirements.txt @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +mkdocs-awesome-pages-plugin==2.9.2 +mkdocs-macros-plugin==1.0.4 +mkdocs-material==9.1.21 +mkdocs-material-extensions==1.1.1 +mkdocs-monorepo-plugin==1.0.5 +mkdocs-redirects==1.2.1 diff --git a/site/variables.yml b/site/variables.yml new file mode 100644 index 000000000000..2514e2dda37c --- /dev/null +++ b/site/variables.yml @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +extra: + icebergVersion: nightly + social: + - icon: fontawesome/brands/github-alt + link: https://github.com/apache/iceberg + - icon: fontawesome/brands/youtube + link: https://www.youtube.com/@ApacheIceberg + - icon: fontawesome/brands/slack + link: https://join.slack.com/t/apache-iceberg/shared_invite/zt-1znkcg5zm-7_FE~pcox347XwZE3GNfPg