# Phoenix Omid Transaction Manager (/docs/addons/phoenix-omid-transaction-manager)
The Apache Omid podling has recently decided to graduate as a sub-project of Apache Phoenix.
This graduation process is currently ongoing. Please follow the Apache Phoenix mailing lists
for more information about this effort.
# Phoenix ORM library (/docs/addons/phoenix-orm-library)
PHO is an Object Relational Mapping (ORM) library for building and executing queries on HBase using Apache Phoenix.
It provides ORM-style mappings and DSL-style query building. Initially developed and open sourced by eHarmony, it is available on [GitHub](https://github.com/eHarmony/pho).
Its interfaces and generic annotations make it possible to switch data store APIs in the future without changing query definitions.
Currently, it supports HBase integration through Apache Phoenix, and can be extended with other implementations.
## Entity Class
Suppose we have the following `TestClass` we want to query in our data store:
```java
// class must be annotated with Entity
import com.google.code.morphia.annotations.Embedded;
import com.google.code.morphia.annotations.Entity;
@Entity(value = "user_matches")
public class MatchDataFeedItemDto {
@Embedded
private MatchCommunicationElement communication;
@Embedded
private MatchElement match;
@Embedded
private MatchProfileElement matchedUser;
}
public class MatchElement {
// row key
@Property(value = "UID")
private long userId;
@Property(value = "MID")
private long matchId;
@Property(value = "DLVRYDT")
private Date deliveredDate;
@Property(value = "STATUS")
private int status;
}
```
## Query Building
Query building can be done in DSL style. More advanced query building is under development, but for now we will use a combination of `QueryBuilder` and static, Hibernate-style `Restrictions` methods to construct queries.
### Simple queries
Construct a query to find all user matches delivered in the past two days and not in a closed state.
```java
import com.eharmony.datastore.api.DataStoreApi;
import com.eharmony.datastore.model.MatchDataFeedItemDto;
import com.eharmony.datastore.query.QuerySelect;
import com.eharmony.datastore.query.builder.QueryBuilder;
import com.eharmony.datastore.query.criterion.Restrictions;
@Repository
public class MatchStoreQueryRepositoryImpl implements MatchStoreQueryRepository {
final QuerySelect query = QueryBuilder
.builderFor(MatchDataFeedItemDto.class)
.select()
.add(Restrictions.eq("userId", userId))
.add(Restrictions.eq("status", 2))
.add(Restrictions.gt("deliveredDate", timeThreshold.getTime()))
.build();
Iterable feedItems = dataStoreApi.findAll(query);
}
```
### Compound queries
Construct a more complex query where we find items older than one day, include multiple status values, order by `deliveryDate`, and limit result size to 10.
```java
// provided
List statusFilters = request.getMatchStatusFilters();
String sortBy = request.getSortBy();
Disjunction disjunction = new Disjunction();
for (Integer statusFilter : statusFilters) {
disjunction.add(Restrictions.eq("status", statusFilter));
}
final QuerySelect query = QueryBuilder
.builderFor(MatchDataFeedItemDto.class)
.select()
.add(Restrictions.eq("userId", userId))
.add(Restrictions.gt("deliveredDate", timeThreshold.getTime()))
.add(disjunction)
.addOrder(new Ordering(sortBy, Order.DESCENDING))
.build();
Iterable feedItems = dataStoreApi.findAll(query);
```
By default, expressions are combined with `AND` when added separately.
### Query Interface
The following query components are supported:
```java
// equals
EqualityExpression eq(String propertyName, Object value);
// does not equal
EqualityExpression ne(String propertyName, Object value);
// less than
EqualityExpression lt(String propertyName, Object value);
// less than or equal
EqualityExpression lte(String propertyName, Object value);
// greater than
EqualityExpression gt(String propertyName, Object value);
// greater than or equal
EqualityExpression gte(String propertyName, Object value);
// between from and to (inclusive)
RangeExpression between(String propertyName, Object from, Object to);
// and - takes a variable list of expressions as arguments
Conjunction and(Criterion... criteria);
// or - takes a variable list of expressions as arguments
Disjunction or(Criterion... criteria);
```
### Resolving Entity and Property Names
Always use the property names of your Java objects in your queries.
If these names differ from those used in your datastore, use annotations to provide mappings.
Entity resolvers are configured to map entity classes to table or collection names.
Property resolvers are configured to map object variable names to column or field names.
The following annotations are currently supported for the indicated data store type.
Custom `EntityResolvers` and `PropertyResolvers` are straightforward to configure and create.
See [Morphia annotations](https://code.google.com/p/morphia/wiki/AllAnnotations) for entity class annotation mappings.
## Query Execution
The QueryExecutor interface supports the following operations:
```java
// return an iterable of type R from the query against type T
// (R and T are often the same type)
Iterable findAll(QuerySelect query);
// return one R from the query against type T
R findOne(QuerySelect query);
// save the entity of type T to the data store
T save(T entity);
// save all entities in the provided iterable to the data store
Iterable save(Iterable entities);
// save entities in batches with a configured batch size
int[] saveBatch(Iterable entities);
```
## Configuration
Here are some example Spring configuration files for HBase using Apache Phoenix.
### HBase
Configuration properties:
```properties
hbase.connection.url=jdbc:phoenix:zkhost:2181
```
```xml
com.eharmony.datastore.model.MatchDataFeedItemDto
```
# Phoenix Tephra Transaction Manager (/docs/addons/phoenix-tephra-transaction-manager)
The Apache Tephra podling has recently decided to graduate as a sub-project of Apache Phoenix.
This graduation process is currently ongoing. Please follow the Apache Phoenix mailing lists
for more information about this effort.
# Array Type (/docs/array-type)
Apache Phoenix 3.0/4.0 introduced support for the [JDBC ARRAY type](http://docs.oracle.com/javase/tutorial/jdbc/basics/array.html). Any primitive type may be used in an `ARRAY`. Here is an example of declaring an array type when creating a table:
```sql
CREATE TABLE regions (
region_name VARCHAR NOT NULL PRIMARY KEY,
zips VARCHAR ARRAY[10]
);
```
or alternately:
```sql
CREATE TABLE regions (
region_name VARCHAR NOT NULL PRIMARY KEY,
zips VARCHAR[]
);
```
Insertion into the array may be done entirely through a SQL statement:
```sql
UPSERT INTO regions(region_name, zips)
VALUES ('SF Bay Area', ARRAY['94115', '94030', '94125']);
```
or programmatically through JDBC:
```java
PreparedStatement stmt = conn.prepareStatement("UPSERT INTO regions VALUES(?,?)");
stmt.setString(1, "SF Bay Area");
String[] zips = new String[] {"94115", "94030", "94125"};
Array array = conn.createArrayOf("VARCHAR", zips);
stmt.setArray(2, array);
stmt.execute();
```
The entire array may be selected:
```sql
SELECT zips FROM regions WHERE region_name = 'SF Bay Area';
```
or an individual element in the array may be accessed via a subscript notation. The subscript is one-based, so the following would select the first element:
```sql
SELECT zips[1] FROM regions WHERE region_name = 'SF Bay Area';
```
Use of the array subscript notation is supported in other expressions as well, for example in a WHERE clause:
```sql
SELECT region_name FROM regions WHERE zips[1] = '94030' OR zips[2] = '94030' OR zips[3] = '94030';
```
The array length grows dynamically as needed and can be accessed through the built-in `ARRAY_LENGTH` function:
```sql
SELECT ARRAY_LENGTH(zips) FROM regions;
```
Attempts to access an array element beyond the current length will evaluate to `null`.
For searching in an array, built-in functions like ANY and ALL are provided. For example,
```sql
SELECT region_name FROM regions WHERE '94030' = ANY(zips);
SELECT region_name FROM regions WHERE '94030' = ALL(zips);
```
The built-in function `ANY` checks if any array element satisfies the condition and is equivalent to an `OR` condition:
```sql
SELECT region_name FROM regions WHERE zips[1] = '94030' OR zips[2] = '94030' OR zips[3] = '94030';
```
The built-in function `ALL` checks if all array elements satisfy the condition and is equivalent to an `AND` condition:
```sql
SELECT region_name FROM regions WHERE zips[1] = '94030' AND zips[2] = '94030' AND zips[3] = '94030';
```
## Limitations
* Only one-dimensional arrays are currently supported.
* For an array of fixed width types, null elements occurring in the middle of an array are not tracked.
* The declaration of an array length at DDL time is not enforced currently, but maybe in the future. Note that it is persisted with the table metadata.
* An array may only be used as the last column in a primary key constraint.
* Partial update of an array is currently not possible. Instead, the array may be manipulated on the client-side and then upserted back in its entirety.
# Building Website (/docs/contributing/building-website)
## Prerequisites
* Node.js 22.x (minimum `22.12.0`)
* npm (bundled with Node.js)
## Building Phoenix Project Website
1. Clone the repository:
```shell
git clone git@github.com:apache/phoenix-site.git
cd phoenix-site
```
2. During development, install dependencies:
```shell
npm ci
```
3. For local iteration, run checks/build directly when needed:
```shell
npm run ci
```
4. Before opening a pull request, run the mandatory build script:
```shell
./build.sh
```
`build.sh` is required for all contributors. It:
* ensures Node/npm are available
* runs a clean install (`npm ci`)
* runs full validation/build (`npm run ci`)
* copies `build/client/` into `output/`
There is no remote CI/CD runner currently executing this script. The generated `output/` directory is a build artifact that must be committed and pushed with your PR.
## Publishing Website Artifact
Current publishing flow:
1. Run `./build.sh` locally
2. Commit both source changes and updated `output/`
3. Push your branch and open a PR
After merge, `output/` is used as the website artifact for deployment.
## Local Testing During Development
Start the development server with hot reload:
```shell
npm run dev
```
By default, the website is available at:
```shell
http://localhost:5173
```
To test the production build locally:
```shell
npm run start
```
# Developing Phoenix (/docs/contributing/develop)
## Getting Started
1. Review the [How to Contribute](/docs/contributing) documentation.
2. Sign up for a [GitHub](https://github.com/) account if you do not have one.
3. Go to the [Phoenix GitHub repository](https://github.com/apache/phoenix) and create a fork, which creates `{username}/phoenix`.
4. Set up Git locally.
* [Instructions](https://help.github.com/articles/set-up-git/)
* [Download](https://git-scm.com/downloads)
5. Make sure you have a JDK (Phoenix historically required [JDK 7](https://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html)).
6. Make sure you have [Maven 3+](https://maven.apache.org/download.cgi) installed.
7. Add the following to your `.bashrc` or equivalent to ensure Maven and Java are configured correctly for command-line usage.
```shell
export JAVA_HOME={path to jdk}
export JDK_HOME={path to jdk}
export M2_HOME={path to maven}
export PATH=$M2_HOME/bin:$PATH
```
## Other Phoenix Subprojects
The instructions here are for the main Phoenix project. For the other subprojects, use the corresponding [repository](/source-repository) and [JIRA project](/issues-tracking).
The Eclipse and IntelliJ setup instructions may not necessarily work well for the other projects.
## Setup Local Git Repository
You may find it easier to clone from your IDE of choice, especially with IntelliJ.
1. Create a local clone of your new forked repository
```shell
git clone https://github.com/{username}/phoenix.git
```
2. Configure your local repository to be able to sync with the apache/phoenix repository
```shell
cd {repository}
git remote add upstream https://github.com/apache/phoenix.git
```
3. Setup your development environment
## For Eclipse IDE for Java Developers (Luna)
1. [Download Eclipse](https://eclipse.org/downloads/packages/eclipse-ide-java-developers/lunar)
* You will want 'Eclipse IDE for Java Developers' unless you want to install the following tools by hand
* [m2e](http://download.eclipse.org/technology/m2e/releases/)
* [egit](http://www.eclipse.org/egit/download/)
2. Configure Eclipse to handle Maven Issues appropriately so you don't see unnecessary errors.
* Window -> Preferences -> Maven -> Errors/Warnings
* Choose Ignore option for 'Plugin execution not covered by lifecycle configuration' -> Ok
3. Add the local Git repository to Eclipse
* Window -> Show View -> Other... -> Git | Git Repositories -> Ok
* Click 'Add an existing local Git Repository to this view'
* Search for appropriate git repository
* Finish
4. Import Maven Projects
* File -> Import -> Maven -> Existing Maven Projects
* Choose Root directory where phoenix git repository is located
* Select All
* Finish
5. Generate Lexer and Parser Files
* Select phoenix-core project
* Run -> Run As -> Maven generate-sources
6. Make sure you are setup to develop now.
* Open IndexUtilTest.Java
* Run -> Run As -> JUnit Test
### Get Settings and Preferences Correct
1. Import General Preferences
* File -> Import... -> General -> Preferences
* From - `{repository}/dev/eclipse_prefs_phoenix.epf`
* Import All
* Finish
2. Import Code Templates
* Window -> Preferences -> Java -> Code Style -> Code Templates -> Import...
* Navigate to `{repository}/dev/PhoenixCodeTemplate.xml` -> Ok
3. Import Formatter
* Window -> Preferences -> Java -> Code Style -> Formatter-> Import...
* Navigate to `{repository}/dev/PhoenixCodeTemplate.xml` -> Ok
4. Import correct import order settings
* Window -> Preferences -> Java -> Code Style -> Organize Imports -> Import...
* Navigate to `{repository}/dev/phoenix.importorder` -> Ok
5. Make sure you use space for tabs
* Window -> Preferences -> General -> Editors -> Text Editors
* Select 'Insert Spaces for tabs' -> Ok
### Connecting to Jira
1. Install Connector for Jira
* Help -> Install New Software -> Add
* Location - [https://update.atlassian.com/atlassian-eclipse-plugin/rest/e3.7](https://update.atlassian.com/atlassian-eclipse-plugin/rest/e3.7) -> Atlassian Connector
* Finish
2. Add Task Repository
* Window -> Show View -> Mylyn -> Task Repositories -> Add Task Repository
* JIRA -> Next -> Server - [https://issues.apache.org/jira](https://issues.apache.org/jira) -> Validate Settings
* Finish
3. Add Filter Of All JIRAs assigned to you
* Right Click on Repository You added -> New Query... -> Predefined Filter
* Select Phoenix Project -> Select Assigned to me
* Finish
### Commit
1. Commit Changes and Push to Github with appropriate Message
* CTRL-# -> Set Commit message to include jira number at beginning PHOENIX-####
* Commit and Push
## For Intellij
* [Download IntelliJ](https://www.jetbrains.com/idea/download/)
### If you don't have a local git repository setup
This will automatically create the local clone of your repository for you. You will still want to add the remote upstream repository from above afterwards.
1. Clone Github project and Import Maven Projects to IDE
* Check out from Version Control -> GitHub -> Enter your GitHub Login Info
* `https://github.com/{username}/phoenix.git` -> Check out from Version Control | Yes
2. Generate Parser and Lexer Files
* Maven Projects -> Phoenix Core -> Lifecycle -> compile
3. Compile Project
* Build -> Make Project
4. Make sure you are setup to develop now.
* Open IndexUtilTest.Java -> Run -> Run IndexUtilTest
### If you already have a local git repository setup
1. Import Projects
* Import Project
* Select Directory of your local repository -> Next
* Import project from external model -> Maven -> Next
* Select 'Import Maven project automatically'
* Select 'Create IntelliJ IDEA modules for aggregator projects'
* Select 'Keep source and test folders on reimport'
* Select 'Exclude build directory'
* Select 'Use Maven output directories' -> Next
* Select maven-3 -> Next
* Next a whole bunch
2. Generate Parser and Lexer Files
* Maven Projects -> Phoenix Core -> Lifecycle -> compile
3. Compile Project
* Build -> Make Project
4. Make sure you are setup to develop now.
* Open IndexUtilTest.Java -> Run -> Run IndexUtilTest
### Get Settings and Preferences Correct
1. Import Settings from eclipse profile
* File -> Settings -> Editor -> Code Style -> Java
* Set From... -> Import... -> Eclipse XML Profile -> `{repository}/dev/PhoenixCodeTemplate.xml`
### Connecting to Jira
1. Create Connection to Apache Jira
* Tools -> Tasks and Contexts -> Configure Servers -> + -> Jira ->
* Server Url: `https://issues.apache.org/jira`
* Query: 'project=Phoenix and ...'
2. Switch Easily between Tasks
* Tools-> Tasks and Contexts -> Open Task->PHOENIX-####
* Select Create branch PHOENIX-#### from master->OK
### Commit
1. Commit Changes and Push to Github with appropriate Message
* VCS -> Commit -> Set Commit message to include jira number PHOENIX-####
* Commit and Push
## Contributing finished work
### Create pull request
1. Review the [How to Contribute](/docs/contributing) documentation.
2. Navigate to branch: `https://github.com/{username}/phoenix/tree/{branchname}`
3. Click Pull Request
4. Confirm that you see `apache:master ... {username}:{branchname}`
5. Make sure the pull request title begins with the JIRA key, for example `PHOENIX-####`.
6. Click Create pull request.
# How to Release (/docs/contributing/how-to-release)
## How to Release
Phoenix has several repos: `phoenix-thirdparty`, `phoenix-omid`, `phoenix-tephra`, `phoenix`, `phoenix-connectors`, and `phoenix-queryserver`.
The create-release scripts provide a unified script to handle the release from each repo.
The create-release scripts are in the `master` branch of the `phoenix` repo, in the `dev/create-release` directory.
## Pre-Reqs
1. Make sure that the JIRAs included in the release have their fix-version and release notes fields set correctly, and are resolved.\
The script will query them and create the `CHANGES` and `RELEASE_NOTES` files from that information.\
Use `dev/misc_utils/git_jira_fix_version_check.py` to find discrepancies between commits and JIRAs.
2. Make sure you have set up your user for release signing. Details: [http://www.apache.org/dev/release-signing.html](http://www.apache.org/dev/release-signing.html).
3. Make sure you have set up Maven for deploying to the ASF repo. Details: [https://infra.apache.org/publishing-maven-artifacts.html](https://infra.apache.org/publishing-maven-artifacts.html).
4. Clone the Phoenix `master` branch locally (the script will download the actual repo to release itself).
5. Make sure Docker is running locally.
Note that Docker Desktop for Mac works, but will be slow (several hours for a Phoenix core release). Running on a native Linux machine is much faster because you avoid filesystem translation layer overhead.
## Do a dry run
Read `/dev/create_release/README.txt` to understand what the script does and how to set up `gpg-agent` for signing.
Run the `dev/create-release/do-release-docker.sh -d -p ` command, where
* `` is the repo name you're releasing from (i.e. phoenix)
* `` is any existing directory that can be deleted
The script will ask a number of questions. Some of them will have intelligent default, but make sure you check them all:
```text
[stoty@IstvanToth-MBP15:~/workspaces/apache-phoenix/phoenix (PHOENIX-6307)$]dev/create-release/do-release-docker.sh -p phoenix -d ~/x/phoenix-build/
Output directory already exists. Overwrite and continue? [y/n] y
========================
=== Gathering release details.
PROJECT [phoenix]:
GIT_BRANCH []: master
Current branch VERSION is 5.1.0-SNAPSHOT.
RELEASE_VERSION [5.1.0]:
RC_COUNT [0]:
RELEASE_TAG [5.1.0RC0]:
This is a dry run. If tag does not actually exist, please confirm the ref that will be built for testing.
GIT_REF [5.1.0RC0]:
ASF_USERNAME [stoty]:
GIT_NAME [Istvan Toth]:
GPG_KEY [stoty@apache.org]:
We think the key 'stoty@apache.org' corresponds to the key id '0x77E592D4'. Is this correct [y/n]? y
================
Release details:
GIT_BRANCH: master
RELEASE_VERSION: 5.1.0
RELEASE_TAG: 5.1.0RC0
API_DIFF_TAG:
ASF_USERNAME: stoty
GPG_KEY: 0x77E592D4
GIT_NAME: Istvan Toth
GIT_EMAIL: stoty@apache.org
DRY_RUN: yes
================
```
* `PROJECT`: the repo to release (default specified by `-p` on the command line)
* `GIT_BRANCH`: the git branch to use for release. This can be `master`, or a pre-created release branch.
* `RC_COUNT`: the RC number, starting from 0
* `RELEASE_TAG`: the git tag the script will tag the RC commit with.
* `ASF_USERNAME`: your ASF username, for publishing the release artifacts.
* `ASF_PASSWORD`: your ASF password, only for non-dry runs
* `GIT_NAME`/`GIT_EMAIL`: will be used for the RC commit
* `GPG_KEY`: ID for your GPG key. The script will offer a GPG secret key from your key ring, double-check that it is your code-signing key, and correct it if it is not.
The dry-run will generate the signed release files into `/output` directory.
The Maven artifacts will be in `/output/phoenix-repo-XXXX` local Maven repo, in the usual structure.
## Create real RC
If the dry-run release artifacts (source, binary, and Maven) check out, then publish a real RC to ASF.
Repeat the dry run process, but add the `-f` switch to the `do-release-docker.sh` command.
The script will upload the source and binary release artifacts to a directory under `https://dist.apache.org/repos/dist/dev/phoenix/`.
The script will also deploy the Maven artifacts to `https://repository.apache.org/#stagingRepositories`.
Check that these are present.
### Close the staging repository
The published staging repository is in the "open" state. Open staging repositories
are aggressively cleaned up, and may be removed by the time the vote passes.
To avoid this, [close](https://central.sonatype.org/publish/release/#locate-and-examine-your-staging-repository)
(but DO NOT release) the staging repository immediately after deployment.
## Voting
1. Initiate the vote email. See example [here](https://www.mail-archive.com/dev@phoenix.apache.org/msg41202.html), or use the `/output/vote.txt` template generated by the script.
2. In case the RC (Release Candidate) is rejected via the vote, you will have to repeat the above process and re-initiate the vote for the next RC (RC0, RC1, etc.).
## Release
1. Once voting is successful (say for RC1), copy artifacts to `https://dist.apache.org/repos/dist/release/phoenix`:
```shell
svn mv https://dist.apache.org/repos/dist/dev/phoenix/apache-phoenix-4.15.0-HBase-1.3-rc1 \
https://dist.apache.org/repos/dist/release/phoenix/apache-phoenix-4.15.0-HBase-1.3
```
2. Set release tag and commit:
```shell
git tag -a v4.15.0-HBase-1.3 v4.15.0-HBase-1.3-rc1 -m "Phoenix v4.15.0-HBase-1.3 release"
```
3. Remove any obsolete releases on `https://dist.apache.org/repos/dist/release/phoenix` given the current release.
4. Go to `https://repository.apache.org/#stagingRepositories` and release the staged artifacts (this takes a while so you may need to refresh multiple times).
5. Create new branch based on current release if needed, for ex: 4.15 branches in this case.
6. Set version to the upcoming `SNAPSHOT` and commit:
```shell
mvn versions:set -DnewVersion=4.16.0-HBase-1.3-SNAPSHOT -DgenerateBackupPoms=false
```
7. If releasing Phoenix (core) Create a JIRA to update `PHOENIX_MAJOR_VERSION`, `PHOENIX_MINOR_VERSION` and `PHOENIX_PATCH_NUMBER` in `MetaDataProtocol.java` appropriately to next version (4, 16, 0 respectively in this case) and `compatible_client_versions.json` file with the client versions that are compatible against the next version (in this case 4.14.3 and 4.15.0 would be the backward compatible clients for 4.16.0). This JIRA should be committed/marked with fixVersion of the next release candidate.
8. Add documentation of released version to the [downloads page](/downloads) and [wiki](https://en.wikipedia.org/wiki/Apache_Phoenix).
9. Update the [Apache Committee Report Helper DB](https://reporter.apache.org/addrelease.html?phoenix)
10. Send out an announcement email. See example [here](https://www.mail-archive.com/dev@phoenix.apache.org/msg54764.html).
11. Bulk close JIRAs that were marked for the release fixVersion.
**Congratulations!**
# Contributing (/docs/contributing)
## General process
The general process for contributing code to Phoenix works as follows:
1. Discuss your changes on the dev mailing list
2. Create a JIRA issue unless there already is one
3. Setup your development environment
4. Prepare a patch containing your changes
5. Submit the patch
These steps are explained in greater detail below.
Note that the instructions below are for the main Phoenix project.
Use the corresponding [repository](/source-repository) for the other subprojects.
Tephra and Omid also have their own [JIRA projects](/issues-tracking).
### Discuss on the mailing list
It's often best to discuss a change on the public mailing lists before creating and submitting a patch.
If you're unsure whether certain behavior in Phoenix is a bug, please send a mail to the [user mailing list](/mailing-lists) to check.
If you're considering adding major new functionality to Phoenix, it's a good idea to first discuss the idea on the [developer mailing list](/mailing-lists) to make sure that your plans are in line with others in the Phoenix community.
### Log a JIRA ticket
The first step is to create a ticket on the [Phoenix JIRA](https://issues.apache.org/jira/browse/PHOENIX).
### Setup development environment
To set up your development environment, see [these directions](/docs/contributing/develop).
### Generate a patch
There are two general approaches for creating and submitting a patch: GitHub pull requests, or manual patch creation with Git. Both are explained below. Please make sure that the patch applies cleanly on all active branches, including **master** and the unified **4.x** branch.
Regardless of which approach is taken, please make sure to follow the Phoenix code conventions (more information below). Whenever possible, unit tests or integration tests should be included with patches.
Please make sure that the patch contains only one commit, then click the "Submit patch" button to automatically trigger tests on the patch.
The commit message should reference the JIRA ticket issue (which has the format
`PHOENIX-{NUMBER}:{JIRA-TITLE}`).
To effectively get the patch reviewed, please raise the pull request against an appropriate branch.
#### Naming convention for the patch
When generating a patch, make sure the patch name uses the following format:
`PHOENIX-{NUMBER}.{BRANCH-NAME}.{VERSION}.patch`
Examples: `PHOENIX-4872.master.v1.patch`, `PHOENIX-4872.master.v2.patch`, `PHOENIX-4872.4.x-HBase-1.3.v1.patch`, etc.
#### GitHub workflow
1. Create a pull request in GitHub for the [mirror of the Phoenix Git repository](https://github.com/apache/phoenix).
2. Generate a patch and attach it to JIRA so Hadoop QA runs automated tests.
3. If you update the PR, generate a new patch with a different name so patch changes are detected and tests run for the new patch.
#### Local Git workflow
1. Create a local branch:
```shell
git checkout -b
```
2. Make and commit changes
3. Generate a patch based on the JIRA issue number:
```shell
git format-patch --stdout HEAD^ > PHOENIX-{NUMBER}.patch
```
4. Attach the created patch file to the JIRA ticket.
## Code conventions
The Phoenix code conventions are similar to the [Sun/Oracle Java Code Convention](https://www.oracle.com/technetwork/java/index-135089.html). We use 4 spaces (no tabs) for indentation and limit lines to 100 characters.
Eclipse code formatting settings and import order settings (which can also be imported into Intellij IDEA) are available in the dev directory of the Phoenix codebase.
All new source files should include the Apache license header.
## Committer workflow
In general, the "rebase" workflow should be used with the Phoenix codebase (see [this blog post](http://randyfay.com/content/rebase-workflow-git) for more information on the difference between "merge" and "rebase" workflows in Git).
A patch file can be downloaded from a GitHub pull request by adding `.patch` to the end of the pull request URL, for example: `https://github.com/apache/phoenix/pull/35.patch`.
When applying a user-contributed patch, use `git am` when a fully formatted patch file is available, as this preserves contributor contact information. Otherwise, add the contributor's name to the commit message.
If a single ticket consists of a patch with multiple commits, the commits can be squashed into a single commit using `git rebase`.
# Data Types (/docs/datatypes)
export const datatypesTopicIndex = {
"select": "select",
"upsert values": "upsert-values",
"upsert select": "upsert-select",
"delete": "delete",
"declare cursor": "declare-cursor",
"open cursor": "open-cursor",
"fetch next": "fetch-next",
"close": "close",
"create table": "create-table",
"drop table": "drop-table",
"create function": "create-function",
"drop function": "drop-function",
"create view": "create-view",
"drop view": "drop-view",
"create sequence": "create-sequence",
"drop sequence": "drop-sequence",
"alter": "alter",
"create index": "create-index",
"drop index": "drop-index",
"alter index": "alter-index",
"explain": "explain",
"constraint": "constraint",
"update statistics": "update-statistics",
"create schema": "create-schema",
"use": "use",
"drop schema": "drop-schema",
"grant": "grant",
"revoke": "revoke",
"options": "options",
"hint": "hint",
"scan hint": "scan-hint",
"cache hint": "cache-hint",
"index hint": "index-hint",
"small hint": "small-hint",
"seek to column hint": "seek-to-column-hint",
"join hint": "join-hint",
"serial hint": "serial-hint",
"column def": "column-def",
"table ref": "table-ref",
"sequence ref": "sequence-ref",
"column ref": "column-ref",
"select expression": "select-expression",
"select statement": "select-statement",
"split point": "split-point",
"table spec": "table-spec",
"aliased table ref": "aliased-table-ref",
"join type": "join-type",
"join": "join-type",
"func argument": "func-argument",
"class name": "class-name",
"jar path": "jar-path",
"order": "order",
"expression": "expression",
"and condition": "and-condition",
"boolean condition": "boolean-condition",
"condition": "condition",
"rhs operand": "rhs-operand",
"operand": "operand",
"summand": "summand",
"factor": "factor",
"term": "term",
"array constructor": "array-constructor",
"sequence": "sequence",
"cast": "cast",
"row value constructor": "row-value-constructor",
"bind parameter": "bind-parameter",
"value": "value",
"case": "case",
"case when": "case-when",
"name": "name",
"quoted name": "quoted-name",
"alias": "alias",
"null": "null",
"data type": "data-type",
"data": "data-type",
"sql data type": "sql-data-type",
"sql data": "sql-data-type",
"hbase data type": "hbase-data-type",
"hbase data": "hbase-data-type",
"string": "string",
"boolean": "boolean-type",
"numeric": "numeric",
"int": "int",
"long": "long",
"decimal": "decimal-type",
"number": "number",
"comments": "comments",
"integer type": "integer-type",
"integer": "integer-type",
"unsigned_int type": "unsigned-int-type",
"unsigned_int": "unsigned-int-type",
"bigint type": "bigint-type",
"bigint": "bigint-type",
"unsigned_long type": "unsigned-long-type",
"unsigned_long": "unsigned-long-type",
"tinyint type": "tinyint-type",
"tinyint": "tinyint-type",
"unsigned_tinyint type": "unsigned-tinyint-type",
"unsigned_tinyint": "unsigned-tinyint-type",
"smallint type": "smallint-type",
"smallint": "smallint-type",
"unsigned_smallint type": "unsigned-smallint-type",
"unsigned_smallint": "unsigned-smallint-type",
"float type": "float-type",
"float": "float-type",
"unsigned_float type": "unsigned-float-type",
"unsigned_float": "unsigned-float-type",
"double type": "double-type",
"double": "double-type",
"unsigned_double type": "unsigned-double-type",
"unsigned_double": "unsigned-double-type",
"decimal type": "decimal-type",
"boolean type": "boolean-type",
"time type": "time-type",
"time": "time-type",
"date type": "date-type",
"date": "date-type",
"timestamp type": "timestamp-type",
"timestamp": "timestamp-type",
"unsigned_time type": "unsigned-time-type",
"unsigned_time": "unsigned-time-type",
"unsigned_date type": "unsigned-date-type",
"unsigned_date": "unsigned-date-type",
"unsigned_timestamp type": "unsigned-timestamp-type",
"unsigned_timestamp": "unsigned-timestamp-type",
"varchar type": "varchar-type",
"varchar": "varchar-type",
"char type": "char-type",
"char": "char-type",
"binary type": "binary-type",
"binary": "binary-type",
"varbinary type": "varbinary-type",
"varbinary": "varbinary-type",
"array": "array",
"avg": "avg",
"count": "count",
"approx_count_distinct": "approx-count-distinct",
"max": "max",
"min": "min",
"sum": "sum",
"percentile_cont": "percentile-cont",
"percentile_disc": "percentile-disc",
"percent_rank": "percent-rank",
"first_value": "first-value",
"last_value": "last-value",
"first_values": "first-values",
"last_values": "last-values",
"nth_value": "nth-value",
"stddev_pop": "stddev-pop",
"stddev_samp": "stddev-samp",
"round": "round",
"ceil": "ceil",
"floor": "floor",
"trunc": "trunc",
"substr": "substr",
"instr": "instr",
"trim": "trim",
"ltrim": "ltrim",
"rtrim": "rtrim",
"lpad": "lpad",
"array_elem": "array-elem",
"array_length": "array-length",
"array_append": "array-append",
"array_prepend": "array-prepend",
"array_cat": "array-cat",
"array_fill": "array-fill",
"array_to_string": "array-to-string",
"any": "any",
"all": "all",
"length": "length",
"regexp_substr": "regexp-substr",
"regexp_replace": "regexp-replace",
"regexp_split": "regexp-split",
"md5": "md5",
"invert": "invert",
"encode": "encode",
"decode": "decode",
"to_number": "to-number",
"rand": "rand",
"upper": "upper",
"lower": "lower",
"reverse": "reverse",
"to_char": "to-char",
"to_date": "to-date",
"current_date": "current-date",
"to_time": "to-time",
"to_timestamp": "to-timestamp",
"current_time": "current-time",
"convert_tz": "convert-tz",
"timezone_offset": "timezone-offset",
"now": "now",
"year": "year",
"month": "month",
"week": "week",
"dayofyear": "dayofyear",
"dayofmonth": "dayofmonth",
"dayofweek": "dayofweek",
"hour": "hour",
"minute": "minute",
"second": "second",
"coalesce": "coalesce",
"sign": "sign",
"abs": "abs",
"sqrt": "sqrt",
"cbrt": "cbrt",
"exp": "exp",
"power": "power",
"ln": "ln",
"log": "log",
"get_bit": "get-bit",
"get_byte": "get-byte",
"octet_length": "octet-length",
"set_bit": "set-bit",
"set_byte": "set-byte",
"collation_key": "collation-key"
}
## Data Types
### INTEGER Type
Possible values: -2147483648 to 2147483647.
Mapped to `java.lang.Integer`. The binary representation is a 4 byte integer with the sign bit flipped (so that negative values sorts before positive values).
**Example**
```sql
INTEGER
```
### UNSIGNED\_INT Type
Possible values: 0 to 2147483647. Mapped to `java.lang.Integer`. The binary representation is a 4 byte integer, matching the `Bytes.toBytes(int)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_INT
```
### BIGINT Type
Possible values: -9223372036854775808 to 9223372036854775807. Mapped to `java.lang.Long`. The binary representation is an 8 byte long with the sign bit flipped (so that negative values sorts before positive values).
**Example**
```sql
BIGINT
```
### UNSIGNED\_LONG Type
Possible values: 0 to 9223372036854775807. Mapped to `java.lang.Long`. The binary representation is an 8 byte integer, matching the `Bytes.toBytes(long)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_LONG
```
### TINYINT Type
Possible values: -128 to 127. Mapped to `java.lang.Byte`. The binary representation is a single byte, with the sign bit flipped (so that negative values sorts before positive values).
**Example**
```sql
TINYINT
```
### UNSIGNED\_TINYINT Type
Possible values: 0 to 127. Mapped to `java.lang.Byte`. The binary representation is a single byte, matching the `Bytes.toBytes(byte)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_TINYINT
```
### SMALLINT Type
Possible values: -32768 to 32767. Mapped to `java.lang.Short`. The binary representation is a 2 byte short with the sign bit flipped (so that negative values sort before positive values).
**Example**
```sql
SMALLINT
```
### UNSIGNED\_SMALLINT Type
Possible values: 0 to 32767. Mapped to `java.lang.Short`. The binary representation is an 2 byte integer, matching the `Bytes.toBytes(short)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_SMALLINT
```
### FLOAT Type
Possible values: -3.402823466 E + 38 to 3.402823466 E + 38. Mapped to `java.lang.Float`. The binary representation is an 4 byte float with the sign bit flipped (so that negative values sort before positive values).
**Example**
```sql
FLOAT
```
### UNSIGNED\_FLOAT Type
Possible values: 0 to 3.402823466 E + 38. Mapped to `java.lang.Float`. The binary representation is an 4 byte float matching the `Bytes.toBytes(float)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_FLOAT
```
### DOUBLE Type
Possible values: -1.7976931348623158 E + 308 to 1.7976931348623158 E + 308. Mapped to `java.lang.Double`. The binary representation is an 8 byte double with the sign bit flipped (so that negative values sort before positive value).
**Example**
```sql
DOUBLE
```
### UNSIGNED\_DOUBLE Type
Possible values: 0 to 1.7976931348623158 E + 308. Mapped to `java.lang.Double`. The binary representation is an 8 byte double matching the `Bytes.toBytes(double)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_DOUBLE
```
### DECIMAL Type
Data type with fixed precision and scale. A user can specify precision and scale by expression `DECIMAL(precision,scale)` in a DDL statement, for example, DECIMAL(10,2). The maximum precision is 38 digits. Mapped to `java.math.BigDecimal`. The binary representation is binary comparable, variable length format. When used in a row key, it is terminated with a null byte unless it is the last column.
**Example**
```sql
DECIMAL
DECIMAL(10,2)
```
### BOOLEAN Type
Possible values: `TRUE` and `FALSE`.
Mapped to `java.lang.Boolean`. The binary representation is a single byte with `0` for false and `1` for true
**Example**
```sql
BOOLEAN
```
### TIME Type
The time data type. The format is yyyy-MM-dd hh:mm:ss, with both the date and time parts maintained. Mapped to `java.sql.Time`. The binary representation is an 8 byte long (the number of milliseconds from the epoch), making it possible (although not necessarily recommended) to store more information within a TIME column than what is provided by "java.sql.Time". Note that the internal representation is based on a number of milliseconds since the epoch (which is based on a time in GMT), while "java.sql.Time" will format times based on the client's local time zone. Please note that this TIME type is different than the TIME type as defined by the SQL 92 standard in that it includes year, month, and day components. As such, it is not in compliance with the JDBC APIs. As the underlying data is still stored as a long, only the presentation of the value is incorrect.
**Example**
```sql
TIME
```
### DATE Type
The date data type. The format is yyyy-MM-dd hh:mm:ss, with both the date and time parts maintained to a millisecond accuracy. Mapped to `java.sql.Date`. The binary representation is an 8 byte long (the number of milliseconds from the epoch), making it possible (although not necessarily recommended) to store more information within a DATE column than what is provided by "java.sql.Date". Note that the internal representation is based on a number of milliseconds since the epoch (which is based on a time in GMT), while "java.sql.Date" will format dates based on the client's local time zone. Please note that this DATE type is different than the DATE type as defined by the SQL 92 standard in that it includes a time component. As such, it is not in compliance with the JDBC APIs. As the underlying data is still stored as a long, only the presentation of the value is incorrect.
**Example**
```sql
DATE
```
### TIMESTAMP Type
The timestamp data type. The format is yyyy-MM-dd hh:mm:ss\[.nnnnnnnnn]. Mapped to `java.sql.Timestamp` with an internal representation of the number of nanos from the epoch. The binary representation is 12 bytes: an 8 byte long for the epoch time plus a 4 byte integer for the nanos. Note that the internal representation is based on a number of milliseconds since the epoch (which is based on a time in GMT), while "java.sql.Timestamp" will format timestamps based on the client's local time zone.
**Example**
```sql
TIMESTAMP
```
### UNSIGNED\_TIME Type
The unsigned time data type. The format is yyyy-MM-dd hh:mm:ss, with both the date and time parts maintained to the millisecond accuracy. Mapped to `java.sql.Time`. The binary representation is an 8 byte long (the number of milliseconds from the epoch) matching the `HBase.toBytes(long)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_TIME
```
### UNSIGNED\_DATE Type
The unsigned date data type. The format is yyyy-MM-dd hh:mm:ss, with both the date and time parts maintained to a millisecond accuracy. Mapped to `java.sql.Date`. The binary representation is an 8 byte long (the number of milliseconds from the epoch) matching the `HBase.toBytes(long)` method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_DATE
```
### UNSIGNED\_TIMESTAMP Type
The timestamp data type. The format is yyyy-MM-dd hh:mm:ss\[.nnnnnnnnn]. Mapped to `java.sql.Timestamp` with an internal representation of the number of nanos from the epoch. The binary representation is 12 bytes: an 8 byte long for the epoch time plus a 4 byte integer for the nanos with the long serialized through the HBase.toBytes(long) method. The purpose of this type is to map to existing HBase data that was serialized using this HBase utility method. If that is not the case, use the regular signed type instead.
**Example**
```sql
UNSIGNED_TIMESTAMP
```
### VARCHAR Type
A variable length String with an optional max byte length. The binary representation is UTF8 matching the `Bytes.toBytes(String)` method. When used in a row key, it is terminated with a null byte unless it is the last column.
Mapped to `java.lang.String`.
**Example**
```sql
VARCHAR
VARCHAR(255)
```
### CHAR Type
A fixed length String with single-byte characters. The binary representation is UTF8 matching the `Bytes.toBytes(String)` method.
Mapped to `java.lang.String`.
**Example**
```sql
CHAR(10)
```
### BINARY Type
Raw fixed length byte array.
Mapped to `byte[]`.
**Example**
```sql
BINARY
```
### VARBINARY Type
Raw variable length byte array.
Mapped to `byte[]`.
**Example**
```sql
VARBINARY
```
### ARRAY
Mapped to `java.sql.Array`. Every primitive type except for `VARBINARY` may be declared as an `ARRAY`. Only single dimensional arrays are supported.
**Example**
```sql
VARCHAR ARRAY
CHAR(10) ARRAY [5]
INTEGER []
INTEGER [100]
```
# Explain Plan (/docs/explain-plan)
## Explain Plan
An `EXPLAIN` plan tells you a lot about how a query will be run:
* All the HBase range queries that will be executed
* An estimate of the number of bytes that will be scanned
* An estimate of the number of rows that will be traversed
* Time at which the above estimate information was collected
* Which HBase table will be used for each scan
* Which operations (sort, merge, scan, limit) are executed on the client versus the server
Use an `EXPLAIN` plan to check how a query will run, and consider rewriting queries to meet the following goals:
* Emphasize operations on the server rather than the client. Server operations are distributed across the cluster and operate in parallel, while client operations execute within the single client JDBC driver.
* Use `RANGE SCAN` or `SKIP SCAN` whenever possible rather than `TABLE SCAN`.
* Filter against leading columns in the primary key constraint. This assumes you have designed the primary key to lead with frequently-accessed or frequently-filtered columns as described in “Primary Keys,” above.
* If necessary, introduce a local index or a global index that covers your query.
* If you have an index that covers your query but the optimizer is not detecting it, try hinting the query:
`SELECT /*+ INDEX() */ …`
See also: [SQL Language Reference - EXPLAIN](/docs/grammar#explain)
## Anatomy of an Explain Plan
An explain plan consists of lines of text that describe operations that Phoenix will perform during a query, using the following terms:
* `AGGREGATE INTO ORDERED DISTINCT ROWS` — aggregates the returned rows using an operation such as addition. When `ORDERED` is used, the `GROUP BY` operation is applied to the leading part of the primary key constraint, which allows the aggregation to be done in place rather than keeping all distinct groups in memory on the server side.
* `AGGREGATE INTO SINGLE ROW` — aggregates the results into a single row using an aggregate function with no `GROUP BY` clause. For example, the `count()` statement returns one row with the total number of rows that match the query.
* `CLIENT` — the operation will be performed on the client side. It's faster to perform most operations on the server side, so you should consider whether there's a way to rewrite the query to give the server more of the work to do.
* `FILTER BY` expression—returns only results that match the expression.
* `FULL SCAN OVER` tableName—the operation will scan every row in the specified table.
* `INNER-JOIN` — the operation will join multiple tables on rows where the join condition is met.
* `MERGE SORT` — performs a merge sort on the results.
* `RANGE SCAN OVER` tableName `[` ... `]` — The information in the square brackets indicates the start and stop for each primary key that's used in the query.
* `ROUND ROBIN` — when the query doesn't contain `ORDER BY` and therefore the rows can be returned in any order, `ROUND ROBIN` order maximizes parallelization on the client side.
* `-CHUNK` — describes how many threads will be used for the operation. The maximum parallelism is limited to the number of threads in the thread pool. The minimum parallelization corresponds to the number of regions the table has between the start and stop rows of the scan. The number of chunks will increase with a lower guidepost width, as there is more than one chunk per region.
* `PARALLEL -WAY` — describes how many parallel scans will be merge sorted during the operation.
* `SERIAL` — some queries run serially. For example, a single row lookup or a query that filters on the leading part of the primary key and limits the results below a configurable threshold.
* `EST_BYTES_READ` - provides an estimate of the total number of bytes that will be scanned as part of executing the query.
* `EST_ROWS_READ` - provides an estimate of the total number of rows that will be scanned as part of executing the query.
* `EST_INFO_TS` - epoch time in milliseconds at which the estimate information was collected.
## Example
```text
+-----------------------------------------------------------------------------------------------------------------------------------
| PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS |
+-----------------------------------------------------------------------------------------------------------------------------------
| CLIENT 36-CHUNK 237878 ROWS 6787437019 BYTES PARALLEL 36-WAY FULL SCAN
| OVER exDocStoreb | 237878 | 6787437019 | 1510353318102|
| PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE) | 237878 | 6787437019 | 1510353318102|
| CLIENT 36-CHUNK PARALLEL 36-WAY RANGE SCAN OVER indx_exdocb
| [0,' 42ecf4abd4bd7e7606025dc8eee3de 6a3cc04418cbc2619ddc01f54d88d7 c3bf']
| - [0,' 42ecf4abd4bd7e7606025dc8eee3de 6a3cc04418cbc2619ddc01f54d88d7 c3bg' | 237878 | 6787437019 | 1510353318102|
| SERVER FILTER BY FIRST KEY ONLY | 237878 | 6787437019 | 1510353318102|
| SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY ["ID"] | 237878 | 6787437019 | 1510353318102|
| CLIENT MERGE SORT | 237878 | 6787437019 | 1510353318102|
| DYNAMIC SERVER FILTER BY (A.CURRENT_TIMESTAMP, [A.ID](http://a.id/))
IN ((TMP.MCT, TMP.TID)) | 237878 | 6787437019 | 1510353318102|
+-----------------------------------------------------------------------------------------------------------------------------------
```
## JDBC Explain Plan API and Estimates
The information displayed in the explain plan API can also be accessed programmatically through the standard JDBC interfaces. When statistics collection
is enabled for a table, the explain plan also gives an estimate of number of rows and bytes a query is going to scan. To get hold of the info, you can
use corresponding columns in the result set returned by the explain plan statement. When stats collection is not enabled or if for some reason
Phoenix cannot provide the estimate information, the columns return null. Below is an example:
```java
String explainSql = "EXPLAIN SELECT * FROM T";
Long estimatedBytes = null;
Long estimatedRows = null;
Long estimateInfoTs = null;
try (Statement statement = conn.createStatement(explainSql)) {
int paramIdx = 1;
ResultSet rs = statement.executeQuery(explainSql);
rs.next();
estimatedBytes =
(Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_BYTES_READ_COLUMN);
estimatedRows =
(Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN);
estimateInfoTs =
(Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATE_INFO_TS_COLUMN);
}
```
# FAQ (/docs/faq)
## Questions answered in this page:
* [I want to get started. Is there a Phoenix Hello World?](#i-want-to-get-started-is-there-a-phoenix-hello-world)
* [What is the Phoenix JDBC URL syntax?](#what-is-the-phoenix-jdbc-url-syntax)
* [Is there a way to bulk load in Phoenix?](#is-there-a-way-to-bulk-load-in-phoenix)
* [How I map Phoenix table to an existing HBase table?](#how-i-map-phoenix-table-to-an-existing-hbase-table)
* [Are there any tips for optimizing Phoenix?](#are-there-any-tips-for-optimizing-phoenix)
* [How do I create Secondary Index on a table?](#how-do-i-create-secondary-index-on-a-table)
* [Why isn't my secondary index being used?](#why-isnt-my-secondary-index-being-used)
* [How fast is Phoenix? Why is it so fast?](#how-fast-is-phoenix-why-is-it-so-fast)
* [How do I connect to secure HBase cluster?](#how-do-i-connect-to-secure-hbase-cluster)
* [What HBase and Hadoop versions are supported?](#what-hbase-and-hadoop-versions-are-supported)
* [Can phoenix work on tables with arbitrary timestamp as flexible as HBase API?](#can-phoenix-work-on-tables-with-arbitrary-timestamp-as-flexible-as-hbase-api)
* [Why isn't my query doing a RANGE SCAN?](#why-isnt-my-query-doing-a-range-scan)
* [Should I pool Phoenix JDBC Connections?](#should-i-pool-phoenix-jdbc-connections)
* [Why does Phoenix add an empty or dummy KeyValue when doing an upsert?](#why-does-phoenix-add-an-emptydummy-keyvalue-when-doing-an-upsert)
### I want to get started. Is there a Phoenix *Hello World*?
*Pre-requisite:* [Download](/downloads) and [install](/docs/installation) the latest Phoenix.
#### Using console
Start Sqlline:
```shell
$ sqlline.py [zookeeper quorum hosts]
```
Execute the following statements when Sqlline connects:
```sql
create table test (mykey integer not null primary key, mycolumn varchar);
upsert into test values (1,'Hello');
upsert into test values (2,'World!');
select * from test;
```
You should get the following output:
```text
+-------+------------+
| MYKEY | MYCOLUMN |
+-------+------------+
| 1 | Hello |
| 2 | World! |
+-------+------------+
```
#### Using Java
Create test.java file with the following content:
```java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.sql.Statement;
public class test {
public static void main(String[] args) throws SQLException {
Statement stmt = null;
ResultSet rset = null;
Connection con = DriverManager.getConnection("jdbc:phoenix:[zookeeper quorum hosts]");
stmt = con.createStatement();
stmt.executeUpdate("create table test (mykey integer not null primary key, mycolumn varchar)");
stmt.executeUpdate("upsert into test values (1,'Hello')");
stmt.executeUpdate("upsert into test values (2,'World!')");
con.commit();
PreparedStatement statement = con.prepareStatement("select * from test");
rset = statement.executeQuery();
while (rset.next()) {
System.out.println(rset.getString("mycolumn"));
}
statement.close();
con.close();
}
}
```
Compile and execute on command line
```shell
$ javac test.java
$ java -cp "../phoenix-[version]-client.jar:." test
```
You should get the following output
```text
Hello
World!
```
### What is the Phoenix JDBC URL syntax?
#### Thick Driver
**See [Using the Phoenix JDBC Driver](/docs/fundamentals/client-classpath-and-jdbc-url#using-the-phoenix-jdbc-driver) for a more up-to-date description**
The Phoenix (Thick) Driver JDBC URL syntax is as follows (where elements in square brackets are optional):
```text
jdbc:phoenix:[comma-separated ZooKeeper Quorum Hosts [: ZK port [:hbase root znode [:kerberos_principal [:path to kerberos keytab] ] ] ]
```
The simplest URL is:
```text
jdbc:phoenix
```
Whereas the most complicated URL is:
```text
jdbc:phoenix:zookeeper1.domain,zookeeper2.domain,zookeeper3.domain:2181:/hbase-1:phoenix@EXAMPLE.COM:/etc/security/keytabs/phoenix.keytab
```
Please note that each optional element in the URL requires all previous optional elements. For example, to specify the
HBase root ZNode, the ZooKeeper port *must* also be specified.
See also [Connection String](/docs#connection).
#### Thin Driver
The Phoenix Thin Driver (used with the Phoenix Query Server) JDBC URL syntax is as follows:
```text
jdbc:phoenix:thin:[key=value[;key=value...]]
```
There are a number of keys exposed for client-use. The most commonly-used keys are: `url` and `serialization`. The `url`
key is required to interact with the Phoenix Query Server.
The simplest URL is:
```text
jdbc:phoenix:thin:url=http://localhost:8765
```
Where as very complicated URL is:
```text
jdbc:phoenix:thin:url=http://queryserver.domain:8765;serialization=PROTOBUF;authentication=SPENGO;principal=phoenix@EXAMPLE.COM;keytab=/etc/security/keytabs/phoenix.keytab
```
Please refer to the [Apache Avatica documentation](https://calcite.apache.org/avatica/docs/client_reference.html) for a full list of supported options in the Thin client JDBC URL,
or see the [Query Server documentation](/docs/features/query-server)
### Is there a way to bulk load in Phoenix?
#### Map Reduce
See the example [here](/docs/features/bulk-loading)
#### CSV
CSV data can be bulk loaded with built in utility named `psql`. Typical upsert rates are 20K - 50K rows per second (depends on how wide are the rows).
Usage example:
* Create table using psql:
```shell
$ psql.py [zookeeper] ../examples/web_stat.sql
```
* Upsert CSV bulk data:
```shell
$ psql.py [zookeeper] ../examples/web_stat.csv
```
### How I map Phoenix table to an existing HBase table?
You can create both a Phoenix table or view through the `CREATE TABLE`/`CREATE VIEW` DDL statement on a pre-existing HBase table. In both cases, we'll leave the HBase metadata as-is. For `CREATE TABLE`, we'll create any metadata (table, column families) that doesn't already exist. We'll also add an empty key value for each row so that queries behave as expected (without requiring all columns to be projected during scans).
The other caveat is that the way the bytes were serialized must match the way the bytes are serialized by Phoenix. For `VARCHAR`, `CHAR`, and `UNSIGNED_*` types, we use the HBase `Bytes` methods. The `CHAR` type expects only single-byte characters and the `UNSIGNED` types expect values greater than or equal to zero. For signed types (`TINYINT`, `SMALLINT`, `INTEGER` and `BIGINT`), Phoenix will flip the first bit so that negative values will sort before positive values. Because HBase sorts row keys in lexicographical order and negative value's first bit is 1 while positive 0 so that negative value is 'greater than' positive value if we don't flip the first bit. So if you stored integers by HBase native API and want to access them by Phoenix, make sure that all your data types are `UNSIGNED` types.
Our composite row keys are formed by simply concatenating the values together, with a zero byte character used as a separator after a variable length type.
If you create an HBase table like this:
```shell
create 't1', {NAME => 'f1', VERSIONS => 5}
```
then you have an HBase table with a name of `t1` and a column family with a name of `f1`. Remember, in HBase, you don't model the possible `KeyValue`s or the structure of the row key. This is the information you specify in Phoenix above and beyond the table and column family.
So in Phoenix, you'd create a view like this:
```sql
CREATE VIEW "t1" ( pk VARCHAR PRIMARY KEY, "f1".val VARCHAR )
```
The `pk` column declares that your row key is a `VARCHAR` (i.e. a string) while the `"f1".val` column declares that your HBase table will contain `KeyValue`s with a column family and column qualifier of `"f1":VAL` and that their value will be a `VARCHAR`.
Note that you don't need the double quotes if you create your HBase table with all caps names (since this is how Phoenix normalizes strings, by upper casing them). For example, with:
```shell
create 'T1', {NAME => 'F1', VERSIONS => 5}
```
you could create this Phoenix view:
```sql
CREATE VIEW t1 ( pk VARCHAR PRIMARY KEY, f1.val VARCHAR )
```
Or if you're creating new HBase tables, just let Phoenix do everything for you like this (No need to use the HBase shell at all.):
```sql
CREATE TABLE t1 ( pk VARCHAR PRIMARY KEY, val VARCHAR )
```
### Are there any tips for optimizing Phoenix?
* Use **Salting** to increase read/write performance\
Salting can significantly increase read/write performance by pre-splitting the data into multiple regions. Although Salting will yield better performance in most scenarios.\
Example:
```sql
CREATE TABLE TEST (HOST VARCHAR NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR) SALT_BUCKETS=16
```
*Note: Ideally for a 16 region server cluster with quad-core CPUs, choose salt buckets between 32-64 for optimal performance.*
* **Pre-split** table\
Salting does automatic table splitting but in case you want to exactly control where table split occurs with out adding extra byte or change row key order then you can pre-split a table.\
Example:
```sql
CREATE TABLE TEST (HOST VARCHAR NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR) SPLIT ON ('CS','EU','NA')
```
* Use **multiple column families**\
Column family contains related data in separate files. If you query use selected columns then it make sense to group those columns together in a column family to improve read performance.\
Example:\
Following create table DDL will create two column faimiles A and B.
```sql
CREATE TABLE TEST (MYKEY VARCHAR NOT NULL PRIMARY KEY, A.COL1 VARCHAR, A.COL2 VARCHAR, B.COL3 VARCHAR)
```
* Use **compression**\
On disk compression improves performance on large tables\
Example:
```sql
CREATE TABLE TEST (HOST VARCHAR NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR) COMPRESSION='GZ'
```
* Create **indexes**
See [How do I connect to secure HBase cluster?](#how-do-i-connect-to-secure-hbase-cluster)
* **Optimize cluster** parameters
See [https://hbase.apache.org/docs/performance](https://hbase.apache.org/docs/performance)
* **Optimize Phoenix** parameters
See [Configuration](/docs/fundamentals/configuration)
### How do I create Secondary Index on a table?
Starting with Phoenix version 2.1, Phoenix supports index over mutable and immutable data. Note that Phoenix 2.0.x only supports Index over immutable data. Index write performance index with immutable table is slightly faster than mutable table however data in immutable table cannot be updated.
Example:
* **Create table**\
Immutable table: `create table test (mykey varchar primary key, col1 varchar, col2 varchar) IMMUTABLE_ROWS=true;`\
Mutable table: `create table test (mykey varchar primary key, col1 varchar, col2 varchar);`
* **Creating index on col2**\
`create index idx on test (col2)`
* **Creating index on col1 and a covered index on col2**\
`create index idx on test (col1) include (col2)`\
Upsert rows in this test table and Phoenix query optimizer will choose correct index to use. You can see in [explain plan](/docs/grammar#explain) if Phoenix is using the index table. You can also give a [hint](/docs/grammar#hint) in Phoenix query to use a specific index.
See [Secondary Indexing](/docs/features/secondary-indexes) for further information
### Why isn't my secondary index being used?
The secondary index won't be used unless all columns used in the query are in it ( as indexed or covered columns). All columns making up the primary key of the data table will automatically be included in the index.
Example: DDL `create table usertable (id varchar primary key, firstname varchar, lastname varchar); create index idx_name on usertable (firstname);`
Query: DDL `select id, firstname, lastname from usertable where firstname = 'foo';`
Index would not be used in this case as `lastname` is not part of indexed or covered column. This can be verified by looking at the explain plan. To fix this create index that has either `lastname` part of index or covered column. Example: `create idx_name on usertable (firstname) include (lastname);`
You can force Phoenix to use secondary for uncovered columns by specifying an [index hint](/docs/features/secondary-indexes)
### How fast is Phoenix? Why is it so fast?
Phoenix is fast. Full table scan of 100M rows usually completes in 20 seconds (narrow table on a medium sized cluster). This time come down to few milliseconds if query contains filter on key columns. For filters on non-key columns or non-leading key columns, you can add index on these columns which leads to performance equivalent to filtering on key column by making copy of table with indexed column(s) part of key.
Why is Phoenix fast even when doing full scan:
1. Phoenix chunks up your query using the region boundaries and runs them in parallel on the client using a configurable number of threads.
2. The aggregation will be done in a coprocessor on the server-side, collapsing the amount of data that gets returned back to the client rather than returning it all.
### How do I connect to secure HBase cluster?
Specify the principal and corresponding keytab in the JDBC URL as show above.
For ancient Phoenix versions heck out the excellent [post](http://bigdatanoob.blogspot.com/2013/09/connect-phoenix-to-secure-hbase-cluster.html) by Anil Gupta
### What HBase and Hadoop versions are supported?
Phoenix 4.x supports HBase 1.x running on Hadoop 2
Phoenix 5.x supports HBase 2.x running on Hadoop 3
See the release notes and [BUILDING](/docs/fundamentals/building) in recent releases for the exact versions supported,
and on how to build Phoenix for specific HBase and Hadoop versions
### Can phoenix work on tables with arbitrary timestamp as flexible as HBase API?
By default, Phoenix lets HBase manage the timestamps and just shows you the latest values for everything. However, Phoenix also allows arbitrary timestamps to be supplied by the user. To do that you'd specify a `CurrentSCN` at connection time, like this:
```java
Properties props = new Properties();
props.setProperty("CurrentSCN", Long.toString(ts));
Connection conn = DriverManager.connect(myUrl, props);
conn.createStatement().execute("UPSERT INTO myTable VALUES ('a')");
conn.commit();
```
The above is equivalent to doing this with the HBase API:
```java
myTable.put(Bytes.toBytes('a'), ts);
```
By specifying a `CurrentSCN`, you're telling Phoenix that you want everything for that connection to be done at that timestamp. Note that this applies to queries done on the connection as well - for example, a query over `myTable` above would not see the data it just upserted, since it only sees data that was created before its `CurrentSCN` property. This provides a way of doing snapshot, flashback, or point-in-time queries.
Keep in mind that creating a new connection is *not* an expensive operation. The same underlying `HConnection` is used for all connections to the same cluster, so it's more or less like instantiating a few objects.
### Why isn't my query doing a RANGE SCAN?
```sql
CREATE TABLE TEST (
pk1 char(1) not null,
pk2 char(1) not null,
pk3 char(1) not null,
non-pk varchar,
CONSTRAINT PK PRIMARY KEY(pk1, pk2, pk3)
);
```
RANGE SCAN means that only a subset of the rows in your table will be scanned over. This occurs if you use one or more leading columns from your primary key constraint. Query that is not filtering on leading PK columns ex. `select * from test where pk2='x' and pk3='y';` will result in full scan whereas the following query will result in range scan `select * from test where pk1='x' and pk2='y';`. Note that you can add a secondary index on your `pk2` and `pk3` columns and that would cause a range scan to be done for the first query (over the index table).
DEGENERATE SCAN means that a query can't possibly return any rows. If we can determine that at compile time, then we don't bother to even run the scan.
FULL SCAN means that all rows of the table will be scanned over (potentially with a filter applied if you have a WHERE clause)
SKIP SCAN means that either a subset or all rows in your table will be scanned over, however it will skip large groups of rows depending on the conditions in your filter. See [this](http://phoenix-hbase.blogspot.com/2013/05/demystifying-skip-scan-in-phoenix.html) blog for more detail. We don't do a SKIP SCAN if you have no filter on the leading primary key columns, but you can force a SKIP SCAN by using the `/*+ SKIP_SCAN */` hint. Under some conditions, namely when the cardinality of your leading primary key columns is low, it will be more efficient than a FULL SCAN.
### Should I pool Phoenix JDBC Connections?
No, it is not necessary to pool Phoenix JDBC Connections.
Phoenix's Connection objects are different from most other JDBC Connections due to the underlying HBase connection. The Phoenix Connection object is designed to be a thin object that is inexpensive to create. If Phoenix Connections are reused, it is possible that the underlying HBase connection is not always left in a healthy state by the previous user. It is better to create new Phoenix Connections to ensure that you avoid any potential issues.
Implementing pooling for Phoenix could be done simply by creating a delegate Connection that instantiates a new Phoenix connection when retrieved from the pool and then closes the connection when returning it to the pool (see [PHOENIX-2388](https://issues.apache.org/jira/browse/PHOENIX-2388)).
### Why does Phoenix add an empty/dummy KeyValue when doing an upsert?
The empty or dummy `KeyValue` (with a column qualifier of `_0`) is needed to ensure that a given column is available
for all rows.
As you may know, data is stored in HBase as `KeyValue`s, meaning that
the full row key is stored for each column value. This also implies
that the row key is not stored at all unless there is at least one
column stored.
Now consider JDBC row which has an integer primary key, and several
columns which are all null. In order to be able to store the primary
key, a KeyValue needs to be stored to show that the row is present at
all. This column is represented by the empty column that you've
noticed. This allows doing a `SELECT * FROM TABLE` and receiving
records for all rows, even those whose non-pk columns are null.
The same issue comes up even if only one column is null for some (or
all) records. A scan over Phoenix will include the empty column to
ensure that rows that only consist of the primary key (and have null
for all non-key columns) will be included in a scan result.
# Atomic Upsert (/docs/features/atomic-upsert)
To support atomic upsert, an optional `ON DUPLICATE KEY` clause, similar to the MySQL syntax, has been
incorporated into the `UPSERT VALUES` command as of Phoenix 4.9. The general syntax is described
[here](/docs/grammar#upsert-values). This feature provides a superset of the HBase `Increment` and
`CheckAndPut` functionality to enable atomic upserts. On the server-side, when the commit
is processed, the row being updated will be locked while the current column values are read and the
`ON DUPLICATE KEY` clause is executed. Given that the row must be locked and read when the `ON DUPLICATE KEY`
clause is used, there will be a performance penalty (much like there is for an HBase `Put` versus a `CheckAndPut`).
In the presence of the `ON DUPLICATE KEY` clause, if the row already exists, the `VALUES` specified will
be ignored and instead either:
* the row will not be updated if `ON DUPLICATE KEY IGNORE` is specified or
* the row will be updated (under lock) by executing the expressions following the `ON DUPLICATE KEY UPDATE`
clause.
Multiple `UPSERT` statements for the same row in the same commit batch will be processed in the order of their
execution. Thus the same result will be produced when auto commit is on or off.
## Examples
For example, to atomically increment two counter columns, you would execute the following command:
```sql
UPSERT INTO my_table(id, counter1, counter2) VALUES ('abc', 0, 0)
ON DUPLICATE KEY UPDATE counter1 = counter1 + 1, counter2 = counter2 + 1;
```
To only update a column if it doesn't yet exist:
```sql
UPSERT INTO my_table(id, my_col) VALUES ('abc', 100)
ON DUPLICATE KEY IGNORE;
```
Note that arbitrarily complex expressions may be used in this new clause:
```sql
UPSERT INTO my_table(id, total_deal_size, deal_size) VALUES ('abc', 0, 100)
ON DUPLICATE KEY UPDATE
total_deal_size = total_deal_size + deal_size,
approval_reqd = CASE WHEN total_deal_size < 100 THEN 'NONE'
WHEN total_deal_size < 1000 THEN 'MANAGER APPROVAL'
ELSE 'VP APPROVAL' END;
```
## Limitations
The following limitations are enforced for the `ON DUPLICATE KEY` clause usage:
* Primary key columns may not be updated, since this would essentially be creating a *new* row.
* Transactional tables may not use this clause as atomic upserts are already possible through
exception handling when a conflict occurs.
* Immutable tables may not use this clause as by definition there should be no updates to
existing rows.
* The `CURRENT_SCN` property may not be set on connection when this clause is used as HBase
does not handle atomicity unless the latest value is being updated.
* The same column should not be updated more than once in the same statement.
* No aggregation or references to sequences are allowed within the clause.
* Global indexes on columns being atomically updated are not supported, as potentially a separate RPC across the wire would be made while the row is under lock to maintain the secondary index.
# Bulk Loading (/docs/features/bulk-loading)
Phoenix provides two methods for bulk loading data into Phoenix tables:
* Single-threaded client loading tool for CSV-formatted data via the [psql](/docs/installation#loading-data) command
* MapReduce-based bulk load tool for CSV and JSON-formatted data
The `psql` tool is typically appropriate for tens of megabytes, while the MapReduce-based loader is typically better for larger load volumes.
## Sample data
For the following examples, assume we have a CSV file named `data.csv` with this content:
```csv
12345,John,Doe
67890,Mary,Poppins
```
We will use a table with the following structure:
```sql
CREATE TABLE example (
my_pk BIGINT NOT NULL,
m.first_name VARCHAR(50),
m.last_name VARCHAR(50),
CONSTRAINT pk PRIMARY KEY (my_pk)
);
```
## Loading via PSQL
The `psql` command is invoked via `psql.py` in the Phoenix `bin` directory. To load CSV data, provide connection information for your HBase cluster, the target table name, and one or more CSV file paths. All CSV files must use the `.csv` extension (because arbitrary SQL scripts with the `.sql` extension can also be supplied on the `psql` command line).
To load the example data above into HBase running locally:
```shell
bin/psql.py -t EXAMPLE localhost data.csv
```
The following parameters can be used for loading data with `psql`:
| Parameter | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `-t` | Provide the target table name. By default, the table name is taken from the CSV file name. This parameter is case-sensitive. |
| `-h` | Override the column names to which CSV data maps (case-sensitive). A special value of `in-line` indicates that the first line of the CSV file determines column mapping. |
| `-s` | Run in strict mode, throwing an error on CSV parsing errors. |
| `-d` | Supply one or more custom delimiters for CSV parsing. |
| `-q` | Supply a custom phrase delimiter (defaults to the double quote character). |
| `-e` | Supply a custom escape character (default is backslash). |
| `-a` | Supply an array delimiter (explained in more detail below). |
## Loading via MapReduce
For higher-throughput loading distributed across the cluster, the MapReduce loader can be used. This loader first converts data into HFiles, then provides the created HFiles to HBase after HFile creation completes.
The CSV MapReduce loader is launched using the `hadoop` command with the Phoenix client JAR:
```shell
hadoop jar phoenix--client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool --table EXAMPLE --input /data/example.csv
```
When using Phoenix 4.0 and above, there is a known HBase issue ("Notice to MapReduce users of HBase 0.96.1 and above" in the [HBase Reference Guide](https://hbase.apache.org/docs)). You should use:
```shell
HADOOP_CLASSPATH=$(hbase mapredcp):/path/to/hbase/conf hadoop jar phoenix--client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool --table EXAMPLE --input /data/example.csv
```
Or:
```shell
HADOOP_CLASSPATH=/path/to/hbase-protocol.jar:/path/to/hbase/conf hadoop jar phoenix--client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool --table EXAMPLE --input /data/example.csv
```
The JSON MapReduce loader is launched similarly:
```shell
hadoop jar phoenix--client.jar org.apache.phoenix.mapreduce.JsonBulkLoadTool --table EXAMPLE --input /data/example.json
```
The input file must be present on HDFS (not the local filesystem where the command is run).
The following parameters can be used with the MapReduce loader:
| Parameter | Description |
| ---------------------- | ------------------------------------------- |
| `-i,--input` | Input CSV path (mandatory) |
| `-t,--table` | Phoenix table name (mandatory) |
| `-a,--array-delimiter` | Array element delimiter (optional) |
| `-c,--import-columns` | Comma-separated list of columns to import |
| `-d,--delimiter` | Input delimiter (defaults to comma) |
| `-g,--ignore-errors` | Ignore input errors |
| `-o,--output` | Output path for temporary HFiles (optional) |
| `-s,--schema` | Phoenix schema name (optional) |
| `-z,--zookeeper` | ZooKeeper quorum to connect to (optional) |
| `-it,--index-table` | Index table name to load (optional) |
### Notes on the MapReduce importer
The current MR-based bulk loader runs one MR job to load your data table and one MR job per index table to populate indexes. Use `-it` to load only one index table.
#### Permission issues when uploading HFiles
There can be issues due to file permissions on created HFiles in the final stage of a bulk load, when HFiles are handed over to HBase. HBase must be able to move the created HFiles, which means it needs write access to the directories where files were written. If not, HFile upload may hang for a long time before failing.
Two common workarounds are:
* Run the bulk load process as the `hbase` user.
* Create output files readable/writable for all users.
The first option can be done by running:
```shell
sudo -u hbase hadoop jar phoenix--client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool --table EXAMPLE --input /data/example.csv
```
Creating output files readable by all can be done by setting `fs.permissions.umask-mode` to `000`. This can be set in Hadoop config on the submit host, or only for job submission:
```shell
hadoop jar phoenix--client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool -Dfs.permissions.umask-mode=000 --table EXAMPLE --input /data/example.csv
```
#### Loading array data
Both PSQL and MapReduce loaders support array values with `-a`. Arrays in CSV are represented by a field that uses a different delimiter than the main CSV delimiter. For example, this file represents an `id` field and an array of integers:
```csv
1,2:3:4
2,3:4:5
```
To load this file, use the default CSV delimiter (comma) and pass colon as the array delimiter via `-a ':'`.
#### A note on separator characters
The default separator for both loaders is comma (`,`). A common separator for input files is tab, which can be tricky to pass on the command line. A common mistake is:
```shell
-d '\t'
```
This does not work because the shell passes two characters (backslash and `t`) to Phoenix.
Two working approaches:
1. Prefix the string representation of tab with `$`:
```shell
-d $'\\t'
```
2. Enter the separator as `Ctrl+v` and then press tab:
```text
-d '^v'
```
#### A note on lowercase table/schema names
Table names in Phoenix are case-insensitive (generally uppercase), but users may need to map an existing lowercase HBase table name into Phoenix. In this case, double quotes around the table name (for example, `"tablename"`) preserve case sensitivity.
This support was extended to bulk load options. However, due to how Apache Commons CLI parses command-line options (CLI-275), pass the argument as `\"\"tablename\"\"` instead of just `"tablename"` for `CsvBulkLoadTool`.
Example:
```shell
hadoop jar phoenix--client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool --table \"\"t\"\" --input /data/example.csv
```
# Cursor (/docs/features/cursor)
To work on a subset of rows from a query, Phoenix supports a `CURSOR` control structure. The sequence below shows how to use a cursor.
## Using a cursor
1. Define a cursor for a query using the `DECLARE` statement.
```java
PreparedStatement statement = conn.prepareStatement(
"DECLARE empCursor CURSOR FOR SELECT * FROM EMP_TABLE"
);
statement.execute();
```
2. Open the cursor.
```java
statement = conn.prepareStatement("OPEN empCursor");
statement.execute();
```
3. Fetch a subset of rows to work with.
```java
statement = conn.prepareStatement("FETCH NEXT 10 ROWS FROM empCursor");
ResultSet rset = statement.executeQuery();
```
4. Iterate through the fetched rows and process them as required.
```java
while (rset.next()) {
// ...
}
```
5. Fetch additional sets of rows as needed, and close the cursor when done.
```java
statement = conn.prepareStatement("CLOSE empCursor");
statement.execute();
```
# Dynamic Columns (/docs/features/dynamic-columns)
Sometimes defining a static schema up front is not feasible. Instead, a subset of columns may be specified at table [create](/docs/grammar#create-table) time while the rest are specified at [query](/docs/grammar#select) time. As of Phoenix 1.2, dynamic columns are supported by allowing column definitions in parentheses after the table name in the `FROM` clause of a `SELECT` statement. Although this is not standard SQL, it is useful for leveraging the late-binding capability of HBase.
For example:
```sql
SELECT eventTime, lastGCTime, usedMemory, maxMemory
FROM EventLog(lastGCTime TIME, usedMemory BIGINT, maxMemory BIGINT)
WHERE eventType = 'OOM' AND lastGCTime < eventTime - 1;
```
You might define only a subset of event columns at create time, because each event type can have different properties:
```sql
CREATE TABLE EventLog (
eventId BIGINT NOT NULL,
eventTime TIME NOT NULL,
eventType CHAR(3),
CONSTRAINT pk PRIMARY KEY (eventId, eventTime)
);
```
To upsert a row with dynamic columns:
```sql
UPSERT INTO EventLog (
eventId,
eventTime,
eventType,
lastGCTime TIME,
usedMemory BIGINT,
maxMemory BIGINT
)
VALUES (1, CURRENT_TIME(), 'abc', CURRENT_TIME(), 512, 1024);
```
# Metrics (/docs/features/metrics)
Phoenix surfaces various metrics that provide an insight into what is going on within the Phoenix client as it is executing various SQL statements. These metrics are collected within the client JVM in two ways:
* **Request level metrics** - collected at an individual SQL statement level
* **Global metrics** - collected at the client JVM level
Request level metrics are helpful for figuring out at a more granular level about the amount of work done by every SQL statement executed by Phoenix. These metrics can be classified into three categories:
## Request-level metrics
### Mutation metrics
* `MUTATION_BATCH_SIZE` - Batch sizes of mutations
* `MUTATION_BYTES` - Size of mutations in bytes
* `MUTATION_COMMIT_TIME` - Time it took to commit mutations
### Scan task metrics
* `NUM_PARALLEL_SCANS` - Number of scans executed in parallel
* `SCAN_BYTES` - Number of bytes read by scans
* `MEMORY_CHUNK_BYTES` - Number of bytes allocated by the memory manager
* `MEMORY_WAIT_TIME` - Time in milliseconds threads needed to wait for memory to be allocated through memory manager
* `SPOOL_FILE_SIZE` - Size of spool files created in bytes
* `SPOOL_FILE_COUNTER` - Number of spool files created
* `CACHE_REFRESH_SPLITS_COUNTER` - Number of times Phoenix's metadata cache was refreshed because of splits
* `TASK_QUEUE_WAIT_TIME` - Time in milliseconds tasks had to wait in the queue of the thread pool executor
* `TASK_END_TO_END_TIME` - Time in milliseconds spent by tasks from creation to completion
* `TASK_EXECUTION_TIME` - Time in milliseconds tasks took to execute
* `TASK_EXECUTED_COUNTER` - Counter for number of tasks submitted to the thread pool executor
* `TASK_REJECTED_COUNTER` - Counter for number of tasks that were rejected by the thread pool executor
### Overall query metrics
* `QUERY_TIMEOUT_COUNTER` - Number of times query timed out
* `QUERY_FAILED_COUNTER` - Number of times query failed
* `WALL_CLOCK_TIME_MS` - Wall clock time elapsed for the overall query execution
* `RESULT_SET_TIME_MS` - Wall clock time elapsed for reading all records using `resultSet.next()`
## How to use SQL statement-level metrics
* Log and report query execution details which could be later used for analysis.
* Report top SQL queries by duration. Metric to use: `WALL_CLOCK_TIME_MS`.
* Check if the query is failing because it is timing out. Metric to use: `QUERY_TIMEOUT_COUNTER > 0`.
* Monitor the amount of bytes being written to or read from HBase for a SQL statement. Metrics to use: `MUTATION_BYTES` and `SCAN_BYTES`.
* Check if the query is doing too much work or needs tuning. Possible metrics to use: `TASK_EXECUTED_COUNTER`, `TASK_QUEUE_WAIT_TIME`, `WALL_CLOCK_TIME_MS`.
* Check if a successful query is facing thread starvation, i.e., number of threads in the thread pool likely needs to be increased. This is characterized by a relatively large difference between `TASK_EXECUTION_TIME` and `TASK_END_TO_END_TIME`.
Request level metrics can be turned on/off for every Phoenix JDBC connection. Below is an example of how you can do that:
```java
Properties props = new Properties();
props.setProperty(QueryServices.COLLECT_REQUEST_LEVEL_METRICS, "true");
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
// ...
}
```
A typical pattern for how one could get hold of read metrics for queries:
```java
Map> overAllQueryMetrics = null;
Map> requestReadMetrics = null;
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
// ...
}
overAllQueryMetrics = PhoenixRuntime.getOverAllReadRequestMetrics(rs);
requestReadMetrics = PhoenixRuntime.getRequestReadMetrics(rs);
// log or report metrics as needed
PhoenixRuntime.resetMetrics(rs);
}
```
One could also get hold of write related metrics (collected per table) for DML statements by doing something like this:
```java
Map> mutationWriteMetrics = null;
Map> mutationReadMetrics = null;
try (Connection conn = DriverManager.getConnection(url)) {
conn.createStatement().executeUpdate(dml1);
// ...
conn.createStatement().executeUpdate(dml2);
// ...
conn.createStatement().executeUpdate(dml3);
// ...
conn.commit();
mutationWriteMetrics = PhoenixRuntime.getWriteMetricsForMutationsSinceLastReset(conn);
mutationReadMetrics = PhoenixRuntime.getReadMetricsForMutationsSinceLastReset(conn);
PhoenixRuntime.resetMetrics(conn);
}
```
Global metrics on the other hand are collected at the Phoenix client’s JVM level. These metrics could be used for building out a trend and seeing what is going on within Phoenix from client’s perspective over time. Other than the metrics reported above for request level metrics, the global metrics also includes the following counters:
* `MUTATION_SQL_COUNTER` - Counter for number of mutation SQL statements
* `SELECT_SQL_COUNTER` - Counter for number of SQL queries
* `OPEN_PHOENIX_CONNECTIONS_COUNTER` - Number of open Phoenix connections
Global metrics could be helpful in monitoring and tuning various aspects of the execution environment. For example: an increase in `TASK_REJECTED_COUNTER` is probably a symptom of too much work being submitted, or that the Phoenix thread pool queue depth or number of threads (or both) needs to be increased. Similarly, a spike in `TASK_EXECUTION_TIME` for a time frame could be symptomatic of several things including overloaded region servers, a network glitch, or client/region servers undergoing garbage collection.
Collection of global client metrics can be turned on/off (on by default) by setting the attribute phoenix.query.global.metrics.enabled to true/false in the client side hbase-site.xml.
Below is a code snippet showing how to log/report global metrics by using a scheduled job that runs periodically:
```java
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.submit(new Runnable() {
@Override
public void run() {
Collection metrics = PhoenixRuntime.getGlobalPhoenixClientMetrics();
for (GlobalMetric m : metrics) {
// log or report for trending purposes
}
}
});
```
# Multi-tenancy (/docs/features/multi-tenancy)
## Highlights
* Multi-tenancy in Phoenix works via a combination of multi-tenant tables and tenant-specific connections (detailed below).
* Tenants open tenant-specific connections to Phoenix. These connections can only access data that belongs to the tenant.
* Tenants only see their own data in multi-tenant tables and can see all data in regular tables.
* In order to add their own columns, tenants create tenant-specific views on top of multi-tenant tables and add their own columns to the views.
## Multi-tenant tables
Multi-tenant tables in Phoenix are regular tables declared with the `MULTI_TENANT=true` DDL property. They work in conjunction with tenant-specific connections (detailed below) to ensure tenants only see their own data in these tables. The first primary key column of a multi-tenant table identifies the tenant. For example:
```sql
CREATE TABLE base.event (
tenant_id VARCHAR,
event_type CHAR(1),
created_date DATE,
event_id BIGINT
)
MULTI_TENANT=true;
```
The column that identifies the tenant may have any name, but it must be of type `VARCHAR` or `CHAR`. Regular Phoenix connections work with these tables without tenant constraints, including access across tenant boundaries.
## Tenant-specific connections
Tenants are identified by the presence or absence of the `TenantId` property at JDBC connection time. A connection with a non-null `TenantId` is tenant-specific. A connection with an unspecified or null `TenantId` is a regular connection. A tenant-specific connection may query only:
* **all data in non-multi-tenant (global) tables**, that is, tables created with a regular connection without `MULTI_TENANT=true`.
* **their own data in multi-tenant tables**.
* **their own schema**, meaning it sees only tenant-specific views created by that tenant (detailed below).
For example, a tenant-specific connection is established like this:
```java
Properties props = new Properties();
props.setProperty("TenantId", "Acme");
Connection conn = DriverManager.getConnection("localhost", props);
```
## Tenant-specific views (optional)
Tenant-specific views may only be created using tenant-specific connections. They are created the same way as views, however the base table must be a multi-tenant table or another view that eventually points to one. Tenant-specific views are typically used when new columns and/or filter criteria, specific to that tenant, are required. Otherwise the base table may be used directly through a tenant-specific connection as described above.
For example, a tenant-specific view may be defined as follows:
```sql
CREATE VIEW acme.login_event(acme_user_id CHAR(15)) AS
SELECT * FROM base.event
WHERE event_type = 'L';
```
The `tenant_id` column is neither visible nor accessible from a tenant-specific view. Any reference to it causes a `ColumnNotFoundException`. As with any Phoenix view, whether the view is updatable follows the rules described [here](/docs/features/views#updatable-views). In addition, indexes may be added to tenant-specific views just like regular tables and views (with [these](/docs/features/views#views-limitations) limitations).
## Tenant data isolation
Any DML or query performed on multi-tenant tables using a tenant-specific connection is automatically constrained to that tenant’s data. For `UPSERT`, Phoenix automatically populates the `tenant_id` column with the tenant ID specified at connection time. For query and `DELETE` operations, a `WHERE` clause is transparently added so operations only see data for the current tenant.
# Namespace Mapping (/docs/features/namespace-mapping)
From v4.8.0 onward, users can map Phoenix schemas to HBase namespaces so that any table created with a schema is created in the corresponding HBase namespace.
Earlier, every table (with or without schema) was created in the default namespace.
## Configuration
Parameters to enable namespace mapping:
| Property | Description | Default |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `phoenix.schema.isNamespaceMappingEnabled` | If enabled, tables created with a schema are mapped to the corresponding namespace. This must be set on both client and server. Once enabled, it should not be rolled back. Older clients will not work after this property is enabled. | `false` |
| `phoenix.schema.mapSystemTablesToNamespace` | This takes effect when `phoenix.schema.isNamespaceMappingEnabled` is also set to `true`. If enabled, existing `SYSTEM` tables are automatically migrated to the `SYSTEM` namespace. If disabled, system tables are created only in the default namespace. This must be set on both client and server. | `true` |
## Grammar available
The following DDL statements can be used to interact with schemas:
* [CREATE SCHEMA](/docs/grammar#create-schema)
* [USE SCHEMA](/docs/grammar#use)
* [DROP SCHEMA](/docs/grammar#drop-schema)
## FAQ
* [How to migrate existing tables with schema to namespace?](#how-to-migrate-existing-tables-with-schema-to-namespace)
* [How are system tables migrated?](#how-are-system-tables-migrated)
* [What permissions are required to CREATE and DROP SCHEMA?](#what-permissions-are-required-to-create-and-drop-schema)
* [How are schemas mapped for different table types?](#how-are-schemas-mapped-for-different-table-types)
* [What is a namespace and what are the benefits of mapping tables to namespaces?](#what-is-a-namespace-and-what-are-the-benefits-of-mapping-tables-to-namespaces)
### How to migrate existing tables with schema to namespace
For a Kerberized environment, run with a user that has sufficient permission (`admin`) to create a namespace.
A table is mapped only to a namespace with the same name as the schema (`schema_name`). Currently, migrating an existing table to a different schema or namespace is not supported.
Usage example:
Move `table_name` to the namespace named `schema_name`:
```bash
$ bin/psql.py -m .
```
### How are system tables migrated
`SYSTEM` tables are migrated automatically during the first connection after enabling `phoenix.schema.mapSystemTablesToNamespace` along with `phoenix.schema.isNamespaceMappingEnabled`.
### What permissions are required to CREATE and DROP SCHEMA
Users must have `admin` permission in HBase to execute `CREATE SCHEMA` and `DROP SCHEMA`, since these commands internally create or delete namespaces.
Details for ACL management in HBase can be found [here](https://hbase.apache.org/docs/security/data-access#how-it-works).
### How are schemas mapped for different table types
Schema support in Phoenix is similar to other databases.
The table below describes how physical tables map to Phoenix objects:
| DDL | Table Type | Physical Table | Description |
| ------------------------------------------- | ---------------------------------------------- | -------------- | ---------------------------------------------------------------------------------- |
| `CREATE TABLE S.T (ID INTEGER PRIMARY KEY)` | TABLE | `S:T` | Table `T` is created in namespace `S`. |
| `CREATE INDEX IDX ON S.T(ID)` | INDEX | `S:IDX` | Indexes inherit schema and namespace from the base table. |
| `CREATE VIEW V AS SELECT * FROM S.T` | VIEW with default schema | `S:T` | View does not inherit schema from the parent table and can use the default schema. |
| `CREATE VIEW X.V AS SELECT * FROM S.T` | VIEW with different schema than physical table | `S:T` | View uses the parent physical table and can have a different (or same) schema. |
| `CREATE VIEW S.V AS SELECT * FROM S.T` | VIEW with same schema as physical table | `S:T` | View uses the parent physical table and can have a different (or same) schema. |
| `CREATE INDEX IDX ON S.V(ID)` | VIEW INDEX | `S:_IDX_T` | View indexes inherit schema and map to the corresponding namespace. |
### What is a namespace and what are the benefits of mapping tables to namespaces
A namespace is a logical grouping of tables, analogous to a database in relational database systems. This abstraction lays the groundwork for multi-tenancy-related features:
* Quota Management - Restrict the amount of resources (i.e. regions, tables) a namespace can consume.
* Namespace Security Administration - Provide another level of security administration for tenants.
* Region server groups - A namespace/table can be pinned to a subset of RegionServers, guaranteeing a coarse level of isolation.
Details about namespace management can be read [here](https://hbase.apache.org/docs/datamodel#namespace).
## Resources
* [PHOENIX-1311](https://issues.apache.org/jira/browse/PHOENIX-1311): Implementation details and discussion for the namespace mapping feature.
# Paged Queries (/docs/features/paged-queries)
Phoenix supports standard SQL constructs to enable paged queries:
* Row Value Constructors (RVC)
* `OFFSET` with `LIMIT`
## Row Value Constructors (RVC)
A row value constructor is an ordered sequence of values delimited by parentheses. For example:
```sql
(4, 'foo', 3.5)
('Doe', 'Jane')
(my_col1, my_col2, 'bar')
```
Just like regular values, row value constructors may be used in comparison expressions:
```sql
WHERE (x, y, z) >= ('foo', 'bar')
WHERE (last_name, first_name) = ('Jane', 'Doe')
```
Row value constructors are compared by conceptually concatenating the values together and comparing them against each other, with the leftmost part being most significant. Section 8.2 (comparison predicates) of the SQL-92 standard explains this in detail, but here are a few examples of predicates that would evaluate to true:
```sql
(9, 5, 3) > (8, 8)
('foo', 'bar') < 'g'
(1, 2) = (1, 2)
```
Row value constructors may also be used in an IN list expression to efficiently query for a set of rows given the composite primary key columns. For example, the following would be optimized to be a point get of three rows:
```sql
WHERE (x, y) IN ((1, 2), (3, 4), (5, 6))
```
Another primary use case for row value constructors is to support query-more type functionality by enabling an ordered set of rows to be incrementally stepped through. For example, the following query would step through a set of rows, 20 rows at a time:
```sql
SELECT title, author, isbn, description
FROM library
WHERE published_date > 2010
AND (title, author, isbn) > (?, ?, ?)
ORDER BY title, author, isbn
LIMIT 20
```
Assuming that the client binds the three bind variables to the values of the last row processed, the next invocation would find the next 20 rows that match the query. If the columns you supply in your row value constructor match in order the columns from your primary key (or from a secondary index), then Phoenix will be able to turn the row value constructor expression into the start row of your scan. This enables a very efficient mechanism to locate *at or after* a row.
## OFFSET with LIMIT
Use OFFSET to specify the starting row offset into the result set returned by your query and LIMIT to specify the page size.
For example, if page size is 10, then to select the second page, the following query can be used (rows 11 to 20 are returned):
```sql
SELECT title, author, isbn, description
FROM library
WHERE published_date > 2010
ORDER BY title, author, isbn
LIMIT 10 OFFSET 10
```
`OFFSET` reads and skips rows on either the server or client depending on query type, whereas RVC is more effective for queries on the primary key axis because it can start directly from the provided key.
# Query Server (/docs/features/query-server)
The Phoenix Query Server provides an alternative means for interaction with
Phoenix and HBase.
## Overview
Phoenix 4.4 introduces a stand-alone server that exposes Phoenix to "thin"
clients. It is based on the [Avatica](https://calcite.apache.org/avatica) component of
[Apache Calcite](https://calcite.apache.org). The query server is comprised of a Java server that
manages Phoenix Connections on the clients' behalf.
With the introduction of the
Protobuf transport, Avatica is moving towards backwards compatibility with the
provided thin JDBC driver. There are no such backwards compatibility guarantees
for the JSON API.
To repeat, there is no guarantee of backwards compatibility with the JSON transport;
however, compatibility with the Protobuf transport is stabilizing (although, not
tested thoroughly enough to be stated as "guaranteed").
### Clients
The primary client implementation
is currently a JDBC driver with minimal dependencies. The default and
primary transport mechanism since Phoenix 4.7 is Protobuf, the older JSON mechanism can still
be enabled.
The distribution includes the sqlline-thin.py CLI client that uses the JDBC thin client.
The Phoenix project also maintains the Python driver
[phoenixdb](https://phoenix.apache.org/python.html).
The Avatica [Go client](https://calcite.apache.org/avatica/docs/go_client_reference.html)
can also be used.
Proprietary ODBC drivers are also available for Windows and Linux.
## Installation
In the 4.4-4.14 and 5.0 releases the query server and its JDBC client are part of the standard Phoenix
distribution. They require no additional dependencies or installation.
After the 4.15 and 5.1 release, the query server has been unbundled into the phoenix-queryserver
repository, and its version number has been reset to 6.0.
Download the latest source or binary release from the
[Download page](/downloads),
or check out the development version from
[GitHub](https://github.com/apache/phoenix-queryserver).
Either unpack the binary distribution, or build it from source. See BUILDING.md
in the source distribution on how to build.
## Usage
### Server
The standalone Query Server distribution does not contain the necessary
Phoenix (thick) client library by default.
If using the standalone library you will either need to rebuild it from source to include the
client library (See BUILDING.md), or manually copy the phoenix thick client library
into the installation directory.
The server component is managed through `bin/queryserver.py`. Its usage is as
follows
```shell
bin/queryserver.py [start|stop]
```
When invoked with no arguments, the query server is launched in the foreground,
with logging directed to the console.
The first argument is an optional `start` or `stop` command to the daemon. When
either of these are provided, it will take appropriate action on a daemon
process, if it exists.
Any subsequent arguments are passed to the main class for interpretation.
The server is packaged in a standalone jar,
`phoenix-queryserver-.jar`. This jar, the phoenix-client.jar and `HBASE_CONF_DIR` on the
classpath are all that is required to launch the server.
### Client
Phoenix provides two mechanisms for interacting with the query server. A JDBC
driver is provided in the standalone
`phoenix-queryserver-client-.jar`. The script
`bin/sqlline-thin.py` is available for the command line.
The JDBC connection string is composed as follows:
```
jdbc:phoenix:thin:url=://:[;option=value...]
```
`` specifies the transport protocol (http or https) used when communicating with the
server.
`` is the name of the host offering the service.
`` is the port number on which the host is listening. Default is `8765`,
though this is configurable (see below).
The full list of options that can be provided via the JDBC URL string is [available
in the Avatica documentation](https://calcite.apache.org/avatica/docs/client_reference.html).
The script `bin/sqlline-thin.py` is intended to behave identically to its
sibling script `bin/sqlline.py`. It supports the following usage options.
```shell
bin/sqlline-thin.py [[scheme://]host[:port]] [sql_file]
```
The first optional argument is a connection URL, as described previously. When
not provided, `scheme` defaults to `http`, `host` to `localhost`, and `port` to
`8765`.
```shell
bin/sqlline-thin.py http://localhost:8765
```
The second optional parameter is a sql file from which to read commands.
## Wire API documentation
The API itself is documented in the Apache Calcite project as it is the Avatica
API -- there is no wire API defined in Phoenix itself.
[JSON API](https://calcite.apache.org/avatica/docs/json_reference.html)
[Protocol Buffer API](https://calcite.apache.org/avatica/docs/protobuf_reference.html)
For more information in building clients in other languages that work with
Avatica, please feel free to reach out to the [Apache Calcite dev mailing list](mailto:dev@calcite.apache.org).
## Impersonation
By default, the Phoenix Query Server executes queries on behalf of the end-user. HBase permissions
are enforced given the end-user, not the Phoenix Query Server's identity. In some cases, it may
be desirable to execute the query as some other user -- this is referred to as "impersonation".
This can enable workflows where a trusted user has the privilege to run queries for other users.
This can be enabled by setting the configuration property `phoenix.queryserver.withRemoteUserExtractor`
to `true`. The URL of the Query Server can be modified to include the required request parameter.
For example, to let "bob" to run a query as "alice", the following JDBC URL could be used:
```text
jdbc:phoenix:thin:url=http://localhost:8765?doAs=alice
```
The standard Hadoop "proxyuser" configuration keys are checked to validate if the "real" remote user
is allowed to impersonate the "doAs" user. See the [Hadoop documentation](https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/Superusers.html)
for more information on how to configure these rules.
As a word of warning: there is no end-to-end test coverage for the HBase 0.98 and 1.1 Phoenix releases
because of missing test-related code in those HBase releases. While we expect no issues on these
Phoenix release lines, we recommend additional testing by the user to verify that there are no issues.
## Metrics
By default, the Phoenix Query Server exposes various Phoenix global client metrics via JMX (for HBase versions 1.3 and up).
The list of metrics are available [here](/docs/features/metrics).
PQS Metrics use [Hadoop Metrics 2](https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/Metrics.html) internally for metrics publishing. Hence it publishes various JVM related metrics. Metrics can be filtered based on certain tags, which can be configured by the property specified in hbase-site.xml on the classpath. Further details are provided in Configuration section.
## Configuration
Server components are spread across a number of java packages, so effective
logging configuration requires updating multiple packages. The default server
logging configuration sets the following log levels:
```properties
log4j.logger.org.apache.calcite.avatica=INFO
log4j.logger.org.apache.phoenix.queryserver.server=INFO
log4j.logger.org.eclipse.jetty.server=INFO
```
As of the time of writing, the underlying Avatica component respects the
following configuration options exposed via `hbase-site.xml`.
### Server Instantiation
| Property | Description | Default |
| --------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------- |
| `phoenix.queryserver.http.port` | Port the server listens on. | `8765` |
| `phoenix.queryserver.metafactory.class` | Avatica `Meta.Factory` implementation class. | `org.apache.phoenix.queryserver.server.PhoenixMetaFactoryImpl` |
| `phoenix.queryserver.serialization` | Transport/serialization format (`PROTOBUF` or `JSON`). | `PROTOBUF` |
### HTTPS
HTTPS support is only available in unbundled `phoenix-queryserver` versions.
| Property | Description | Default |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------- |
| `phoenix.queryserver.tls.enabled` | Enables HTTPS transport. When enabled, keystore/truststore files and passwords are also required. | `false` |
| `phoenix.queryserver.tls.keystore` | Keystore file containing the HTTPS private key. | *unset* |
| `phoenix.queryserver.tls.keystore.password` | Password for HTTPS keystore. | *empty string* |
| `phoenix.queryserver.tls.truststore` | Keystore file containing the HTTPS certificate. | *unset* |
| `phoenix.queryserver.tls.truststore.password` | Password for HTTPS truststore. | *empty string* |
### Secure Cluster Connection
| Property | Description | Default |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- |
| `hbase.security.authentication` | When set to `kerberos`, server logs in before initiating Phoenix connections. | *specified in `hbase-default.xml`* |
| `phoenix.queryserver.keytab.file` | Key for keytab file lookup. | *unset* |
| `phoenix.queryserver.kerberos.principal` | Kerberos principal for authentication; also used for SPNEGO if HTTP principal is not configured. | *unset* |
| `phoenix.queryserver.http.keytab.file` | Keytab for SPNEGO auth; required if `phoenix.queryserver.kerberos.http.principal` is set; falls back to `phoenix.queryserver.keytab.file`. | *unset* |
| `phoenix.queryserver.http.kerberos.principal` | Kerberos principal for SPNEGO auth; falls back to `phoenix.queryserver.kerberos.principal`. | *unset* |
| `phoenix.queryserver.kerberos.http.principal` | Deprecated; use `phoenix.queryserver.http.kerberos.principal`. | *unset* |
| `phoenix.queryserver.kerberos.allowed.realms` | Additional Kerberos realms allowed for SPNEGO auth. | *unset* |
| `phoenix.queryserver.dns.nameserver` | DNS hostname. | `default` |
| `phoenix.queryserver.dns.interface` | Network interface name for DNS queries. | `default` |
### Server Connection Cache
| Property | Description | Default |
| ----------------------------------------- | ----------------------------------------------------------------------- | --------- |
| `avatica.connectioncache.concurrency` | Connection cache concurrency level. | `10` |
| `avatica.connectioncache.initialcapacity` | Connection cache initial capacity. | `100` |
| `avatica.connectioncache.maxcapacity` | Connection cache maximum capacity; LRU eviction begins near this point. | `1000` |
| `avatica.connectioncache.expiryduration` | Connection cache expiration duration. | `10` |
| `avatica.connectioncache.expiryunit` | Time unit for `avatica.connectioncache.expiryduration`. | `MINUTES` |
### Server Statement Cache
| Property | Description | Default |
| ---------------------------------------- | ---------------------------------------------------------------------- | --------- |
| `avatica.statementcache.concurrency` | Statement cache concurrency level. | `100` |
| `avatica.statementcache.initialcapacity` | Statement cache initial capacity. | `1000` |
| `avatica.statementcache.maxcapacity` | Statement cache maximum capacity; LRU eviction begins near this point. | `10000` |
| `avatica.statementcache.expiryduration` | Statement cache expiration duration. | `5` |
| `avatica.statementcache.expiryunit` | Time unit for `avatica.statementcache.expiryduration`. | `MINUTES` |
### Impersonation
| Property | Description | Default |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------ | ------- |
| `phoenix.queryserver.withRemoteUserExtractor` | If true, extracts impersonated user from request param instead of authenticated HTTP user. | `false` |
| `phoenix.queryserver.remoteUserExtractor.param` | HTTP request parameter name for impersonated user. | `doAs` |
### Metrics
| Property | Description | Default |
| ---------------------------- | ----------------------------------------------------------------------------------------------- | ------------ |
| `phoenix.client.metrics.tag` | Tag for filtering Phoenix global client metrics emitted by PQS in `hadoop-metrics2.properties`. | `FAT_CLIENT` |
## Query Server Additions
The Phoenix Query Server is meant to be horizontally scalable which means that it
is a natural fit for add-on features like service discovery and load balancing.
### Load balancing
The Query Server can use off-the-shelf HTTP load balancers such as the [Apache HTTP Server](https://httpd.apache.org),
[nginx](https://nginx.org), or [HAProxy](https://haproxy.org). The primary requirement of
using these load balancers is that the implementation must implement "sticky session" (when a client
communicates with a backend server, that client continues to talk to that backend server). The Query Server also
provides some bundled functionality for load balancing using ZooKeeper.
The ZooKeeper-based load balancer functions by automatically registering PQS instances in
ZooKeeper and then allows clients to query the list of available servers. This implementation, unlike
the others mentioned above, requires that client use the advertised information to make a routing decision.
In this regard, this ZooKeeper-based approach is more akin to a service-discovery layer than a traditional
load balancer. This load balancer implementation does *not* support SASL-based (Kerberos) ACLs in
ZooKeeper (see [PHOENIX-4085](https://issues.apache.org/jira/browse/PHOENIX-4085)).
The following properties configure this load balancer:
| Property | Description | Default |
| -------------------------------------------- | -------------------------------------------------------------- | ------------- |
| `phoenix.queryserver.loadbalancer.enabled` | If true, PQS registers itself in ZooKeeper for load balancing. | `false` |
| `phoenix.queryserver.base.path` | Root znode where PQS instances register themselves. | `/phoenix` |
| `phoenix.queryserver.service.name` | Unique name to identify this PQS instance. | `queryserver` |
| `phoenix.queryserver.zookeeper.acl.username` | Username for optional DIGEST ZooKeeper ACL. | `phoenix` |
| `phoenix.queryserver.zookeeper.acl.password` | Password for optional DIGEST ZooKeeper ACL. | `phoenix` |
# Row Timestamp Column (/docs/features/row-timestamp-column)
Phoenix 4.6 provides a way to map HBase’s native row timestamp to a Phoenix column. This helps you take advantage of HBase optimizations for time ranges in store files, along with query optimizations built into Phoenix.
For a column to be designated as ROW\_TIMESTAMP, certain constraints need to be followed:
* Only a primary key column of type `TIME`, `DATE`, `TIMESTAMP`, `BIGINT`, or `UNSIGNED_LONG` can be designated as `ROW_TIMESTAMP`.
* Only one primary key column can be designated as `ROW_TIMESTAMP`.
* The column value cannot be `NULL` (because it maps directly to the HBase row timestamp). This also means a column can be declared as `ROW_TIMESTAMP` only when creating the table.
* A `ROW_TIMESTAMP` column value cannot be negative. For `DATE`/`TIME`/`TIMESTAMP`, the corresponding epoch time in milliseconds cannot be less than zero.
When upserting rows for a table with a row timestamp column (using `UPSERT VALUES` or `UPSERT SELECT`), you can explicitly provide the row timestamp value or let Phoenix set it automatically. When not specified, Phoenix sets the row timestamp column value to server-side time. The value also becomes the timestamp of the corresponding row in HBase.
## Sample schema
```sql
CREATE TABLE DESTINATION_METRICS_TABLE
(
CREATED_DATE DATE NOT NULL,
METRIC_ID CHAR(15) NOT NULL,
METRIC_VALUE LONG
CONSTRAINT PK PRIMARY KEY (CREATED_DATE ROW_TIMESTAMP, METRIC_ID)
)
SALT_BUCKETS = 8;
```
```sql
UPSERT INTO DESTINATION_METRICS_TABLE VALUES (?, ?, ?)
```
This sets `CREATED_DATE` to the value specified in the corresponding bind parameter.
```sql
UPSERT INTO DESTINATION_METRICS_TABLE (METRIC_ID, METRIC_VALUE) VALUES (?, ?)
```
This sets `CREATED_DATE` to server-side time.
```sql
UPSERT INTO DESTINATION_METRICS_TABLE (CREATED_DATE, METRICS_ID, METRIC_VALUE)
SELECT DATE, METRICS_ID, METRIC_VALUE FROM SOURCE_METRICS_TABLE
```
This sets `CREATED_DATE` to the `DATE` selected from `SOURCE_METRICS_TABLE`.
```sql
UPSERT INTO DESTINATION_METRICS_TABLE (METRICS_ID, METRIC_VALUE)
SELECT METRICS_ID, METRIC_VALUE FROM SOURCE_METRICS_TABLE
```
This sets `CREATED_DATE` in the destination table to the server timestamp.
When querying with filters on the row timestamp column, Phoenix performs its usual row-key optimizations and can also set scan min/max time ranges appropriately. Using this time range information, HBase can skip store files that do not fall in the target time range, which significantly improves performance, especially for tail-end data queries.
# Salted Tables (/docs/features/salted-tables)
Sequential writes in HBase may suffer from region server hotspotting if your row key is monotonically increasing. Salting the row key helps mitigate this problem. See [this article](http://blog.sematext.com/2012/04/09/hbasewd-avoid-regionserver-hotspotting-despite-writing-records-with-sequential-keys/) for details.
Phoenix provides a way to transparently salt the row key with a salting byte for a table. Specify this at table creation time with the `SALT_BUCKETS` table property, using a value from 1 to 256:
```sql
CREATE TABLE table (a_key VARCHAR PRIMARY KEY, a_col VARCHAR) SALT_BUCKETS = 20;
```
There are some behavior differences and cautions to be aware of when using a salted table.
## Sequential scan
Since a salted table does not store data in natural key sequence, a strict sequential scan does not return data in natural sorted order. Clauses that force sequential scan behavior (for example, `LIMIT`) may return rows differently compared to a non-salted table.
## Splitting
If no split points are specified, a salted table is pre-split on salt-byte boundaries to ensure load distribution across region servers, including during initial table growth. If split points are provided manually, they must include the salt byte.
## Row key ordering
Pre-splitting also ensures that entries in each region start with the same salt byte and are therefore locally sorted. During a parallel scan across regions, Phoenix can use this property to perform a client-side merge sort. The resulting scan can still be returned sequentially, as if from a normal table.
This row-key ordered scan can be enabled by setting `phoenix.query.rowKeyOrderSaltedTable=true` in `hbase-site.xml`. When enabled, user-specified split points on salted tables are disallowed to ensure each bucket contains only entries with the same salt byte. With this property enabled, a salted table behaves more like a normal table for scans and returns items in row-key order.
## Performance
Using salted tables with pre-splitting helps distribute write workload uniformly across region servers, which improves write performance. Our [performance evaluation](/docs/fundamentals/performance#performance-salting) shows that salted tables can achieve up to 80% higher write throughput than non-salted tables.
Reads from salted tables can also benefit from more uniform data distribution. Our [performance evaluation](/docs/fundamentals/performance#performance-salting) shows improved read performance for queries focused on subsets of data.
# Secondary Indexes (/docs/features/secondary-indexes)
Secondary indexes are an orthogonal way to access data from its primary access path. In HBase, you have a single
index that is lexicographically sorted on the primary row key. Access to records in any way other than through
the primary row requires scanning over potentially all the rows in the table to test them against your filter.
With secondary indexing, the columns or expressions you index form an alternate row key to allow point lookups
and range scans along this new axis.
## Covered Indexes
Phoenix is particularly powerful in that we provide *covered* indexes -
we do not need to go back to the primary table once we have found the index entry. Instead, we bundle the data
we care about right in the index rows, saving read-time overhead.
For example, the following would create an index on the `v1` and `v2` columns and
include the `v3` column in the index as well to prevent having to get it from the data table:
```sql
CREATE INDEX my_index ON my_table (v1,v2) INCLUDE (v3)
```
## Functional Indexes
Functional indexes (available in 4.3 and above) allow you to create
an index not just on columns, but on an arbitrary expressions. Then when a query uses that expression, the index
may be used to retrieve the results instead of the data table. For example, you could create an index on `UPPER(FIRST_NAME||' '||LAST_NAME)`
to allow you to do case insensitive searches on the combined first name and last name of a person.
For example, the following would create this functional index:
```sql
CREATE INDEX UPPER_NAME_IDX ON EMP (UPPER(FIRST_NAME||' '||LAST_NAME))
```
With this index in place, when the following query is issued, the index would be used instead of the data table to retrieve the results:
```sql
SELECT EMP_ID FROM EMP WHERE UPPER(FIRST_NAME||' '||LAST_NAME)='JOHN DOE'
```
Phoenix supports two types of indexing techniques: global and local indexing.
Each are useful in different scenarios and have their own failure profiles and performance characteristics.
## Global Indexes
Global indexing targets *read heavy* uses cases. With global indexes, all the performance penalties for indexes occur at write time. We intercept the data table updates on write ([DELETE](/docs/grammar#delete), [UPSERT VALUES](/docs/grammar#upsert-values) and [UPSERT SELECT](/docs/grammar#upsert-select)), build the index update and then sent any necessary updates to all interested index tables. At read time, Phoenix will select the index table to use that will produce the fastest query time and directly scan it just like any other HBase table. An index will not be used for a query that references a column that isn't part of the index.
## Local Indexes
Local indexing targets *write heavy*, *space constrained* use cases. Just like with global indexes, Phoenix will automatically select whether or not to use a local index at query-time. With local indexes, index data and table data co-reside on same server preventing any network overhead during writes. Local indexes can be used even when the query isn't fully covered (i.e. Phoenix automatically retrieve the columns not in the index through point gets against the data table). Unlike global indexes, all local indexes of a table are stored in a single, separate shared table prior to 4.8.0 version. From 4.8.0 onwards we are storing all local index data in the separate shadow column families in the same data table. At read time when the local index is used, every region must be examined for the data as the exact region location of index data cannot be predetermined. Thus some overhead occurs at read-time.
## Index Population
By default, when an index is created, it is populated synchronously during the CREATE INDEX call. This may not be feasible depending on the current size of the data table. As of 4.5, initially population of an index may be done asynchronously by including the `ASYNC` keyword in the index creation DDL statement:
```sql
CREATE INDEX async_index ON my_schema.my_table (v) ASYNC
```
The map reduce job that populates the index table must be kicked off separately through the HBase command line like this:
```bash
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool
--schema MY_SCHEMA --data-table MY_TABLE --index-table ASYNC_IDX
--output-path ASYNC_IDX_HFILES
```
Only when the map reduce job is complete will the index be activated and start to be used in queries. The job is resilient to the client being exited. The output-path option is used to specify a HDFS directory that is used for writing HFiles to.
You can also start index population for all indexes in `BUILDING` ("b") state with the following HBase command line:
```bash
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.automation.PhoenixMRJobSubmitter
```
#### ASYNC Index threshold
As of 4.16 (and 5.1), setting the `phoenix.index.async.threshold` property to a positive number will disallow synchronous index creation if the estimated indexed data size exceeds `phoenix.index.async.threshold` (in bytes).
## Index Usage
Indexes are automatically used by Phoenix to service a query when it's determined more efficient to do so. However, a global index will not be used unless all of the columns referenced in the query are contained in the index. For example, the following query would not use the index, because `v2` is referenced in the query but not included in the index:
```sql
SELECT v2 FROM my_table WHERE v1 = 'foo'
```
There are two means of getting an index to be used in this case:
1. Create a *covered* index by including `v2` in the index:
```sql
CREATE INDEX my_index ON my_table (v1) INCLUDE (v2)
```
This will cause the v2 column value to be copied into the index and kept in synch as it changes. This will obviously increase the size of the index.
2. Create a *local* index:
```sql
CREATE LOCAL INDEX my_index ON my_table (v1)
```
Unlike global indexes, local indexes *will* use an index even when all columns referenced in the query are not contained in the index. This is done by default for local indexes because we know that the table and index data coreside on the same region server thus ensuring the lookup is local.
## Index Removal
To drop an index, you'd issue the following statement:
```sql
DROP INDEX my_index ON my_table
```
If an indexed column is dropped in the data table, the index will automatically be dropped. In addition, if a covered column is dropped in the data table, it will be automatically dropped from the index as well.
## Index Properties
Just like with the `CREATE TABLE` statement, the `CREATE INDEX` statement may pass through properties to apply to the underlying HBase table, including the ability to salt it:
```sql
CREATE INDEX my_index ON my_table (v2 DESC, v1) INCLUDE (v3)
SALT_BUCKETS=10, DATA_BLOCK_ENCODING='NONE'
```
Note that if the primary table is salted, then the index is automatically salted in the same way for global indexes. In addition, the MAX\_FILESIZE for the index is adjusted down, relative to the size of the primary versus index table. For more on salting see [here](/docs/features/salted-tables). With local indexes, on the other hand, specifying `SALT_BUCKETS` is not allowed.
## Consistency Guarantees
On successful return to the client after a commit, all data is guaranteed to be written to all interested indexes and the
primary table. In other words, index updates are synchronous with the same strong consistency guarantees provided by HBase.
However, since indexes are stored in separate tables than the data table, depending on the properties of the table and the
type of index, the consistency between your table and index varies in the event that a commit fails due to a server-side
crash. This is an important design consideration driven by your requirements and use case.
Outlined below are the different options with various levels of consistency guarantees.
### Local Indexes
Since Phoenix 4.8 local indexes are always guaranteed to be consistent.
### Global Indexes on Transactional Tables
By declaring your table as [transactional](/docs/features/transactions), you achieve the highest level of consistency guarantee
between your table and index. In this case, your commit of your table mutations and related index updates are atomic
with strong [ACID](https://en.wikipedia.org/wiki/ACID) guarantees. If the commit fails, then none of your data (table
or index) is updated, thus ensuring that your table and index are always in sync.
Why not just always declare your tables as transactional? This may be fine, especially if your
table is declared as immutable, since the transactional overhead is very small in this case. However, if your data
is mutable, make sure that the overhead associated with the conflict detection that occurs with transactional tables
and the operational overhead of running the transaction manager is acceptable. Additionally, transactional tables
with secondary indexes potentially lowers your availability of being able to write to your data table, as both the
data table and its secondary index tables must be availalbe as otherwise the write will fail.
### Global Indexes on Immutable Tables
For a table in which the data is only written once and never updated in-place, certain optimizations may be made to reduce the write-time overhead for incremental maintenance.
This is common with time-series data such as log or event data, where once a row is written, it will never be updated.
To take advantage of these optimizations, declare your table as immutable by adding the `IMMUTABLE_ROWS=true` property to your DDL statement:
```sql
CREATE TABLE my_table (k VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS=true
```
All indexes on a table declared with `IMMUTABLE_ROWS=true` are considered immutable (note that by default, tables are considered mutable).
For global immutable indexes, the index is maintained entirely on the client-side with the index table being generated as changes to the data table occur.
Local immutable indexes, on the other hand, are maintained on the server-side.
Note that no safeguards are in-place to enforce that a table declared as immutable doesn't actually mutate data (as that would negate the performance gain achieved).
If that was to occur, the index would no longer be in sync with the table.
If you have an existing table that you'd like to switch from immutable indexing to mutable indexing, use the `ALTER TABLE` command as show below:
```sql
ALTER TABLE my_table SET IMMUTABLE_ROWS=false
```
Global Indexing for Immutable tables has been completely rewritten for version 4.15 (and 5.1)
#### Immutable table indexes for 4.15 (and 5.1) and newer versions
Immutable index updates go through the same three phase writes as mutable index updates do except that deleting or un-verifying existing index rows is not applicable to immutable indexes.
This guarantees that the index tables are always in sync with the data tables.
#### Immutable table indexes for 4.14 (and 5.0) and older versions
Indexes on non transactional, immutable tables have no mechanism in place to automatically deal with a commit failure. Maintaining
consistency between the table and index is left to the client to handle. Because the updates are idempotent, the simplest
solution is for the client to continue retrying the batch of mutations until they succeed.
### Global Indexes on Mutable Tables
Global Indexing for Mutable tables has been completely rewritten for version 4.15 (and 5.1)
#### Mutable table indexes for 4.15 (and 5.1) and newer versions
The new Strongly Consistent Global Indexing feature uses a three-phase indexing algorithm to guarantee that the index tables are always in sync with the data tables.
The implementation uses a shadow column to track the status of index rows:
* **Write:**
1. Set the status of existing index rows to unverified and write the new index rows with the unverified status
2. Write the data table rows
3. Delete the existing index rows and set the status of new rows to verified
* **Read:**
1. Read the index rows and check their status
2. The unverified rows are repaired from the data table
* **Delete:**
1. Set the index table rows with the unverified status
2. Delete the data table rows
3. Delete index table rows
See [resources](/docs/features/secondary-indexes#secondary-indexes-resources) for more in-depth information.
All newly created tables use the new indexing algorithm.
Indexes created with older Phoenix versions will continue to use the old implementation, until upgraded with [IndexUpgradeTool](/docs/features/secondary-indexes#index-upgrade-tool)
#### Mutable table indexes for 4.14 (and 5.0) and older versions
For non transactional mutable tables, we maintain index update durability by adding the index updates to the Write-Ahead-Log (WAL) entry of the primary table row.
Only after the WAL entry is successfully synced to disk do we attempt to make the index/primary table updates. We write the
index updates in parallel by default, leading to very high throughput. If the server crashes while we are writing the index
updates, we replay the all the index updates to the index tables in the WAL recovery process and rely on the idempotence of
the updates to ensure correctness. Therefore, indexes on non transactional mutable tables are only ever a single batch of
edits behind the primary table.
It's important to note several points:
* For non transactional tables, you could see the index table out of sync with the primary table.
* As noted above, this is ok as we are only a very small bit behind and out of sync for very short periods
* Each data row and its index row(s) are guaranteed to to be written or lost - we never see partial updates as this is part of the atomicity guarantees of HBase.
* Data is first written to the table followed by the index tables (the reverse is true if the WAL is disabled).
**Singular Write Path**
There is a single write path that guarantees the failure properties. All writes to the HRegion get intercepted by our
coprocessor. We then build the index updates based on the pending update (or updates, in the case of the batch).
These update are then appended to the WAL entry for the original update.
If we get any failure up to this point, we return the failure to the client and no data is persisted or made visible
to the client.
Once the WAL is written, we ensure that the index and primary table data will become visible, even in the case of a failure.
* If the server *does* crash, we then replay the index updates with the usual WAL replay mechanism
* If the server does *not* crash, we just insert the index updates to their respective tables.
* If the index updates fail, the various means of maintaining consistency are outlined below.
* If the Phoenix system catalog table cannot be reached when a failure occurs, we force the server to be immediately aborted and failing this, call `System.exit` on the JVM, forcing the server to die. By killing the server, we ensure that the WAL will be replayed on recovery, replaying the index updates to their appropriate tables. This ensures that a secondary index is not continued to be used when it's in a know, invalid state.
**Disallow table writes until mutable index is consistent**
The highest level of maintaining consistency between your non transactional table and index is to declare that writes to the
data table should be temporarily disallowed in the event of a failure to update the index. In this consistency
mode, the table and index will be held at the timestamp before the failure occurred, with writes to the data
table being disallowed until the index is back online and in-sync with the data table. The index will
remain active and continue to be used by queries as usual.
The following server-side configurations control this behavior:
* `phoenix.index.failure.block.write` must be true to enable a writes to the data table to fail
in the event of a commit failure until the index can be caught up with the data table.
* `phoenix.index.failure.handling.rebuild` must be true (the default) to enable a mutable index to
be rebuilt in the background in the event of a commit failure.
**Disable mutable indexes on write failure until consistency restored**
The default behavior with mutable indexes is to mark the index as disabled if a write to them fails at commit time,
partially rebuild them in the background, and then mark them as active again once consistency is restored. In this
consistency mode, writes to the data table will not be blocked while the secondary index is being rebuilt. However,
the secondary index will not be used by queries while the rebuild is happening.
The following server-side configurations control this behavior:
* `phoenix.index.failure.handling.rebuild` must be true (the default) to enable a mutable index to
be rebuilt in the background in the event of a commit failure.
* `phoenix.index.failure.handling.rebuild.interval` controls the millisecond frequency at which the server
checks whether or not a mutable index needs to be partially rebuilt to catch up with updates to the data
table. The default is 10000 or 10 seconds.
* `phoenix.index.failure.handling.rebuild.overlap.time` controls how many milliseconds to go back from the timestamp
at which the failure occurred to go back when a partial rebuild is performed. The default is 1.
**Disable mutable index on write failure with manual rebuild required**
This is the lowest level of consistency for mutable secondary indexes. In this case, when a write to a secondary
index fails, the index will be marked as disabled with a manual
[rebuild of the index](/docs/grammar#alter-index) required to enable it to be used
once again by queries.
The following server-side configurations control this behavior:
* `phoenix.index.failure.handling.rebuild` must be set to false to disable a mutable index from being
rebuilt in the background in the event of a commit failure.
#### BulkLoad Tool Limitation
The `BulkLoadTools` (e.g. `CSVBulkLoadTool` and `JSONBulkLoadTool`) cannot presently generate correct updates to mutable
secondary indexes when pre-existing records are being updated. In the normal mutable secondary index write path, we can
safely calculate a Delete (for the old record) and a Put (for the new record) for each secondary index while holding a
row-lock to prevent concurrent updates. In the context of a MapReduce job, we cannot effectively execute this same logic
because we are specifically doing this "out of band" from the HBase RegionServers. As such, while these Tools generate
HFiles for the index tables with the proper updates for the data being loaded, any previous index records corresponding
to the same record in the table are not deleted. This net-effect of this limitation is: if you use these Tools to re-ingest
the same records to an index table, that index table will have duplicate records in it which will result in incorrect
query results from that index table.
To perform incremental loads of data using the `BulkLoadTools` which may update existing records, you must
drop and re-create all index tables after the data table is loaded. Re-creating the index with the `ASYNC` option and
using `IndexTool` to populate and enable that index is likely a must for tables of non-trivial size.
To perform incremental loading of CSV datasets that do not require any manual index intervention, the `psql` tool can
be used in place of the BulkLoadTools. Additionally, a MapReduce job could be written to parse CSV/JSON data and write
it directly to Phoenix; although, such a tool is not currently provided by Phoenix for users.
## Setup
Non transactional, mutable indexing requires special configuration options on the region server and master to run - Phoenix ensures that they are setup correctly when you enable mutable indexing on the table; if the correct properties are not set, you will not be able to use secondary indexing. After adding these settings to your hbase-site.xml, you'll need to do a rolling restart of your cluster.
As Phoenix matures, it needs less and less manual configuration. For older Phoenix versions you'll need to add the properties listed for that version, *as well as the properties listed for the later versions*.
#### For Phoenix 4.12 and later
You will need to add the following parameters to `hbase-site.xml` on each region server:
```xml
hbase.regionserver.wal.codecorg.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec
```
The above property enables custom WAL edits to be written, ensuring proper writing/replay of the index updates. This codec supports the usual host of WALEdit options, most notably WALEdit compression.
#### For Phoenix 4.8 - 4.11
The following configuration changes are also required to the server-side hbase-site.xml on the master and regions server nodes:
```xml
hbase.region.server.rpc.scheduler.factory.classorg.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactoryFactory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updateshbase.rpc.controllerfactory.classorg.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactoryFactory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates
```
The above properties prevent deadlocks from occurring during index maintenance for global indexes (HBase 0.98.4+ and Phoenix 4.3.1+) by ensuring index updates are processed with a higher priority than data updates. It also prevents deadlocks by ensuring metadata rpc calls are processed with a higher priority than data rpc calls.
#### For Phoenix versions 4.7 and below
The following configuration changes are also required to the server-side hbase-site.xml on the master and regions server nodes:
```xml
hbase.master.loadbalancer.classorg.apache.phoenix.hbase.index.balancer.IndexLoadBalancerhbase.coprocessor.master.classesorg.apache.phoenix.hbase.index.master.IndexMasterObserverhbase.coprocessor.regionserver.classesorg.apache.hadoop.hbase.regionserver.LocalIndexMerger
```
The above properties are required to use local indexing.
### Upgrading Local Indexes created before 4.8.0
While upgrading the Phoenix to 4.8.0+ version at server remove above three local indexing related configurations from `hbase-site.xml` if present. From client we are supporting both online(while initializing the connection from phoenix client of 4.8.0+ versions) and offline(using `psql` tool) upgrade of local indexes created before 4.8.0. As part of upgrade we recreate the local indexes in ASYNC mode. After upgrade user need to build the indexes using [IndexTool](/docs/features/secondary-indexes#index-population)
Following client side configuration used in the upgrade.
* `phoenix.client.localIndexUpgrade`\
The value of it is true means online upgrade and false means offline upgrade.\
**Default: true**
Command to run offline upgrade using `psql`:
```shell
psql [zookeeper] -l
```
## Tuning
Out the box, indexing is pretty fast. However, to optimize for your particular environment and workload, there are several properties you can tune.
All the following parameters must be set in `hbase-site.xml` - they are true for the entire cluster and all index tables, as well as across all regions on the same server (so, for instance, a single server would not write to too many different index tables at once).
1. `index.builder.threads.max`
* Number of threads to used to build the index update from the primary table update
* Increasing this value overcomes the bottleneck of reading the current row state from the underlying HRegion. Tuning this value too high will just bottleneck at the HRegion as it will not be able to handle too many concurrent scan requests as well as general thread-swapping concerns.
* **Default: 10**
2. `index.builder.threads.keepalivetime`
* Amount of time in seconds after we expire threads in the builder thread pool.
* Unused threads are immediately released after this amount of time and not core threads are retained (though this last is a small concern as tables are expected to sustain a fairly constant write load), but simultaneously allows us to drop threads if we are not seeing the expected load.
* **Default: 60**
3. `index.writer.threads.max`
* Number of threads to use when writing to the target index tables.
* The first level of parallelization, on a per-table basis - it should roughly correspond to the number of index tables
* **Default: 10**
4. `index.writer.threads.keepalivetime`
* Amount of time in seconds after we expire threads in the writer thread pool.
* Unused threads are immediately released after this amount of time and not core threads are retained (though this last is a small concern as tables are expected to sustain a fairly constant write load), but simultaneously allows us to drop threads if we are not seeing the expected load.
* **Default: 60**
5. `hbase.htable.threads.max`
* Number of threads each index `HTable` can use for writes.
* Increasing this allows more concurrent index updates (for instance across batches), leading to high overall throughput.
* **Default: 2,147,483,647**
6. `hbase.htable.threads.keepalivetime`
* Amount of time in seconds after we expire threads in the `HTable`'s thread pool.
* Using the "direct handoff" approach, new threads will only be created if it is necessary and will grow unbounded. This could be bad but `HTable`s only create as many `Runnable`s as there are region servers; therefore, it also scales when new region servers are added.
* **Default: 60**
7. `index.tablefactory.cache.size`
* Number of index `HTable`s we should keep in cache.
* Increasing this number ensures that we do not need to recreate an `HTable` for each attempt to write to an index table. Conversely, you could see memory pressure if this value is set too high.
* **Default: 10**
8. `org.apache.phoenix.regionserver.index.priority.min`
* Value to specify to bottom (inclusive) of the range in which index priority may lie.
* **Default: 1000**
9. `org.apache.phoenix.regionserver.index.priority.max`
* Value to specify to top (exclusive) of the range in which index priority may lie.
* Higher priorites within the index min/max range do not means updates are processed sooner.
* **Default: 1050**
10. `org.apache.phoenix.regionserver.index.handler.count`
* Number of threads to use when serving index write requests for global index maintenance.
* Though the actual number of threads is dictated by the Max(number of call queues, handler count), where the number of call queues is determined by standard HBase configuration. To further tune the queues, you can adjust the standard rpc queue length parameters (currently, there are no special knobs for the index queues), specifically `ipc.server.max.callqueue.length` and `ipc.server.callqueue.handler.factor`. See the [HBase Reference Guide](https://hbase.apache.org/docs) for more details.
* **Default: 30**
## Performance
We track secondary index performance via our [performance framework](http://phoenix-bin.github.io/client/performance/latest.htm). This is a generic test of performance based on defaults - your results will vary based on hardware specs as well as you individual configuration.
That said, we have seen secondary indexing (both immutable and mutable) go as quickly as \< 2x the regular write path on a small, (3 node) desktop-based cluster. This is actually pretty reasonable as we have to write to multiple tables as well as build the index update.
## Index Scrutiny Tool
With Phoenix 4.12, there is now a tool to run a MapReduce job to verify that an index table is valid against its data table. The only way to find orphaned rows in either table is to scan over all rows in the table and do a lookup in the other table for the corresponding row. For that reason, the tool can run with either the data or index table as the "source" table, and the other as the "target" table. The tool writes all invalid rows it finds either to file or to an output table `PHOENIX_INDEX_SCRUTINY`. An invalid row is a source row that either has no corresponding row in the target table, or has an incorrect value in the target table (i.e. covered column value).
The tool has job counters that track its status. `VALID_ROW_COUNT`, `INVALID_ROW_COUNT`, `BAD_COVERED_COL_VAL_COUNT`. Note that invalid rows - bad col val rows = number of orphaned rows. These counters are written to the table `PHOENIX_INDEX_SCRUTINY_METADATA`, along with other job metadata.
The Index Scrutiny Tool can be launched via the `hbase` command (in `hbase/bin`) as follows:
```shell
hbase org.apache.phoenix.mapreduce.index.IndexScrutinyTool -dt my_table -it my_index -o
```
It can also be run from Hadoop using either the phoenix-core or phoenix-server jar as follows:
```bash
HADOOP_CLASSPATH=$(hbase mapredcp) hadoop jar phoenix--server.jar org.apache.phoenix.mapreduce.index.IndexScrutinyTool -dt my_table -it my_index -o
```
By default two mapreduce jobs are launched, one with the data table as the source table and one with the index table as the source table.
The following parameters can be used with the Index Scrutiny Tool:
| *Parameter* | *Description* |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| -dt,--data-table | Data table name (mandatory) |
| -it,--index-table | Index table name (mandatory) |
| -s,--schema | Phoenix schema name (optional) |
| -src,--source | DATA\_TABLE\_SOURCE, INDEX\_TABLE\_SOURCE, or BOTH. Defaults to BOTH |
| -o,--output | Whether to output invalid rows. Off by default |
| -of,--output-format | TABLE or FILE output format. Defaults to TABLE |
| -om,--output-max | Maximum number of invalid rows to output per mapper. Defaults to 1M |
| -op,--output-path | For FILE output format, the HDFS directory where files are written |
| -t,--time | Timestamp in millis at which to run the scrutiny. This is important so that incoming writes don't throw off the scrutiny. Defaults to current time minus 60 seconds |
| -b,--batch-size | Number of rows to compare at a time |
### Limitations
* If rows are actively being updated or deleted while the scrutiny is running, the tool may give you false positives for inconsistencies ([PHOENIX-4277](https://issues.apache.org/jira/browse/PHOENIX-4277)).
* Snapshot reads are not supported by the scrutiny tool ([PHOENIX-4270](https://issues.apache.org/jira/browse/PHOENIX-4270)).
## Index Upgrade Tool
`IndexUpgradeTool` updates global indexes created by Phoenix 4.14 and earlier (or 5.0) to use the new Strongly Consistent Global Indexes implementation.
It accepts following parameters:
| *Parameter* | *Description* | *only in version* |
| ------------------------ | ------------------------------------------------------------------------------ | ----------------- |
| -o,--operation | *upgrade* or *rollback* (mandatory) | |
| -tb,--tables | *\[table1,table2,table3]* (-tb or -f mandatory) | |
| -f,--file | Csv file with above format (-tb or -f mandatory) | |
| -d,--dry-run | If passed this will just output steps that will be executed; like a dry run | |
| -h,--help | Help on how to use the tool | |
| -lf,--logfile | File location to dump the logs | |
| -sr,--index-sync-rebuild | whether or not synchronously rebuild the indexes; default rebuild asynchronous | 4.15 |
| -rb,--index-rebuild | Rebuild the indexes. Set -tool to pass options to IndexTool | 4.16+, 5.1+ |
| -tool,--index-tool | Options to pass to indexTool when rebuilding indexes | 4.16+, 5.1+ |
```shell
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexUpgradeTool -o [upgrade/rollback] -tb [table_name] -lf [/tmp/index-upgrade-tool.log]
```
For 4.16+/5.1+ either specifying the -rb option, or manually rebuilding the indexes with IndexTool after the upgrade is recommended, otherwise the first access of every index row will trigger an index row repair.
Depending on whether index is mutable, it will remove *Indexer* coprocessor from a data table and load new coprocessor *IndexRegionObserver*. For both immutable and mutable, it will load *GlobalIndexChecker* coprocessor on Index table. During this process, data table and index table are *disabled-loaded/unloaded with coproc-enabled* within short time span. At the end, it does an asynchronous index rebuilds. Index reads are not blocked while index-rebuild is still ongoing, however, they may be a bit slower for rows written prior to upgrade.
`IndexUpgradeTool` doesn't make any distinction between view-index and table-index. When a table is passed, it will perform the upgrade-operation on all the 'children' indexes of the given table.
## Resources
There have been several presentations given on how secondary indexing works in Phoenix that have a more in-depth look at how indexing works (with pretty pictures!):
* [Slides for Strongly Consistent Global Indexes for Apache Phoenix, 2019 Distributed SQL Summit](https://www.slideshare.net/YugabyteDB/strongly-consistent-global-indexes-for-apache-phoenix-176863877)
* [Recording of Strongly Consistent Global Indexes for Apache Phoenix, 2019 Distributed SQL Summit](https://vimeo.com/362358494)
* [Slides for Local Secondary Indexes in Apache Phoenix, 2017 PhoenixCon](https://www.slideshare.net/rajeshbabuchintaguntla/local-secondary-indexes-in-apache-phoenix)
These older resources refer to obsolete implementations in some cases
* [Los Anglees HBase Meetup](http://www.slideshare.net/jesse_yates/phoenix-secondary-indexing-la-hug-sept-9th-2013) - Sept, 4th, 2013
* [Local Indexes](https://github.com/Huawei-Hadoop/hindex/blob/master/README.md#how-it-works) by Huawei
* [PHOENIX-938](https://issues.apache.org/jira/browse/PHOENIX-938) and [HBASE-11513](https://issues.apache.org/jira/browse/HBASE-11513) for deadlock prevention during global index maintenance.
* [PHOENIX-1112: Atomically rebuild index partially when index update fails](https://issues.apache.org/jira/browse/PHOENIX-1112)
# Skip Scan (/docs/features/skip-scan)
Phoenix uses Skip Scan for intra-row scanning which allows for [significant performance improvement](/docs/fundamentals/performance#performance-skip-scan) over Range Scan when rows are retrieved based on a given set of keys.
Skip Scan leverages `SEEK_NEXT_USING_HINT` in HBase filters. It stores information about which key sets or key ranges are being searched for in each column. During filter evaluation, it checks whether a key is in one of the valid combinations or ranges. If not, it computes the next highest key to jump to.
Input to `SkipScanFilter` is a `List>` where the top-level list represents each row-key column (that is, each primary key part), and the inner list represents OR-ed byte-array boundaries.
Consider the following query:
```sql
SELECT * FROM T
WHERE ((KEY1 >='a' AND KEY1 <= 'b') OR (KEY1 > 'c' AND KEY1 <= 'e'))
AND KEY2 IN (1, 2)
```
For the query above, the `List>` passed to `SkipScanFilter` would look like:
```text
[[[a - b], [d - e]], [1, 2]]
```
Here, `[[a - b], [d - e]]` represents ranges for `KEY1`, and `[1, 2]` represents the keys for `KEY2`.
The following diagram illustrates graphically how the skip scan is able to jump around the key space:
# Statistics Collection (/docs/features/statistics-collection)
The `UPDATE STATISTICS` command updates the statistics collected on a table.
This command collects a set of keys per region per column family that
are equal byte distanced from each other. These collected keys are called *guideposts*
and they act as *hints/guides* to improve the parallelization of queries on a given
target region.
Statistics are also automatically collected during major compactions and region splits so
manually running this command may not be necessary.
## Parallelization
Phoenix breaks up queries into multiple scans and runs them in parallel to reduce latency.
Parallelization in Phoenix is driven by statistics-related configuration parameters.
Each chunk of data between guideposts will be run in parallel in a separate scan to improve
query performance. The chunk size is determined by the `GUIDE_POSTS_WIDTH` table property (Phoenix 4.9+)
or the global server-side `phoenix.stats.guidepost.width` parameter if the table property is
not set. As the size of the chunks decrease,
you'll want to increase `phoenix.query.queueSize` as more work will be queued in that
case. Note that at a minimum, separate scans will be run for each table region. Statistics in Phoenix
provides a means of gaining intraregion parallelization. In addition to the guidepost width specification,
the client-side `phoenix.query.threadPoolSize` and `phoenix.query.queueSize` parameters
and the server-side `hbase.regionserver.handler.count` parameter have an impact on the amount
of parallelization.
## Examples
To update the statistics for a given table `my_table`, execute the following command:
```sql
UPDATE STATISTICS my_table
```
The above syntax would collect the statistics for the table my\_table and all the index tables,
views and view index tables associated with the table my\_table.
The equivalent syntax is:
```sql
UPDATE STATISTICS my_table ALL
```
To collect statistics on the index table only:
```sql
UPDATE STATISTICS my_table INDEX
```
To collect statistics on the table only:
```sql
UPDATE STATISTICS my_table COLUMNS
```
To modify the guidepost width to 10MB for a table, execute the following command:
```sql
ALTER TABLE my_table SET GUIDE_POSTS_WIDTH = 10000000
```
To remove the guidepost width, set the property to null:
```sql
ALTER TABLE my_table SET GUIDE_POSTS_WIDTH = null
```
## Known issues
**Duplicated records** (SQL count shows more rows than HBase `row_count`) can occur in Phoenix versions earlier than **4.12**.
This may happen for tables with several regions where guideposts were not generated for the last region(s) because the region size is smaller than the guidepost width.
In that case, parallel scans for those regions may start with the latest guidepost instead of the region start key.
This was **fixed in 4.12** as part of [PHOENIX-4007](https://issues.apache.org/jira/browse/PHOENIX-4007).
## Configuration
The configuration parameters controlling statistics collection include:
1. `phoenix.stats.guidepost.width`
* A server-side parameter that specifies the number of bytes between guideposts.
A smaller amount increases parallelization, but also increases the number of
chunks which must be merged on the client side.
* The default value is 104857600 (100 MB).
2. `phoenix.stats.updateFrequency`
* A server-side parameter that determines the frequency in milliseconds for which statistics
will be refreshed from the statistics table and subsequently used by the client.
* The default value is 900000 (15 mins).
3. `phoenix.stats.minUpdateFrequency` - A client-side parameter that determines the minimum amount of time in milliseconds that
must pass before statistics may again be manually collected through another `UPDATE
STATISTICS` call. - The default value is `phoenix.stats.updateFrequency` divided by two (7.5 mins).
4. `phoenix.stats.useCurrentTime`
* An advanced server-side parameter that, if true, causes the current time on the server-side
to be used as the timestamp of rows in the statistics table when background tasks such as
compactions or splits occur. If false, then the max timestamp found while traversing the
table over which statistics are being collected is used as the timestamp. Unless your
client is controlling the timestamps while reading and writing data, this parameter
should be left alone.
* The default value is true.
5. `phoenix.use.stats.parallelization`
* This configuration is available starting in Phoenix 4.12. It controls whether statistical information
on the data should be used to drive query parallelization.
* The default value is true.
# Storage Formats (/docs/features/storage-formats)
As part of Phoenix 4.10, we have reduced on-disk storage size to improve overall performance by implementing the following enhancements:
* Introduce a layer of indirection between Phoenix column names and the corresponding HBase column qualifiers.
* Support a new encoding scheme for immutable tables that packs all values into a single cell per column family.
For more details on column mapping and immutable data encoding, see [this blog](https://blogs.apache.org/phoenix/entry/column-mapping-and-immutable-data).
## How to use column mapping
You can set the column mapping property only when creating a table. Before deciding to use column mapping, think about how many columns your table and view hierarchy will require over their lifecycle. The following limits apply for each mapping scheme:
| Config/Property Value | Max # of columns |
| --------------------- | ------------------------ |
| 1 | 255 |
| 2 | 65535 |
| 3 | 16777215 |
| 4 | 2147483647 |
| NONE | no limit (theoretically) |
For mutable tables, this limit applies to columns in **all** column families. For immutable tables, the limit applies **per** column family. By default, new Phoenix tables use column mapping. These defaults can be overridden by setting the following config value in `hbase-site.xml`.
| Table type | Default Column mapping | Config |
| ----------------- | ---------------------- | ------------------------------------------- |
| Mutable/Immutable | 2 byte qualifiers | phoenix.default.column.encoded.bytes.attrib |
This config controls global defaults that apply to all tables. If you want a different mapping scheme than the global default, use the `COLUMN_ENCODED_BYTES` table property.
```sql
CREATE TABLE T
(
a_string varchar not null,
col1 integer,
CONSTRAINT pk PRIMARY KEY (a_string)
)
COLUMN_ENCODED_BYTES = 1;
```
## How to use immutable data encoding
Like column mapping, immutable data encoding can only be set when creating a table. Through performance testing, `SINGLE_CELL_ARRAY_WITH_OFFSETS` generally provides strong performance and space savings. Below are some scenarios where `ONE_CELL_PER_COLUMN` encoding may be a better fit.
* Data is sparse, i.e. less than 50% of the columns have values.
* Size of data within a column family gets too big. With default HBase block size of 64K, if data within a column family grows beyond 50K then `SINGLE_CELL_ARRAY_WITH_OFFSETS` is generally not recommended.
* Immutable tables that are expected to have views on them.
By default, immutable non-multitenant tables are created using two-byte column mapping and `SINGLE_CELL_ARRAY_WITH_OFFSETS` data encoding. Immutable multi-tenant tables are created with two-byte column mapping and `ONE_CELL_PER_COLUMN` data encoding. This is because users often create tenant-specific views on base multi-tenant tables, and as noted above this is more suitable for `ONE_CELL_PER_COLUMN`. Like column mapping, you can change these global defaults by setting the following configs in `hbase-site.xml`.
| Immutable Table type | Immutable storage scheme | Config |
| -------------------- | ---------------------------------- | ---------------------------------------------------- |
| Multi-tenant | ONE\_CELL\_PER\_COLUMN | phoenix.default.multitenant.immutable.storage.scheme |
| Non multi-tenant | SINGLE\_CELL\_ARRAY\_WITH\_OFFSETS | phoenix.default.immutable.storage.scheme |
You can also provide specific immutable storage and column mapping schemes with the `IMMUTABLE_STORAGE_SCHEME` and `COLUMN_ENCODED_BYTES` table properties. For example:
```sql
CREATE IMMUTABLE TABLE T
(
a_string varchar not null,
col1 integer,
CONSTRAINT pk PRIMARY KEY (a_string)
)
IMMUTABLE_STORAGE_SCHEME = SINGLE_CELL_ARRAY_WITH_OFFSETS,
COLUMN_ENCODED_BYTES = 1;
```
You can choose not to use `SINGLE_CELL_ARRAY_WITH_OFFSETS` while still using numeric column mapping. For example:
```sql
CREATE IMMUTABLE TABLE T
(
a_string varchar not null,
col1 integer,
CONSTRAINT pk PRIMARY KEY (a_string)
)
IMMUTABLE_STORAGE_SCHEME = ONE_CELL_PER_COLUMN,
COLUMN_ENCODED_BYTES = 1;
```
When using `SINGLE_CELL_ARRAY_WITH_OFFSETS`, you must use a numeric column mapping scheme. Attempting to use `SINGLE_CELL_ARRAY_WITH_OFFSETS` with `COLUMN_ENCODED_BYTES = NONE` throws an error.
## How to disable column mapping
To disable column mapping across all new tables, set `phoenix.default.column.encoded.bytes.attrib` to `0`. You can also keep it enabled globally and disable it selectively for a table by setting `COLUMN_ENCODED_BYTES = 0` in the `CREATE TABLE` statement.
# Table Sampling (/docs/features/table-sampling)
To support table sampling (similar to PostgreSQL and T-SQL syntax), a `TABLESAMPLE` clause was incorporated into aliased table references as of Phoenix 4.12. The general syntax is described [here](/docs/grammar#aliased-table-ref). This feature limits the number of rows returned from a table to a percentage of rows. See [PHOENIX-153](https://issues.apache.org/jira/browse/PHOENIX-153) for implementation details.
This feature is implemented with a Bernoulli trial and a consistent-hashing-based table sampler to achieve Bernoulli sampling on a given row population. Given a sampling rate, it leverages Phoenix statistics and HBase region distribution to perform table sampling.
As part of [statistics collection](/docs/features/statistics-collection), a guidepost (a row reference) is created at equidistant byte intervals. When sampling is required, a Bernoulli trial process is applied repeatedly on each guidepost in each region with a probability proportional to the sampling rate. An included guidepost results in all rows between it and the next guidepost being included in the sample population.
## Performance
Sampling on a table with a sampling rate of 100% costs roughly the same computational resources as a query without sampling.
Resource consumption drops quickly as sampling rate decreases. In general, the amortized complexity of the sampling process is `O(k + mn)`, where:
* `n` is the number of regions in the sampled HBase table.
* `m` is the number of guideposts.
* `k` is the sampled population size.
## Repeatable
Repeatable means repeated sampling on the same table returns the same sampled result.
Repeatability is enabled by applying a consistent-hashing process to the binary representation of the start row key of each guidepost in each region during sampling. By default, an FNV1 implementation with lazy modulo is used. See [FNV1](http://www.isthe.com/chongo/tech/comp/fnv/).
## Examples
To sample a table, execute a query such as the following. The sampling rate is a numeric value between 0 and 100, inclusive.
```sql
SELECT * FROM PERSON TABLESAMPLE (12.08);
```
More examples:
```sql
SELECT * FROM PERSON TABLESAMPLE (12.08) WHERE ADDRESS = 'CA' OR NAME > 'aaa';
SELECT COUNT(*) FROM PERSON TABLESAMPLE (12.08) LIMIT 2;
SELECT COUNT(*) FROM (SELECT NAME FROM PERSON TABLESAMPLE (49) LIMIT 20);
SELECT * FROM (SELECT /*+ NO_INDEX */ * FROM PERSON TABLESAMPLE (10) WHERE NAME > 'tina10') WHERE ADDRESS = 'CA';
SELECT * FROM PERSON1, PERSON2 TABLESAMPLE (70) WHERE PERSON1.NAME = PERSON2.NAME;
SELECT /*+ NO_INDEX */ COUNT(*) FROM PERSON TABLESAMPLE (19), US_POPULATION TABLESAMPLE (28) WHERE PERSON.NAME > US_POPULATION.STATE;
UPSERT INTO PERSONBIG (ID, ADDRESS) SELECT ID, ADDRESS FROM PERSONBIG TABLESAMPLE (1);
```
To use with aggregation:
```sql
SELECT COUNT(*) FROM PERSON TABLESAMPLE (49) LIMIT 2;
SELECT COUNT(*) FROM (SELECT NAME FROM PERSON TABLESAMPLE (49) LIMIT 20);
```
To explain a sampled query:
```sql
EXPLAIN SELECT COUNT(*) FROM PERSON TABLESAMPLE (49) LIMIT 2;
```
## Tuning
* Due to the sampling process, `TABLESAMPLE` should be used with caution. For example, a join of two tables is likely to return a match for each row in both tables; however, when sampling is applied to one or both tables, join results may differ from non-sampled expectations.
* Statistics should be collected to achieve the best sampling accuracy. To turn on statistics collection, refer to [Statistics Collection](/docs/features/statistics-collection).
```sql
ALTER TABLE my_table SET GUIDE_POSTS_WIDTH = 10000000;
```
* A denser guidepost setting improves sampling accuracy, but may reduce performance. A comparison is shown below.
# Tracing (/docs/features/tracing)
As of Phoenix 4.1.0, Phoenix supports collecting per-request traces. This allows you to see each important step in a query or insertion, all the way from the client through HBase and back again.
Phoenix leverages Cloudera's [HTrace](https://github.com/cloudera/htrace) library to integrate with HBase tracing utilities. Trace metrics are then deposited into a Hadoop Metrics2 sink that writes them into a Phoenix table.
Writing traces to a Phoenix table is not supported on Hadoop 1.
## Configuration
There are two key configuration files that you will need to update.
* `hadoop-metrics2-phoenix.properties`
* `hadoop-metrics2-hbase.properties`
They contain the properties you need to set on the client and server, respectively, as well as information on how the Metrics2 system uses the configuration files.
Put these files on their respective classpaths and restart the process to pick up the new configurations.
### hadoop-metrics2-phoenix.properties
This file will configure the [Hadoop Metrics2](http://hadoop.apache.org/docs/current/api/index.html?org/apache/hadoop/metrics2/package-summary.html) system for *Phoenix clients*.
The default properties you should set are:
```properties
# Sample from all the sources every 10 seconds
*.period=10
# Write Traces to Phoenix
##########################
# ensure that we receive traces on the server
phoenix.sink.tracing.class=org.apache.phoenix.trace.PhoenixMetricsSink
# Tell the sink where to write the metrics
phoenix.sink.tracing.writer-class=org.apache.phoenix.trace.PhoenixTableMetricsWriter
# Only handle traces with a context of "tracing"
phoenix.sink.tracing.context=tracing
```
This enables standard Phoenix metrics sink (which collects the trace information) and writer (writes the traces to the Phoenix SYSTEM.TRACING\_STATS table). You can modify this to set your own custom classes as well, if you have them.
See the properties file in the source (`phoenix-hadoop2-compat/bin`) for more information on setting custom sinks and writers.
### hadoop-metrics2-hbase.properties
A default HBase deployment already includes a Metrics2 configuration, so Phoenix Metrics2 config can either replace the existing file (if you do not have custom settings) or be merged into your existing Metrics2 configuration file.
```properties
# ensure that we receive traces on the server
hbase.sink.tracing.class=org.apache.phoenix.trace.PhoenixMetricsSink
# Tell the sink where to write the metrics
hbase.sink.tracing.writer-class=org.apache.phoenix.trace.PhoenixTableMetricsWriter
# Only handle traces with a context of "tracing"
hbase.sink.tracing.context=tracing
```
These are essentially the same properties as in `hadoop-metrics2-phoenix.properties`, but prefixed with `hbase` instead of `phoenix` so they are loaded with the rest of HBase metrics.
### Disabling tracing
You can disable tracing for client requests by creating a new connection without the tracing property enabled (see below).
However, on the server side, once the metrics sink is enabled you cannot turn off trace collection and writing unless you **remove the Phoenix Metrics2 configuration and restart the region server**. This is enforced by the Metrics2 framework, which assumes server metrics should always be collected.
## Usage
There are only a couple small things you need to do to enable tracing a given request with Phoenix.
### Client Property
The frequency of tracing is determined by the following client-side Phoenix property:
```text
phoenix.trace.frequency
```
There are three possible tracing frequencies you can use:
1. `never`
* This is the default
2. `always`
* Every request will be traced
3. `probability`
* Take traces with a probabilistic frequency
* probability threshold is set by `phoenix.trace.probability.threshold` with a default of 0.05 (5%).
As with other configuration properties, this property may be specified at JDBC connection time as a connection property.
Enabling one of these properties only turns on trace collection. Trace data still needs to be deposited somewhere.
Example:
```java
# Enable tracing on every request
Properties props = new Properties();
props.setProperty("phoenix.trace.frequency", "always");
Connection conn = DriverManager.getConnection("jdbc:phoenix:localhost", props);
# Enable tracing on 50% of requests
props.setProperty("phoenix.trace.frequency", "probability");
props.setProperty("phoenix.trace.probability.threshold", "0.5");
Connection conn = DriverManager.getConnection("jdbc:phoenix:localhost", props);
```
#### hbase-site.xml
You can also enable tracing via `hbase-site.xml`. However, only `always` and `never` are currently supported.
```xml
phoenix.trace.frequencyalways
```
## Reading Traces
Once the traces are deposited into the tracing table, by default `SYSTEM.TRACING_STATS`, but it is configurable in the HBase configuration via:
```xml
phoenix.trace.statsTableNameYOUR_CUSTOM_TRACING_TABLE
```
The tracing table is initialized via the DDL:
```sql
CREATE TABLE SYSTEM.TRACING_STATS (
trace_id BIGINT NOT NULL,
parent_id BIGINT NOT NULL,
span_id BIGINT NOT NULL,
description VARCHAR,
start_time BIGINT,
end_time BIGINT,
hostname VARCHAR,
tags.count SMALLINT,
annotations.count SMALLINT,
CONSTRAINT pk PRIMARY KEY (trace_id, parent_id, span_id)
)
```
The tracing table also contains a number of dynamic columns for each trace. A trace is identified by trace ID (request ID), parent ID (parent span ID), and span ID (individual segment ID), and may have multiple tags and annotations. Once you know the number of tags and annotations, you can retrieve them from the table with a query like:
```sql
SELECT
FROM SYSTEM.TRACING_STATS
WHERE trace_id = ?
AND parent_id = ?
AND span_id = ?
```
Where `columns` is either `annotations.aX` or `tags.tX`, where `X` is the index of the dynamic column to look up.
For more usage examples, see [TraceReader](https://github.com/apache/phoenix/blob/master/phoenix-core/src/main/java/org/apache/phoenix/trace/TraceReader.java), which can programmatically read traces from the tracing results table.
Custom annotations can also be passed into Phoenix to be added to traces. Phoenix looks for connection properties whose names start with `phoenix.annotation.` and adds them as annotations to client-side traces. For example, a connection property `phoenix.annotation.myannotation=abc` results in an annotation with key `myannotation` and value `abc`. Use this to link traces to other request identifiers in your system, such as user or session IDs.
## Phoenix Tracing Web Application
### How to start the tracing web application
1. Enable tracing for Apache Phoenix as above
2. Start the web app:
```shell
./bin/traceserver.py start
```
3. Open this URL in your browser: [http://localhost:8864/webapp/](http://localhost:8864/webapp/)
4. Stop the tracing web app:
```shell
./bin/traceserver.py stop
```
### Changing the web app port number
Execute the command below:
```shell
-Dphoenix.traceserver.http.port=8887
```
## Feature list
The tracing web app for Apache Phoenix includes: feature list, dependency tree, trace count, trace distribution, and timeline.
### List
The most recent traces are listed down. The limiting value entered on the textbox is used to determine the trace count displayed. With each trace, there is a link to view either the dependency tree or the timeline.
### Dependency tree
The dependency tree shows traces for a given trace ID in a tree view. Parent-child relationships are displayed clearly. Tooltip data includes host name, parent ID, span ID, start time, end time, description, and duration. Each node is collapsible and expandable. The SQL query is shown for each tree rendering. Clear is used to remove the tree from view.
### Trace count
The trace list is categorized by description. The trace count chart can be viewed as pie, line, bar, or area chart. The chart selector is collapsible and can be hidden.
### Trace distribution
The trace distribution chart shows traces across Phoenix hosts on which they are running. Chart types include pie, line, bar, and area. The chart selector is collapsible and can be hidden.
### Timeline
The traces can be viewed along the time axis for a given trace id. Traces can be added or cleared from the timeline. There should be a minimum of two traces starting at two different times for the system to draw its timeline. This feature helps the user to easily compare execution times between traces and within the same trace.
# Transactions (/docs/features/transactions)
**Transactions (beta).** Above and beyond the row-level transactional semantics of HBase, Phoenix adds cross row and cross table transaction support with full [ACID](https://en.wikipedia.org/wiki/ACID) semantics by integrating with [Tephra](http://tephra.io/), now an Apache incubator project. Tephra provides snapshot isolation of concurrent transactions by implementing multi-versioned concurrency control.
Set up a system to use transactions in Phoenix with the following steps:
Add the following config to your client-side `hbase-site.xml` file to enable transactions:
```xml
phoenix.transactions.enabledtrue
```
Add the following config to your server-side `hbase-site.xml` file to configure the transaction manager.
The "Transaction Server Configuration" section of [Tephra](https://github.com/caskdata/tephra) describes the available configuration options.
```xml
data.tx.snapshot.dir/tmp/tephra/snapshots
```
Also set the transaction timeout (time after which open transactions become invalid) to a reasonable value:
```xml
data.tx.timeout60
```
Set `$HBASE_HOME` and start the transaction manager:
```shell
./bin/tephra
```
The transaction manager would typically be configured to run on one or more of the master nodes in your HBase cluster.
Once this setup is done, transactions may then be enabled on a table by table basis by using the `TRANSACTIONAL=true` property when you create your table:
```sql
CREATE TABLE my_table (k BIGINT PRIMARY KEY, v VARCHAR) TRANSACTIONAL=true;
```
An existing table may also be altered to be transactional, **but be careful because you cannot switch a transactional table back to being non transactional**:
```sql
ALTER TABLE my_other_table SET TRANSACTIONAL=true;
```
A transaction is started implicitly through the execution of a statement on a transactional table and then finished through either a commit or rollback. Once started, the statements will not see any data committed by other transactions until the transaction is complete. They will, however, see their own uncommitted data. For example:
```sql
SELECT * FROM my_table; -- This will start a transaction
UPSERT INTO my_table VALUES (1,'A');
SELECT count(*) FROM my_table WHERE k=1; -- Will see uncommitted row
DELETE FROM my_other_table WHERE k=2;
!commit -- Other transactions will now see your updates and you will see theirs
```
An exception is thrown if a transaction tries to commit a row that conflicts with other overlapping transaction that already committed. For example:
```sql
UPSERT INTO my_table VALUES (1,'A');
```
In a second transaction perform a commit for the same row.
```sql
UPSERT INTO my_table VALUES (1,'B');
!commit
```
Now if you try to commit the first transaction you will get an exception
```text
java.sql.SQLException: ERROR 523 (42900): Transaction aborted due to conflict with other mutations. Conflict detected for transaction 1454112544975000000.
```
Queries are only able to view commits that completed before the current transaction started and are not able to view the in progress changes of other transactions.
Indexes added to a transactional table are transactional as well with regard to their incremental maintenance. For example, the following index added to my\_table will be kept transactional consistent with its data table as mutations are made:
```sql
CREATE INDEX my_table (k BIGINT PRIMARY KEY, v VARCHAR) TRANSACTIONAL=true;
```
During a commit, if either the index or data table write fails, an exception is thrown and the client can either roll back or retry.
If the commit fails both the index and data table rows are not visible.
An external Tephra transaction that has already been started can be used with Phoenix by setting the transaction context of the Phoenix connection:
```java
setTransactionContext(TransactionContext txContext)
```
## Limitations
1. Starting a transaction on a connection with an SCN set is not allowed.
2. Setting the maximum number of versions property while creating a transactional table limits the number of snapshots available for concurrent transactions.
3. When a transaction times out or if it cannot be rolled back by the client, it is added to an invalid list. This list can potentially grow if there are a lot of failed or timed out transactions.
For now, an administrator can manually clear transactions from this list after a major compaction has occurred. [TEPHRA-35](https://issues.cask.co/browse/TEPHRA-35) describes ongoing work to automatically remove transactions from the invalid list once all data associated with the transaction has been removed.
4. If adding an index asynchronously to an existing transactional table, make sure to run a major compaction before issuing the CREATE INDEX ASYNC command as otherwise invalid and/or uncommitted transactions may appear in your index [PHOENIX-2154](https://issues.apache.org/jira/browse/PHOENIX-2154).
# User-defined Functions (/docs/features/user-defined-functions)
As of Phoenix 4.4.0 we have added the ability to allow users to create and deploy
their own custom or domain-specific UDFs to the cluster.
## Overview
Users can create temporary or permanent user-defined (domain-specific) scalar functions.
UDFs can be used like built-in functions in queries such as `SELECT`, `UPSERT`, `DELETE`, and when creating functional indexes.
Temporary functions are session-scoped and are not accessible from other sessions.
Permanent function metadata is stored in the `SYSTEM.FUNCTION` table.
Phoenix also supports tenant-specific functions. Functions created in one tenant-specific connection are not visible to other tenant-specific connections.
Only global tenant (no-tenant) functions are visible to all connections.
Phoenix leverages the HBase dynamic class loader to load UDF JARs from HDFS at the Phoenix client and region server without restarting services.
## Configuration
Add the following parameters to `hbase-site.xml` on the Phoenix client:
```xml
phoenix.functions.allowUserDefinedFunctionstruefs.hdfs.implorg.apache.hadoop.hdfs.DistributedFileSystemhbase.rootdir${hbase.tmp.dir}/hbaseThe directory shared by region servers and into
which HBase persists. The URL should be 'fully-qualified'
to include the filesystem scheme. For example, to specify the
HDFS directory '/hbase' where the HDFS instance's namenode is
running at namenode.example.org on port 9000, set this value to:
hdfs://namenode.example.org:9000/hbase. By default, we write
to whatever ${hbase.tmp.dir} is set too -- usually /tmp --
so change this configuration or else all data will be lost on
machine restart.hbase.dynamic.jars.dir${hbase.rootdir}/lib
The directory from which the custom udf jars can be loaded
dynamically by the phoenix client/region server without the need to restart. However,
an already loaded udf class would not be un-loaded. See
HBASE-1936 for more details.
```
**The last two configuration values should match the HBase server-side configuration.**
As with other configuration properties, `phoenix.functions.allowUserDefinedFunctions`
may be specified at JDBC connection time as a connection property.
Example:
```java
Properties props = new Properties();
props.setProperty("phoenix.functions.allowUserDefinedFunctions", "true");
Connection conn = DriverManager.getConnection("jdbc:phoenix:localhost", props);
```
The following optional parameter is used by the dynamic class loader to copy JARs from HDFS into the local filesystem:
```xml
hbase.local.dir${hbase.tmp.dir}/local/Directory on the local filesystem to be used
as a local storage.
```
## Creating Custom UDFs
Implement your custom UDF by following [How to write custom
UDF](#how-to-write-custom-udf).
Compile your code into a JAR, then deploy the JAR to HDFS. It is recommended
to add the JAR to the HDFS directory configured by `hbase.dynamic.jars.dir`.
Run the
[`CREATE FUNCTION`](/docs/grammar#create-function)
query.
## Dropping the UDFs
You can drop functions using the [`DROP FUNCTION`](/docs/grammar#drop-function) query.
Dropping a function deletes the metadata for that function from Phoenix.
## How to write custom UDF
You can follow these steps to write your UDF (for more detail, see [this blog post](http://phoenix-hbase.blogspot.in/2013/04/how-to-add-your-own-built-in-function.html)):
* Create a new class derived from `org.apache.phoenix.expression.function.ScalarFunction`.
* Implement `getDataType()` to determine the function return type.
* Implement `evaluate()` to calculate the result for each row.
The method receives `org.apache.phoenix.schema.tuple.Tuple` with the current row state and an `org.apache.hadoop.hbase.io.ImmutableBytesWritable` to populate with the function result.
The method returns `false` if there is not enough information to calculate the result (usually because one argument is unknown), and `true` otherwise.
Below are additional optimization-related steps.
* To contribute to scan start/stop key formation, custom functions need to override the following two methods from `ScalarFunction`:
```java
/**
* Determines whether or not a function may be used to form
* the start/stop key of a scan
* @return the zero-based position of the argument to traverse
* into to look for a primary key column reference, or
* {@value #NO_TRAVERSAL} if the function cannot be used to
* form the scan key.
*/
public int getKeyFormationTraversalIndex() {
return NO_TRAVERSAL;
}
/**
* Manufactures a KeyPart used to construct the KeyRange given
* a constant and a comparison operator.
* @param childPart the KeyPart formulated for the child expression
* at the {@link #getKeyFormationTraversalIndex()} position.
* @return the KeyPart for constructing the KeyRange for this
* function.
*/
public KeyPart newKeyPart(KeyPart childPart) {
return null;
}
```
* Additionally, to enable `ORDER BY` optimization or in-place `GROUP BY`, override:
```java
/**
* Determines whether or not the result of the function invocation
* will be ordered in the same way as the input to the function.
* Returning YES enables an optimization to occur when a
* GROUP BY contains function invocations using the leading PK
* column(s).
* @return YES if the function invocation will always preserve order for
* the inputs versus the outputs and false otherwise, YES_IF_LAST if the
* function preserves order, but any further column reference would not
* continue to preserve order, and NO if the function does not preserve
* order.
*/
public OrderPreserving preservesOrder() {
return OrderPreserving.NO;
}
```
## Limitations
* The JAR containing UDFs must be manually added to and deleted from HDFS. There is ongoing work to add SQL statements for JAR add/remove ([PHOENIX-1890](https://issues.apache.org/jira/browse/PHOENIX-1890)).
* The dynamic class loader copies UDF JARs to `{hbase.local.dir}/jars` at the Phoenix client and region server when a UDF is used in queries. These JARs must be deleted manually when a function is deleted.
* Functional indexes must be rebuilt manually if the function implementation changes ([PHOENIX-1907](https://issues.apache.org/jira/browse/PHOENIX-1907)).
* Once loaded, a JAR is not unloaded. Use a different JAR for modified implementations to avoid restarting the cluster ([PHOENIX-1907](https://issues.apache.org/jira/browse/PHOENIX-1907)).
* To list functions, query the `SYSTEM."FUNCTION"` table ([PHOENIX-1921](https://issues.apache.org/jira/browse/PHOENIX-1921)).
# Views (/docs/features/views)
The standard SQL view syntax (with some limitations) is now supported by Phoenix to enable multiple virtual tables to all share the same underlying physical HBase table. This is important in HBase as there are limits to the number of regions which HBase can manage. Limiting the number of tables can help limit the number of regions in a cluster.
For example, given the following table definition that defines a base table to collect product metrics:
```sql
CREATE TABLE product_metrics (
metric_type CHAR(1),
created_by VARCHAR,
created_date DATE,
metric_id INTEGER,
CONSTRAINT pk PRIMARY KEY (metric_type, created_by, created_date, metric_id)
);
```
You may define the following view:
```sql
CREATE VIEW mobile_product_metrics (carrier VARCHAR, dropped_calls BIGINT) AS
SELECT * FROM product_metrics
WHERE metric_type = 'm';
```
In this case, the same underlying physical HBase table (i.e. PRODUCT\_METRICS) stores all of the data.
Notice that unlike with standard SQL views, you may define additional columns for your view. The view inherits all of the columns from its base table, in addition to being able to optionally add new KeyValue columns. You may also add these columns after-the-fact with an ALTER VIEW statement.
**NOTE**: Phoenix 4.15.0 onwards contains [PHOENIX-4810](https://issues.apache.org/jira/browse/PHOENIX-4810) which introduces a new endpoint coprocessor on the SYSTEM.CHILD\_LINK table for adding parent->child links, whenever a view is created. Thus, when namespace mapping is enabled, users that wish to create views will need to be granted EXEC permissions on SYSTEM.CHILD\_LINK in order to be able to invoke this coprocessor.
## Updatable Views
If your view uses only simple equality expressions in the WHERE clause, you are also allowed to issue DML against the view. These views are termed *updatable views*. For example, in this case you could issue the following UPSERT statement:
```sql
UPSERT INTO mobile_product_metrics(created_by, created_date, metric_id, carrier, dropped_calls)
VALUES('John Doe', CURRENT_DATE(), NEXT VALUE FOR metric_seq, 'Verizon', 20);
```
In this case, the row will be stored in the PRODUCT\_METRICS HBase table and the metric\_type column value will be inferred to be 'm' since the VIEW defines it as such.
Also, queries done through the view will automatically apply the WHERE clause filter. For example:
```sql
SELECT SUM(dropped_calls) FROM mobile_product_metrics WHERE carrier = 'Verizon';
```
This would sum all the dropped\_calls across all product\_metrics with a metric\_type of 'm' and a carrier of 'Verizon'.
## Read-only Views
Views may also be defined with more complex WHERE clauses, but in that case you cannot issue DML against them as you'll get a ReadOnlyException. You are still allowed to query through them and their WHERE clauses will be in effect as with standard SQL views.
As expected, you may create a VIEW on another VIEW as well to further filter the data set. The same rules as above apply: if only simple equality expressions are used in the VIEW and its parent VIEW(s), the new view is updatable as well, otherwise it's read-only.
Note that the previous support for creating a read-only VIEW directly over an HBase table is still supported.
## Indexes on Views
In addition, you may create an INDEX over a VIEW, just as with a TABLE. This is particularly useful to improve query performance over newly added columns on a VIEW, since it provides a way of doing point lookups based on these column values. Note that until [PHOENIX-1499](https://issues.apache.org/jira/browse/PHOENIX-1499) gets implemented, an INDEX over a VIEW is only maintained if the updates are made through the VIEW (as opposed to through the underlying TABLE).
## Limitations
Views have the following restrictions:
1. An INDEX over a VIEW is only maintained if the updates are made through the VIEW. Updates made through the underlying TABLE or the parent VIEW will not be reflected in the index ([PHOENIX-1499](https://issues.apache.org/jira/browse/PHOENIX-1499)).
2. A primary key column may not be added to a VIEW when its base table has a primary key constraint that ends with a variable-length column ([PHOENIX-2157](https://issues.apache.org/jira/browse/PHOENIX-2157)).
3. A VIEW may be defined over only a single table through a simple `SELECT *` query. You may not create a VIEW over multiple joined tables or over aggregations ([PHOENIX-1505](https://issues.apache.org/jira/browse/PHOENIX-1505), [PHOENIX-1506](https://issues.apache.org/jira/browse/PHOENIX-1506)).
4. When a column is added to a VIEW, the new column is not automatically added to child VIEWs ([PHOENIX-2054](https://issues.apache.org/jira/browse/PHOENIX-2054)). The workaround is to manually add the column to child VIEWs.
5. All columns must be projected into a VIEW when it is created (that is, only `CREATE VIEW ... AS SELECT *` is supported). You may drop non-primary-key columns inherited from the base table after creation using `ALTER VIEW`. Providing a subset of columns or expressions in the `SELECT` clause is planned for a future release ([PHOENIX-1507](https://issues.apache.org/jira/browse/PHOENIX-1507)).
# SQL Functions (/docs/functions)
export const functionsTopicIndex = {
"select": "select",
"upsert values": "upsert-values",
"upsert select": "upsert-select",
"delete": "delete",
"declare cursor": "declare-cursor",
"open cursor": "open-cursor",
"fetch next": "fetch-next",
"close": "close",
"create table": "create-table",
"drop table": "drop-table",
"create function": "create-function",
"drop function": "drop-function",
"create view": "create-view",
"drop view": "drop-view",
"create sequence": "create-sequence",
"drop sequence": "drop-sequence",
"alter": "alter",
"create index": "create-index",
"drop index": "drop-index",
"alter index": "alter-index",
"explain": "explain",
"constraint": "constraint",
"update statistics": "update-statistics",
"create schema": "create-schema",
"use": "use",
"drop schema": "drop-schema",
"grant": "grant",
"revoke": "revoke",
"options": "options",
"hint": "hint",
"scan hint": "scan-hint",
"cache hint": "cache-hint",
"index hint": "index-hint",
"small hint": "small-hint",
"seek to column hint": "seek-to-column-hint",
"join hint": "join-hint",
"serial hint": "serial-hint",
"column def": "column-def",
"table ref": "table-ref",
"sequence ref": "sequence-ref",
"column ref": "column-ref",
"select expression": "select-expression",
"select statement": "select-statement",
"split point": "split-point",
"table spec": "table-spec",
"aliased table ref": "aliased-table-ref",
"join type": "join-type",
"join": "join-type",
"func argument": "func-argument",
"class name": "class-name",
"jar path": "jar-path",
"order": "order",
"expression": "expression",
"and condition": "and-condition",
"boolean condition": "boolean-condition",
"condition": "condition",
"rhs operand": "rhs-operand",
"operand": "operand",
"summand": "summand",
"factor": "factor",
"term": "term",
"array constructor": "array-constructor",
"sequence": "sequence",
"cast": "cast",
"row value constructor": "row-value-constructor",
"bind parameter": "bind-parameter",
"value": "value",
"case": "case",
"case when": "case-when",
"name": "name",
"quoted name": "quoted-name",
"alias": "alias",
"null": "null",
"data type": "data-type",
"data": "data-type",
"sql data type": "sql-data-type",
"sql data": "sql-data-type",
"hbase data type": "hbase-data-type",
"hbase data": "hbase-data-type",
"string": "string",
"boolean": "boolean-type",
"numeric": "numeric",
"int": "int",
"long": "long",
"decimal": "decimal-type",
"number": "number",
"comments": "comments",
"integer type": "integer-type",
"integer": "integer-type",
"unsigned_int type": "unsigned-int-type",
"unsigned_int": "unsigned-int-type",
"bigint type": "bigint-type",
"bigint": "bigint-type",
"unsigned_long type": "unsigned-long-type",
"unsigned_long": "unsigned-long-type",
"tinyint type": "tinyint-type",
"tinyint": "tinyint-type",
"unsigned_tinyint type": "unsigned-tinyint-type",
"unsigned_tinyint": "unsigned-tinyint-type",
"smallint type": "smallint-type",
"smallint": "smallint-type",
"unsigned_smallint type": "unsigned-smallint-type",
"unsigned_smallint": "unsigned-smallint-type",
"float type": "float-type",
"float": "float-type",
"unsigned_float type": "unsigned-float-type",
"unsigned_float": "unsigned-float-type",
"double type": "double-type",
"double": "double-type",
"unsigned_double type": "unsigned-double-type",
"unsigned_double": "unsigned-double-type",
"decimal type": "decimal-type",
"boolean type": "boolean-type",
"time type": "time-type",
"time": "time-type",
"date type": "date-type",
"date": "date-type",
"timestamp type": "timestamp-type",
"timestamp": "timestamp-type",
"unsigned_time type": "unsigned-time-type",
"unsigned_time": "unsigned-time-type",
"unsigned_date type": "unsigned-date-type",
"unsigned_date": "unsigned-date-type",
"unsigned_timestamp type": "unsigned-timestamp-type",
"unsigned_timestamp": "unsigned-timestamp-type",
"varchar type": "varchar-type",
"varchar": "varchar-type",
"char type": "char-type",
"char": "char-type",
"binary type": "binary-type",
"binary": "binary-type",
"varbinary type": "varbinary-type",
"varbinary": "varbinary-type",
"array": "array",
"avg": "avg",
"count": "count",
"approx_count_distinct": "approx-count-distinct",
"max": "max",
"min": "min",
"sum": "sum",
"percentile_cont": "percentile-cont",
"percentile_disc": "percentile-disc",
"percent_rank": "percent-rank",
"first_value": "first-value",
"last_value": "last-value",
"first_values": "first-values",
"last_values": "last-values",
"nth_value": "nth-value",
"stddev_pop": "stddev-pop",
"stddev_samp": "stddev-samp",
"round": "round",
"ceil": "ceil",
"floor": "floor",
"trunc": "trunc",
"substr": "substr",
"instr": "instr",
"trim": "trim",
"ltrim": "ltrim",
"rtrim": "rtrim",
"lpad": "lpad",
"array_elem": "array-elem",
"array_length": "array-length",
"array_append": "array-append",
"array_prepend": "array-prepend",
"array_cat": "array-cat",
"array_fill": "array-fill",
"array_to_string": "array-to-string",
"any": "any",
"all": "all",
"length": "length",
"regexp_substr": "regexp-substr",
"regexp_replace": "regexp-replace",
"regexp_split": "regexp-split",
"md5": "md5",
"invert": "invert",
"encode": "encode",
"decode": "decode",
"to_number": "to-number",
"rand": "rand",
"upper": "upper",
"lower": "lower",
"reverse": "reverse",
"to_char": "to-char",
"to_date": "to-date",
"current_date": "current-date",
"to_time": "to-time",
"to_timestamp": "to-timestamp",
"current_time": "current-time",
"convert_tz": "convert-tz",
"timezone_offset": "timezone-offset",
"now": "now",
"year": "year",
"month": "month",
"week": "week",
"dayofyear": "dayofyear",
"dayofmonth": "dayofmonth",
"dayofweek": "dayofweek",
"hour": "hour",
"minute": "minute",
"second": "second",
"coalesce": "coalesce",
"sign": "sign",
"abs": "abs",
"sqrt": "sqrt",
"cbrt": "cbrt",
"exp": "exp",
"power": "power",
"ln": "ln",
"log": "log",
"get_bit": "get-bit",
"get_byte": "get-byte",
"octet_length": "octet-length",
"set_bit": "set-bit",
"set_byte": "set-byte",
"collation_key": "collation-key"
}
## Functions (Aggregate)
### AVG
The average (mean) value. If no rows are selected, the result is NULL. Aggregates are only allowed in select statements. The returned value is of the same data type as the parameter.
**Example**
```sql
AVG(X)
```
### COUNT
The count of all row, or of the non-null values. This method returns a `long`. When DISTINCT is used, it counts only distinct values. If no rows are selected, the result is 0. Aggregates are only allowed in select statements.
**Example**
```sql
COUNT(*)
```
### APPROX\_COUNT\_DISTINCT
The approximate distinct count of all row, or of the non-null values. The relative error of approximation by default is less than 0.00405 This method returns a `long`. If no rows are selected, the result is 0. Aggregates are only allowed in select statements.
**Example**
```sql
APPROX_COUNT_DISTINCT(*)
```
### MAX
The highest value. If no rows are selected, the result is NULL. Aggregates are only allowed in select statements. The returned value is of the same data type as the parameter.
**Example**
```sql
MAX(NAME)
```
### MIN
The lowest value. If no rows are selected, the result is NULL. Aggregates are only allowed in select statements. The returned value is of the same data type as the parameter.
**Example**
```sql
MIN(NAME)
```
### SUM
The sum of all values. If no rows are selected, the result is NULL. Aggregates are only allowed in select statements. The returned value is of the same data type as the parameter.
**Example**
```sql
SUM(X)
```
### PERCENTILE\_CONT
The nth percentile of values in the column. The percentile value can be between 0 and 1 inclusive. Aggregates are only allowed in select statements. The returned value is of `decimal` data type.
**Example**
```sql
PERCENTILE_CONT( 0.9 ) WITHIN GROUP (ORDER BY X ASC)
```
### PERCENTILE\_DISC
PERCENTILE\_DISC is an inverse distribution function that assumes a discrete distribution model. It takes a percentile value and a sort specification and returns an element from the set. Nulls are ignored in the calculation.
**Example**
```sql
PERCENTILE_DISC( 0.9 ) WITHIN GROUP (ORDER BY X DESC)
```
### PERCENT\_RANK
The percentile rank for a hypothetical value, if inserted into the column. Aggregates are only allowed in select statements. The returned value is of `decimal` data type.
**Example**
```sql
PERCENT_RANK( 100 ) WITHIN GROUP (ORDER BY X ASC)
```
### FIRST\_VALUE
The first value in each distinct group ordered according to the ORDER BY specification.
**Example**
```sql
FIRST_VALUE( name ) WITHIN GROUP (ORDER BY salary DESC)
```
### LAST\_VALUE
The last value in each distinct group ordered according to the ORDER BY specification.
**Example**
```sql
LAST_VALUE( name ) WITHIN GROUP (ORDER BY salary DESC)
```
### FIRST\_VALUES
Returns an array of at most the given numeric size of the first values in each distinct group ordered according to the ORDER BY specification.
**Example**
```sql
FIRST_VALUES( name, 3 ) WITHIN GROUP (ORDER BY salary DESC)
```
### LAST\_VALUES
Returns an array of at most the given numeric size of the last values in each distinct group ordered according to the ORDER BY specification.
**Example**
```sql
LAST_VALUES( name, 3 ) WITHIN GROUP (ORDER BY salary DESC)
```
### NTH\_VALUE
The nth value in each distinct group ordered according to the ORDER BY specification.
**Example**
```sql
NTH_VALUE( name, 2 ) WITHIN GROUP (ORDER BY salary DESC)
```
### STDDEV\_POP
The population standard deviation of all values. Aggregates are only allowed in select statements. The returned value is of `decimal` data type.
**Example**
```sql
STDDEV_POP( X )
```
### STDDEV\_SAMP
The sample standard deviation of all values. Aggregates are only allowed in select statements. The returned value is of `decimal` data type.
**Example**
```sql
STDDEV_SAMP( X )
```
## Functions (Numeric)
### ROUND
Rounds the numeric or timestamp expression to the nearest scale or time unit specified. If the expression is a numeric type, then the second argument is the scale to be used for rounding off the number, defaulting to zero. If the expression is a date/time type, then the second argument may be one of the time units listed to determine the remaining precision of the date/time. A default of MILLISECONDS is used if not present. The multiplier is only applicable for a date/time type and is used to round to a multiple of a time unit (i.e. 10 minute) and defaults to 1 if not specified. This method returns the same type as its first argument.
**Example**
```sql
ROUND(number)
ROUND(number, 2)
ROUND(timestamp)
ROUND(time, 'HOUR')
ROUND(date, 'MINUTE', 30)
```
### CEIL
Same as ROUND, except it rounds any fractional value up to the next even multiple.
**Example**
```sql
CEIL(number, 3)
CEIL(2.34)
CEIL(timestamp, 'SECOND', 30)
CEIL(date, 'DAY', 7)
```
### FLOOR
Same as ROUND, except it rounds any fractional value down to the previous even multiple.
**Example**
```sql
FLOOR(timestamp)
FLOOR(date, 'DAY', 7)
```
### TRUNC
Same as FLOOR
**Example**
```sql
TRUNC(timestamp, 'SECOND', 30)
TRUNC(date, 'DAY', 7)
```
### TO\_NUMBER
Formats a string or date/time type as a number, optionally accepting a format string. For details on the format, see `java.text.DecimalFormat`. For date, time, and timeStamp terms, the result is the time in milliseconds since the epoch. This method returns a `decimal` number.
**Example**
```sql
TO_NUMBER('$123.33', '\u00A4###.##')
```
### RAND
Function that produces a random, uniformly distributed double value between 0.0 (inclusive) and 1.0 (exclusive). If a seed is provided, then the the returned value is identical across each invocation for the same row. If a seed is not provided, then the returned value is different for each invocation. The seed must be a constant.
**Example**
```sql
RAND()
RAND(5)
```
## Functions (String)
### SUBSTR
Returns a substring of a string starting at the one-based position. If zero is used, the position is zero-based. If the start index is negative, then the start index is relative to the end of the string. The length is optional and if not supplied, the rest of the string will be returned.
**Example**
```sql
SUBSTR('[Hello]', 2, 5)
SUBSTR('Hello World', -5)
```
### INSTR
Returns the one-based position of the initial occurrence of the second argument in the first argument. If the second argument is not contained in the first argument, then zero is returned.
**Example**
```sql
INSTR('Hello World', 'World')
INSTR('Simon says', 'mon')
INSTR('Peace on earth', 'war')
```
### TRIM
Removes leading and trailing spaces from the input string.
**Example**
```sql
TRIM(' Hello ')
```
### LTRIM
Removes leading spaces from the input string.
**Example**
```sql
LTRIM(' Hello')
```
### RTRIM
Removes trailing spaces from the input string.
**Example**
```sql
RTRIM('Hello ')
```
### LPAD
Pads the string expression with the specific pad character (space by default) up to the length argument.
**Example**
```sql
LPAD('John',30)
```
### LENGTH
Returns the length of the string in characters.
**Example**
```sql
LENGTH('Hello')
```
### REGEXP\_SUBSTR
Returns a substring of a string by applying a regular expression start from the offset of a one-based position. Just like with SUBSTR, if the start index is negative, then it is relative to the end of the string. If not specified, the start index defaults to 1.
**Example**
```sql
REGEXP_SUBSTR('na1-appsrv35-sj35', '[^-]+') evaluates to 'na1'
```
### REGEXP\_REPLACE
Returns a string by applying a regular expression and replacing the matches with the replacement string. If the replacement string is not specified, it defaults to an empty string.
**Example**
```sql
REGEXP_REPLACE('abc123ABC', '[0-9]+', '#') evaluates to 'abc#ABC'
```
### REGEXP\_SPLIT
Splits a string into a `VARCHAR ARRAY` using a regular expression. If characters that have a special meaning in regular expressions are to be used as a regular delimiter in the pattern string, they must be escaped with backslashes.
**Example**
```sql
REGEXP_SPLIT('ONE,TWO,THREE', ',') evaluates to ARRAY['ONE', 'TWO', 'THREE']
REGEXP_SPLIT('ONE!#TWO#,!THREE', '[,!#]+') evaluates to ARRAY['ONE', 'TWO', 'THREE']
```
### UPPER
Returns upper case string of the string argument. If localeString (available in Phoenix 4.14) is provided, it identifies the locale whose rules are used for the conversion. If localeString is not provided, the default locale is used. The localeString must be of the form returned by the Java 6 implementation of `java.util.Locale.toString()` e.g. 'zh\_TW\_STROKE' or 'en\_US' or 'fr\_FR'.
**Example**
```sql
UPPER('Hello')
UPPER('Hello', 'tr_TR')
```
### LOWER
Returns lower case string of the string argument. If localeString (available in Phoenix 4.14) is provided, it identifies the locale whose rules are used for the conversion. If localeString is not provided, the default locale is used. The localeString must be of the form returned by the Java 6 implementation of `java.util.Locale.toString()` e.g. 'zh\_TW\_STROKE' or 'en\_US' or 'fr\_FR'.
**Example**
```sql
LOWER('HELLO')
LOWER('HELLO', 'en_US')
```
### REVERSE
Returns reversed string of the string argument.
**Example**
```sql
REVERSE('Hello')
```
### TO\_CHAR
Formats a date, time, timestamp, or number as a string. The default date format is "yyyy-MM-dd HH:mm:ss" and the default number format is "#,##0.###". For details, see `java.text.SimpleDateFormat` for date/time values and `java.text.DecimalFormat` for numbers. This method returns a string.
**Example**
```sql
TO_CHAR(myDate, '2001-02-03 04:05:06')
TO_CHAR(myDecimal, '#,##0.###')
```
### COLLATION\_KEY
Calculates a collation key that can be used to sort strings in a natural-language-aware way. The localeString must be of the form returned by the Java 6 implementation of `java.util.Locale.toString()` e.g. 'zh\_TW\_STROKE' or 'en\_US' or 'fr\_FR'. The third, fourth and fifth arguments are optional and determine respectively whether to use a special upper-case collator, the strength value of the collator, and the decomposition value of the collator. (See `java.text.Collator` to learn about strength and decomposition).
**Example**
```sql
SELECT NAME FROM EMPLOYEE ORDER BY COLLATION_KEY(NAME, 'zh_TW')
```
## Functions (Array)
### ARRAY\_ELEM
Alternative to using array subscript notation to access an array element. Returns the element in the array at the given position. The position is one-based.
**Example**
```sql
ARRAY_ELEM(my_array_col, 5)
ARRAY_ELEM(ARRAY[1,2,3], 1)
```
### ARRAY\_LENGTH
Returns the current length of the array.
**Example**
```sql
ARRAY_LENGTH(my_array_col)
ARRAY_LENGTH(ARRAY[1,2,3])
```
### ARRAY\_APPEND
Appends the given element to the end of the array.
**Example**
```sql
ARRAY_APPEND(my_array_col, my_element_col)
ARRAY_APPEND(ARRAY[1,2,3], 4) evaluates to ARRAY[1,2,3,4]
```
### ARRAY\_PREPEND
Appends the given element to the beginning of the array.
**Example**
```sql
ARRAY_PREPEND(my_element_col, my_array_col)
ARRAY_PREPEND(0, ARRAY[1,2,3]) evaluates to ARRAY[0,1,2,3]
```
### ARRAY\_CAT
Concatenates the input arrays and returns the result.
**Example**
```sql
ARRAY_CAT(my_array_col1, my_array_col2)
ARRAY_CAT(ARRAY[1,2], ARRAY[3,4]) evaluates to ARRAY[1,2,3,4]
```
### ARRAY\_FILL
Returns an array initialized with supplied value and length.
**Example**
```sql
ARRAY_FILL(my_element_col, my_length_col)
ARRAY_FILL(1, 3) evaluates to ARRAY[1,1,1]
```
### ARRAY\_TO\_STRING
Concatenates array elements using supplied delimiter and optional null string and returns the resulting string. If the nullString parameter is omitted or NULL, any null elements in the array are simply skipped and not represented in the output string.
**Example**
```sql
ARRAY_TO_STRING(my_array_col, my_delimiter_col, my_null_string_col)
ARRAY_TO_STRING(ARRAY['a','b','c'], ',') evaluates to 'a,b,c'
ARRAY_TO_STRING(ARRAY['a','b',null,'c'], ',') evaluates to 'a,b,c'
ARRAY_TO_STRING(ARRAY['a','b',null,'c'], ',', 'NULL') evaluates to 'a,b,NULL,c'
```
### ANY
Used on the right-hand side of a comparison expression to test that any array element satisfies the comparison expression against the left-hand side.
**Example**
```sql
1 = ANY(my_array)
10 > ANY(my_array)
```
### ALL
Used on the right-hand side of a comparison expression to test that all array elements satisfy the comparison expression against the left-hand side. of the array.
**Example**
```sql
1 = ALL(my_array)
10 > ALL(my_array)
```
## Functions (General)
### MD5
Computes the MD5 hash of the argument, returning the result as a `BINARY(16)`.
**Example**
```sql
MD5(my_column)
```
### INVERT
Inverts the bits of the argument. The return type will be the same as the argument.
**Example**
```sql
INVERT(my_column)
```
### ENCODE
Encodes the expression according to the encoding format provided and returns the resulting string. For 'BASE62', converts the given base 10 number to a base 62 number and returns a string representing the number.
**Example**
```sql
ENCODE(myNumber, 'BASE62')
```
### DECODE
Decodes the expression according to the encoding format provided and returns the resulting value as a `VARBINARY`. For 'HEX', converts the hex string expression to its binary representation, providing a mechanism for inputting binary data through the console.
**Example**
```sql
DECODE('000000008512af277ffffff8', 'HEX')
```
### COALESCE
Returns the value of the first argument if not null and the second argument otherwise. Useful to guarantee that a column in an `UPSERT SELECT` command will evaluate to a non null value.
**Example**
```sql
COALESCE(last_update_date, CURRENT_DATE())
```
### GET\_BIT
Retrieves the bit at the given index in the given binary value.
**Example**
```sql
GET_BIT(CAST('FFFF' as BINARY), 1)
```
### GET\_BYTE
Retrieves the byte at the given index in the given binary value.
**Example**
```sql
GET_BYTE(CAST('FFFF' as BINARY), 1)
```
### OCTET\_LENGTH
Returns the number of bytes in a binary value.
**Example**
```sql
OCTET_LENGTH(NAME)
```
### SET\_BIT
Replaces the bit at the given index in the binary value with the provided newValue.
**Example**
```sql
SET_BIT(CAST('FFFF' as BINARY), 1, 61)
```
### SET\_BYTE
Replaces the byte at the given index in the binary value with the provided newValue.
**Example**
```sql
SET_BYTE(CAST('FFFF' as BINARY), 1, 61)
```
## Functions (Time and Date)
### TO\_DATE
Parses a string and returns a date. Note that the returned date is internally represented as the number of milliseconds since the java epoch. The most important format characters are: y year, M month, d day, H hour, m minute, s second. The default format string is "yyyy-MM-dd HH:mm:ss". For details of the format, see `java.text.SimpleDateFormat`. By default, GMT will be used as the time zone when parsing the date. However, a time zone id can also be supplied. This is a time zone id such as 'GMT+1'. If 'local' is provided as the time zone id, the local time zone will be used for parsing. The configuration setting "phoenix.query.dateFormatTimeZone" can also be set to a time zone id, which will cause the default of GMT to be overridden with the configured time zone id. Please see the Data Type reference guide about how Apache Phoenix presently defines the DATE datatype. Additionally, Phoenix supports the ANSI SQL "date" literal which acts similarly to the single-argument "TO\_DATE" function.
**Example**
```sql
TO_DATE('Sat, 3 Feb 2001 03:05:06 GMT', 'EEE, d MMM yyyy HH:mm:ss z')
TO_DATE('1970-01-01', 'yyyy-MM-dd', 'GMT+1')
date '1970-01-01 12:30:00'
```
### CURRENT\_DATE
Returns the current server-side date, bound at the start of the execution of a query based on the current time on the region server owning the metadata of the table being queried. Please see the Data Type reference guide about how Apache Phoenix presently defines the DATE datatype.
**Example**
```sql
CURRENT_DATE()
```
### TO\_TIME
Converts the given string into a TIME instance. When a date format is not provided it defaults to "yyyy-MM-dd HH:mm:ss.SSS" or whatever is defined by the configuration property `phoenix.query.dateFormat`. The configuration setting `phoenix.query.dateFormatTimeZone` can also be set to a time zone id, which will cause the default of GMT to be overridden with the configured time zone id. Additionally, Phoenix supports the ANSI SQL "time" literal which acts similarly to the single-argument "TO\_TIME" function.
**Example**
```sql
TO_TIME('2005-10-01 14:03:22.559')
TO_TIME('1970-01-01', 'yyyy-MM-dd', 'GMT+1')
time '2005-10-01 14:03:22.559'
```
### TO\_TIMESTAMP
Converts the given string into a TIMESTAMP instance. When a date format is not provided it defaults to "yyyy-MM-dd HH:mm:ss.SSS" or whatever is defined by the configuration property `phoenix.query.dateFormat`. The configuration setting `phoenix.query.dateFormatTimeZone` can also be set to a time zone id, which will cause the default of GMT to be overridden with the configured time zone id. Additionally, Phoenix supports the ANSI SQL "timestamp" literal which acts similarly to the single-argument "TO\_TIMESTAMP" function.
**Example**
```sql
TO_TIMESTAMP('2005-10-01 14:03:22.559')
TO_TIMESTAMP('1970-01-01', 'yyyy-MM-dd', 'GMT+1')
timestamp '2005-10-01 14:03:22.559'
```
### CURRENT\_TIME
Same as CURRENT\_DATE(), except returns a value of type TIME. In either case, the underlying representation is the epoch time as a long value. Please see the Data Type reference guide about how Apache Phoenix presently defines the TIME datatype.
**Example**
```sql
CURRENT_TIME()
```
### CONVERT\_TZ
Converts date/time from one time zone to another returning the shifted date/time value.
**Example**
```sql
CONVERT_TZ(myDate, 'UTC', 'Europe/Prague')
```
### TIMEZONE\_OFFSET
Returns offset (shift in minutes) of a time zone at particular date/time in minutes.
**Example**
```sql
TIMEZONE_OFFSET('Indian/Cocos', myDate)
```
### NOW
Returns the current date, bound at the start of the execution of a query based on the current time on the region server owning the metadata of the table being queried.
**Example**
```sql
NOW()
```
### YEAR
Returns the year of the specified date.
**Example**
```sql
YEAR(TO_DATE('2015-6-05'))
```
### MONTH
Returns the month of the specified date.
**Example**
```sql
MONTH(TO_TIMESTAMP('2015-6-05'))
```
### WEEK
Returns the week of the specified date.
**Example**
```sql
WEEK(TO_TIME('2010-6-15'))
```
### DAYOFYEAR
Returns the day of the year of the specified date.
**Example**
```sql
DAYOFYEAR(TO_DATE('2004-01-18 10:00:10'))
```
### DAYOFMONTH
Returns the day of the month of the specified date.
**Example**
```sql
DAYOFMONTH(TO_DATE('2004-01-18 10:00:10'))
```
### DAYOFWEEK
Returns the day of the week of the specified date.
**Example**
```sql
DAYOFWEEK(TO_DATE('2004-01-18 10:00:10'))
```
### HOUR
Returns the hour of the specified date.
**Example**
```sql
HOUR(TO_TIMESTAMP('2015-6-05'))
```
### MINUTE
Returns the minute of the specified date.
**Example**
```sql
MINUTE(TO_TIME('2015-6-05'))
```
### SECOND
Returns the second of the specified date.
**Example**
```sql
SECOND(TO_DATE('2015-6-05'))
```
## Functions (Math)
### SIGN
Returns the signum function of the given numeric expression as an `INTEGER`. The return value is -1 if the given numeric expression is negative; 0 if the given numeric expression is zero; and 1 if the given numeric expression is positive.
**Example**
```sql
SIGN(number)
SIGN(1.1)
SIGN(-1)
```
### ABS
Returns the absolute value of the given numeric expression maintaining the same type.
**Example**
```sql
ABS(number)
ABS(1.1)
ABS(-1)
```
### SQRT
Returns the correctly rounded square root of the given non-negative numeric expression as a `DOUBLE`.
**Example**
```sql
SQRT(number)
SQRT(1.1)
```
### CBRT
Returns the cube root of the given numeric expression as a `DOUBLE`.
**Example**
```sql
CBRT(number)
CBRT(1.1)
CBRT(-1)
```
### EXP
Returns Euler's number e raised to the power of the given numeric value as a `DOUBLE`.
**Example**
```sql
EXP(number)
EXP(1.1)
EXP(-1)
```
### POWER
Returns the value of the first argument raised to the power of the second argument as a `DOUBLE`.
**Example**
```sql
POWER(number, number)
POWER(3, 2)
POWER(2, 3)
```
### LN
Returns the natural logarithm (base e) of the given positive expression as a `DOUBLE`.
**Example**
```sql
LN(number)
LN(3)
LN(2)
```
### LOG
Returns the logarithm of the first argument computed at the base of the second argument as a `DOUBLE`. If omitted, a base of 10 will be used for the second argument.
**Example**
```sql
LOG(3, 2)
LOG(2, 3)
LOG(2)
```
# Backward Compatibility (/docs/fundamentals/backward-compatibility)
Phoenix maintains backward compatibility across at least two minor releases to allow for **no downtime** through server-side rolling
restarts upon upgrading. See below for details.
## Versioning Convention
Phoenix uses a standard three number versioning schema of the form:
```text
. .
```
For example, **`4.2.1`** has a major version of **`4`**,
a minor version of **`2`**, and a patch version of **`1`**.
## Patch Release
Upgrading to a new patch release (i.e. only the patch version has changed) is the simplest case. The jar upgrade may occur in any order: client first or server first, and a mix of clients with different patch release versions is fine.
## Minor Release
When upgrading to a new minor release (i.e. the major version is the same, but the minor
version has changed), sometimes modifications to the system tables are necessary to either
fix a bug or provide a new feature. This upgrade will occur automatically the first time a
newly upgraded client connects to the newly upgraded server. It is **required** that the
server-side jar be upgraded first across your entire cluster, before any clients are
upgraded. An older client (two minor versions back) will work with a newer server jar when
the minor version is different, but not vice versa. In other words, clients do not need to
be upgraded in lock step with the server. However, as the server version moves forward,
the client version should move forward as well. This allows Phoenix to evolve its client/server
protocol while still providing clients sufficient time to upgrade their clients.
As of the 4.3 release, a mix of clients on different minor release versions is supported as well
(note that prior releases required all clients to be upgraded at the same time). Another improvement
as of the 4.3 release is that an upgrade may be done directly from one minor version to another
higher minor version (prior releases required an upgrade to each minor version in between).
## Major Release
Upgrading to a new major release may require downtime as well as potentially the running of a migration
script. Additionally, all clients and servers may need to be upgraded at the same time. This will be
determined on a release-by-release basis.
## Release Notes
Specific details on issues and their fixes that may impact you can be found [here](/release-notes).
# Building (/docs/fundamentals/building)
## Building the Main Phoenix Project
Phoenix consists of several subprojects.
The core project is `phoenix`, which depends on `phoenix-thirdparty`, `phoenix-omid`, and `phoenix-tephra`.
`phoenix-queryserver` and `phoenix-connectors` are optional packages that also depend on `phoenix`.
Check out the [source](/source-repository) and follow the build instructions in `BUILDING.md` (or `README.md`) in the repository root.
## Using Phoenix in a Maven Project
Phoenix artifacts are published to Apache and Maven Central repositories. Add the dependency below to your `pom.xml`:
```xml
org.apache.phoenixphoenix-client-hbase-[hbase.profile][phoenix.version]
```
Where:
* `[phoenix.version]` is the Phoenix release version (for example, `5.1.2` or `4.16.1`)
* `[hbase.profile]` is the compatible HBase profile
See [Downloads](/downloads) for supported release/profile combinations.
## Branches
The main Phoenix project currently has two active branches.
* `4.x` works with HBase 1 and Hadoop 2
* `5.x` works with HBase 2 and Hadoop 3
See [Downloads](/downloads) and `BUILDING.md` for exact version compatibility by release.
See also:
* [Building Project Website](/docs/contributing/building-website)
* [How to Release](/docs/contributing/how-to-release)
# Client Classpath and JDBC URL (/docs/fundamentals/client-classpath-and-jdbc-url)
## Using the Phoenix JDBC Driver
This page is about using the Phoenix thick client.
The thin client for Phoenix Query Server is described on its own [page](/docs/features/query-server).
## The Phoenix classpath
To use Phoenix, both the JDBC driver JAR and `hbase-site.xml` must be added to the application classpath.
### Phoenix driver JAR
The Phoenix JDBC client is built on top of the HBase client, and has an unusually high number of dependencies.
To make this manageable, Phoenix provides a single shaded uberjar that can be added to the classpath.
Phoenix uses some private and semi-public HBase APIs, which may change between HBase versions, and provides separate binary distributions for different HBase versions.
Choose the [binary distribution](/downloads) or Maven artifact corresponding to the HBase version on your cluster.
Copy the driver JAR from the binary distribution.
Copy the corresponding `phoenix-client-embedded-hbase-[hbase.profile]-[phoenix.version].jar` to the application classpath.
Add the dependency via Maven.
```xml
org.apache.phoenixphoenix-client-embedded-hbase-[hbase.profile][phoenix.version]
```
Add
`hbase-site.xml`
from your target cluster to the classpath.
Verify your config is current after cluster changes.
### HBase / Hadoop configuration files
As Phoenix is built on top of the HBase client, it needs the HBase configuration files for correct operation.
For some configurations, it may also need other Hadoop / HDFS config files like core-site.xml.
Download the correct `hbase-site.xml` (the client one, usually in `/etc/hbase/conf`) from the cluster, and copy it to a directory on the classpath.
It is important to add the **directory containing `hbase-site.xml`**, and not the full file path, to the classpath.
Alternatively, package `hbase-site.xml` into the root directory of a JAR file and add that JAR to the classpath.
If `hbase-site.xml` changes on the cluster, make sure to copy the updated file to your application classpath.
For some development clusters that use default configuration Phoenix may work without this, but not having the correct `hbase-site.xml` on the
classpath is almost guaranteed to cause problems.
## The Phoenix JDBC URL
The Phoenix URL contains two main parts. The first describes the connection to HBase; the second specifies extra Phoenix options.
```text
jdbc:[:[:[:[:[:]]]]][;