[Security Solution][Elastic AI Assistant] Adds Knowledge Base (#166570)

## Summary

Advanced Settings to enable the Knowledge Base is currently behind the
same code toggle introduced in
https://github.com/elastic/kibana/pull/164908, please modify
`assistantLangChain` to be `true` to enable the Advanced Settings and
Knowledge Base options:


1dee16e061/x-pack/plugins/security_solution/public/assistant/provider.tsx (L55)

When the above modification is present, a new `Advanced Settings` UI is
available within the Assistant that enables the ability to turn on and
configure the Knowledge Base, and also add the requisite `ES|QL`
Knowledge Base resources.

<p align="center">
<img width="500"
src="3abab422-9a85-45f6-8b0b-854ed7383d1c"
/>
</p> 

Once enabled, the Assistant will query the Knowledge Base using a
`ConversationalRetrievalQAChain`, by means of the ELSER specific
`ElasticsearchStore` LangChain `VectorStore` abstraction:


<p align="center">
<img width="500"
src="a529faab-948c-40f1-bc11-7a39b1766c7f"
/>
</p> 



### Checklist

Delete any items that are not applicable to this PR.

- [X] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
  * TBD
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [X] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Garrett Spong 2023-09-19 18:53:41 -06:00 committed by GitHub
parent 8e800eec5a
commit f5b09864b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
172 changed files with 5593 additions and 70 deletions

View file

@ -923,7 +923,7 @@
"jsonwebtoken": "^9.0.0",
"jsts": "^1.6.2",
"kea": "^2.4.2",
"langchain": "^0.0.132",
"langchain": "^0.0.151",
"launchdarkly-js-client-sdk": "^2.22.1",
"launchdarkly-node-server-sdk": "^6.4.2",
"load-json-file": "^6.2.0",

View file

@ -7,9 +7,9 @@
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common';
import { HttpSetup } from '@kbn/core-http-browser';
import type { Message } from '../assistant_context/types';
import { Conversation } from '../assistant_context/types';
import { HttpSetup, IHttpFetchError } from '@kbn/core-http-browser';
import type { Conversation, Message } from '../assistant_context/types';
import { API_ERROR } from './translations';
import { MODEL_GPT_3_5_TURBO } from '../connectorland/models/model_selector/model_selector';
@ -86,3 +86,121 @@ export const fetchConnectorExecuteAction = async ({
return API_ERROR;
}
};
export interface GetKnowledgeBaseStatusParams {
http: HttpSetup;
resource?: string;
signal?: AbortSignal | undefined;
}
export interface GetKnowledgeBaseStatusResponse {
elser_exists: boolean;
esql_exists?: boolean;
index_exists: boolean;
pipeline_exists: boolean;
}
/**
* API call for getting the status of the Knowledge Base. Provide
* a resource to include the status of that specific resource.
*
* @param {Object} options - The options object.
* @param {HttpSetup} options.http - HttpSetup
* @param {string} [options.resource] - Resource to get the status of, otherwise status of overall KB
* @param {AbortSignal} [options.signal] - AbortSignal
*
* @returns {Promise<GetKnowledgeBaseStatusResponse | IHttpFetchError>}
*/
export const getKnowledgeBaseStatus = async ({
http,
resource,
signal,
}: GetKnowledgeBaseStatusParams): Promise<GetKnowledgeBaseStatusResponse | IHttpFetchError> => {
try {
const path = `/internal/elastic_assistant/knowledge_base/${resource || ''}`;
const response = await http.fetch(path, {
method: 'GET',
signal,
});
return response as GetKnowledgeBaseStatusResponse;
} catch (error) {
return error as IHttpFetchError;
}
};
export interface PostKnowledgeBaseParams {
http: HttpSetup;
resource?: string;
signal?: AbortSignal | undefined;
}
export interface PostKnowledgeBaseResponse {
success: boolean;
}
/**
* API call for setting up the Knowledge Base. Provide a resource to set up a specific resource.
*
* @param {Object} options - The options object.
* @param {HttpSetup} options.http - HttpSetup
* @param {string} [options.resource] - Resource to be added to the KB, otherwise sets up the base KB
* @param {AbortSignal} [options.signal] - AbortSignal
*
* @returns {Promise<PostKnowledgeBaseResponse | IHttpFetchError>}
*/
export const postKnowledgeBase = async ({
http,
resource,
signal,
}: PostKnowledgeBaseParams): Promise<PostKnowledgeBaseResponse | IHttpFetchError> => {
try {
const path = `/internal/elastic_assistant/knowledge_base/${resource || ''}`;
const response = await http.fetch(path, {
method: 'POST',
signal,
});
return response as PostKnowledgeBaseResponse;
} catch (error) {
return error as IHttpFetchError;
}
};
export interface DeleteKnowledgeBaseParams {
http: HttpSetup;
resource?: string;
signal?: AbortSignal | undefined;
}
export interface DeleteKnowledgeBaseResponse {
success: boolean;
}
/**
* API call for deleting the Knowledge Base. Provide a resource to delete that specific resource.
*
* @param {Object} options - The options object.
* @param {HttpSetup} options.http - HttpSetup
* @param {string} [options.resource] - Resource to be deleted from the KB, otherwise delete the entire KB
* @param {AbortSignal} [options.signal] - AbortSignal
*
* @returns {Promise<DeleteKnowledgeBaseResponse | IHttpFetchError>}
*/
export const deleteKnowledgeBase = async ({
http,
resource,
signal,
}: DeleteKnowledgeBaseParams): Promise<DeleteKnowledgeBaseResponse | IHttpFetchError> => {
try {
const path = `/internal/elastic_assistant/knowledge_base/${resource || ''}`;
const response = await http.fetch(path, {
method: 'DELETE',
signal,
});
return response as DeleteKnowledgeBaseResponse;
} catch (error) {
return error as IHttpFetchError;
}
};

View file

@ -17,7 +17,7 @@ export const SETTINGS_TITLE = i18n.translate(
export const SETTINGS_DESCRIPTION = i18n.translate(
'xpack.elasticAssistant.assistant.conversations.settings.settingsDescription',
{
defaultMessage: 'Create and manage conversations with the Elastic AI Assistant',
defaultMessage: 'Create and manage conversations with the Elastic AI Assistant.',
}
);

View file

@ -17,7 +17,7 @@ export const SETTINGS_DESCRIPTION = i18n.translate(
'xpack.elasticAssistant.assistant.promptEditor.systemPrompt.settings.settingsDescription',
{
defaultMessage:
'Create and manage System Prompts. System Prompts are configurable chunks of context that are always sent for a given conversations.',
'Create and manage System Prompts. System Prompts are configurable chunks of context that are always sent for a given conversation.',
}
);
export const ADD_SYSTEM_PROMPT_MODAL_TITLE = i18n.translate(

View file

@ -5,19 +5,130 @@
* 2.0.
*/
import React from 'react';
import { EuiFormRow, EuiTitle, EuiText, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
import React, { useCallback, useMemo, useState } from 'react';
import {
EuiFormRow,
EuiTitle,
EuiText,
EuiTextColor,
EuiHorizontalRule,
EuiLoadingSpinner,
EuiSpacer,
EuiSwitch,
EuiToolTip,
EuiSwitchEvent,
EuiLink,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import * as i18n from './translations';
import { useKnowledgeBaseStatus } from '../../../knowledge_base/use_knowledge_base_status/use_knowledge_base_status';
import { useAssistantContext } from '../../../assistant_context';
import { useSetupKnowledgeBase } from '../../../knowledge_base/use_setup_knowledge_base/use_setup_knowledge_base';
import { useDeleteKnowledgeBase } from '../../../knowledge_base/use_delete_knowledge_base/use_delete_knowledge_base';
const ESQL_RESOURCE = 'esql';
interface Props {
onAdvancedSettingsChange?: () => void;
}
/**
* Advanced Settings -- your catch-all container for settings that don't have a home elsewhere.
* Advanced Settings -- enable and disable LangChain integration, Knowledge Base, and ESQL KB Documents
*/
export const AdvancedSettings: React.FC<Props> = React.memo(({ onAdvancedSettingsChange }) => {
const { http, assistantLangChain } = useAssistantContext();
const {
data: kbStatus,
isLoading,
isFetching,
} = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE });
const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http });
const { mutate: deleteKB, isLoading: isDeletingUpKB } = useDeleteKnowledgeBase({ http });
const [isLangChainEnabled, setIsLangChainEnabled] = useState(assistantLangChain);
const isKnowledgeBaseEnabled =
(kbStatus?.index_exists && kbStatus?.pipeline_exists && kbStatus?.elser_exists) ?? false;
const isESQLEnabled = kbStatus?.esql_exists ?? false;
const isLoadingKb = isLoading || isFetching || isSettingUpKB || isDeletingUpKB;
const isKnowledgeBaseAvailable = isLangChainEnabled && kbStatus?.elser_exists;
const isESQLAvailable = isLangChainEnabled && isKnowledgeBaseAvailable && isKnowledgeBaseEnabled;
const onEnableKnowledgeBaseChange = useCallback(
(event: EuiSwitchEvent) => {
if (event.target.checked) {
setupKB();
} else {
deleteKB();
}
},
[deleteKB, setupKB]
);
const onEnableESQLChange = useCallback(
(event: EuiSwitchEvent) => {
if (event.target.checked) {
setupKB(ESQL_RESOURCE);
} else {
deleteKB(ESQL_RESOURCE);
}
},
[deleteKB, setupKB]
);
const langchainSwitch = useMemo(() => {
return (
<EuiSwitch
checked={isLangChainEnabled}
compressed
disabled={true} // Advanced settings only shown if assistantLangChain=true, remove when storing to localstorage as ui feature toggle
label={i18n.LANNGCHAIN_LABEL}
onChange={() => setIsLangChainEnabled(!isLangChainEnabled)}
showLabel={false}
/>
);
}, [isLangChainEnabled]);
const knowledgeBaseSwitch = useMemo(() => {
return isLoadingKb ? (
<EuiLoadingSpinner size="s" />
) : (
<EuiToolTip
position={'right'}
content={!isKnowledgeBaseAvailable ? i18n.KNOWLEDGE_BASE_LABEL_TOOLTIP : undefined}
>
<EuiSwitch
showLabel={false}
label={i18n.KNOWLEDGE_BASE_LABEL}
checked={isKnowledgeBaseEnabled}
disabled={!isKnowledgeBaseAvailable}
onChange={onEnableKnowledgeBaseChange}
compressed
/>
</EuiToolTip>
);
}, [isLoadingKb, isKnowledgeBaseAvailable, isKnowledgeBaseEnabled, onEnableKnowledgeBaseChange]);
const esqlSwitch = useMemo(() => {
return isLoadingKb ? (
<EuiLoadingSpinner size="s" />
) : (
<EuiToolTip
position={'right'}
content={!isESQLAvailable ? i18n.ESQL_LABEL_TOOLTIP : undefined}
>
<EuiSwitch
showLabel={false}
label={i18n.ESQL_LABEL}
checked={isESQLEnabled}
disabled={!isESQLAvailable}
onChange={onEnableESQLChange}
compressed
/>
</EuiToolTip>
);
}, [isLoadingKb, isESQLAvailable, isESQLEnabled, onEnableESQLChange]);
return (
<>
<EuiTitle size={'s'}>
@ -29,15 +140,52 @@ export const AdvancedSettings: React.FC<Props> = React.memo(({ onAdvancedSetting
<EuiHorizontalRule margin={'s'} />
<EuiFormRow display="rowCompressed" label={'Disable LocalStorage'}>
<>{'Disable LocalStorage'}</>
<EuiFormRow display="columnCompressedSwitch" label={i18n.LANNGCHAIN_LABEL}>
{langchainSwitch}
</EuiFormRow>
<EuiFormRow display="rowCompressed" label={'Clear LocalStorage'}>
<>{'Clear LocalStorage'}</>
<EuiSpacer size="xs" />
<EuiTextColor color={'subdued'}>{i18n.LANNGCHAIN_DESCRIPTION}</EuiTextColor>
<EuiSpacer size="m" />
<EuiFormRow
display="columnCompressedSwitch"
label={i18n.KNOWLEDGE_BASE_LABEL}
isDisabled={!isKnowledgeBaseAvailable}
>
{knowledgeBaseSwitch}
</EuiFormRow>
<EuiFormRow display="rowCompressed" label={'Reset Something Else'}>
<>{'Reset Something Else'}</>
<EuiSpacer size="xs" />
<EuiTextColor color={'subdued'}>
<FormattedMessage
defaultMessage="Initializes a local knowledge base for saving and retrieving relevant context for your conversations. Note: ELSER must be configured and started. {seeDocs}"
id="xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseDescription"
values={{
seeDocs: (
<EuiLink
external
href={
'https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html#download-deploy-elser'
}
target="_blank"
>
{i18n.KNOWLEDGE_BASE_DESCRIPTION_ELSER_LEARN_MORE}
</EuiLink>
),
}}
/>
</EuiTextColor>
<EuiSpacer size="m" />
<EuiFormRow
isDisabled={!isESQLAvailable}
display="columnCompressedSwitch"
label={i18n.ESQL_LABEL}
>
{esqlSwitch}
</EuiFormRow>
<EuiSpacer size="xs" />
<EuiTextColor color={'subdued'}>{i18n.ESQL_DESCRIPTION}</EuiTextColor>
<EuiSpacer size="m" />
</>
);
});

View file

@ -16,6 +16,64 @@ export const SETTINGS_TITLE = i18n.translate(
export const SETTINGS_DESCRIPTION = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.settingsDescription',
{
defaultMessage: "They're not further along, they just have a different set of problems.",
defaultMessage: 'Additional knobs and dials for the Elastic AI Assistant.',
}
);
export const LANNGCHAIN_LABEL = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.langChainLabel',
{
defaultMessage: 'Experimental LangChain Integration',
}
);
export const LANNGCHAIN_DESCRIPTION = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.langChainDescription',
{
defaultMessage:
'Enables advanced features and workflows like the Knowledge Base, Functions, Memories, and advanced agent and chain configurations. ',
}
);
export const KNOWLEDGE_BASE_LABEL = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseLabel',
{
defaultMessage: 'Knowledge Base',
}
);
export const KNOWLEDGE_BASE_LABEL_TOOLTIP = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseLabelTooltip',
{
defaultMessage: 'Requires ELSER to be configured and started.',
}
);
export const KNOWLEDGE_BASE_DESCRIPTION_ELSER_LEARN_MORE = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.knowledgeBaseElserLearnMoreDescription',
{
defaultMessage: 'Learn more.',
}
);
export const ESQL_LABEL = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlLabel',
{
defaultMessage: 'ES|QL Knowledge Base Documents',
}
);
export const ESQL_LABEL_TOOLTIP = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlTooltip',
{
defaultMessage: 'Requires `Knowledge Base` to be enabled.',
}
);
export const ESQL_DESCRIPTION = i18n.translate(
'xpack.elasticAssistant.assistant.settings.advancedSettings.esqlDescription',
{
defaultMessage:
'Loads ES|QL documentation and language files into the Knowledge Base for use in generating ES|QL queries.',
}
);

View file

@ -44,7 +44,6 @@ export const CONVERSATIONS_TAB = 'CONVERSATION_TAB' as const;
export const QUICK_PROMPTS_TAB = 'QUICK_PROMPTS_TAB' as const;
export const SYSTEM_PROMPTS_TAB = 'SYSTEM_PROMPTS_TAB' as const;
export const ANONYMIZATION_TAB = 'ANONYMIZATION_TAB' as const;
export const FUNCTIONS_TAB = 'FUNCTIONS_TAB' as const;
export const ADVANCED_TAB = 'ADVANCED_TAB' as const;
export type SettingsTabs =
@ -52,7 +51,6 @@ export type SettingsTabs =
| typeof QUICK_PROMPTS_TAB
| typeof SYSTEM_PROMPTS_TAB
| typeof ANONYMIZATION_TAB
| typeof FUNCTIONS_TAB
| typeof ADVANCED_TAB;
interface Props {
defaultConnectorId?: string;
@ -78,8 +76,13 @@ export const AssistantSettings: React.FC<Props> = React.memo(
selectedConversation: defaultSelectedConversation,
setSelectedConversationId,
}) => {
const { actionTypeRegistry, http, selectedSettingsTab, setSelectedSettingsTab } =
useAssistantContext();
const {
actionTypeRegistry,
assistantLangChain,
http,
selectedSettingsTab,
setSelectedSettingsTab,
} = useAssistantContext();
const {
conversationSettings,
defaultAllow,
@ -235,6 +238,16 @@ export const AssistantSettings: React.FC<Props> = React.memo(
>
<EuiIcon type="eyeClosed" size="l" />
</EuiKeyPadMenuItem>
{assistantLangChain && (
<EuiKeyPadMenuItem
id={ADVANCED_TAB}
label={i18n.ADVANCED_MENU_ITEM}
isSelected={selectedSettingsTab === ADVANCED_TAB}
onClick={() => setSelectedSettingsTab(ADVANCED_TAB)}
>
<EuiIcon type="advancedSettingsApp" size="l" />
</EuiKeyPadMenuItem>
)}
</EuiKeyPadMenu>
</EuiPageSidebar>
<EuiPageBody paddingSize="none" panelled={true}>
@ -287,7 +300,6 @@ export const AssistantSettings: React.FC<Props> = React.memo(
setUpdatedDefaultAllowReplacement={setUpdatedDefaultAllowReplacement}
/>
)}
{selectedSettingsTab === FUNCTIONS_TAB && <></>}
{selectedSettingsTab === ADVANCED_TAB && <AdvancedSettings />}
</EuiSplitPanel.Inner>
<EuiSplitPanel.Inner

View file

@ -49,13 +49,6 @@ export const ANONYMIZATION_MENU_ITEM = i18n.translate(
}
);
export const FUNCTIONS_MENU_ITEM = i18n.translate(
'xpack.elasticAssistant.assistant.settings.settingsFunctionsMenuItemTitle',
{
defaultMessage: 'Functions',
}
);
export const ADVANCED_MENU_ITEM = i18n.translate(
'xpack.elasticAssistant.assistant.settings.settingsAdvancedMenuItemTitle',
{

View file

@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useMutation } from '@tanstack/react-query';
import type { IToasts } from '@kbn/core-notifications-browser';
import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import { i18n } from '@kbn/i18n';
import { deleteKnowledgeBase } from '../../assistant/api';
import { useInvalidateKnowledgeBaseStatus } from '../use_knowledge_base_status/use_knowledge_base_status';
const DELETE_KNOWLEDGE_BASE_MUTATION_KEY = ['elastic-assistant', 'delete-knowledge-base'];
export interface UseDeleteKnowledgeBaseParams {
http: HttpSetup;
toasts?: IToasts;
}
/**
* Hook for deleting the Knowledge Base. Provide a resource name to delete a
* specific resource within KB.
*
* @param {Object} options - The options object.
* @param {HttpSetup} options.http - HttpSetup
* @param {IToasts} [options.toasts] - IToasts
*
* @returns {useMutation} hook for deleting the Knowledge Base
*/
export const useDeleteKnowledgeBase = ({ http, toasts }: UseDeleteKnowledgeBaseParams) => {
const invalidateKnowledgeBaseStatus = useInvalidateKnowledgeBaseStatus();
return useMutation(
DELETE_KNOWLEDGE_BASE_MUTATION_KEY,
(resource?: string | void) => {
// Optional params workaround: see: https://github.com/TanStack/query/issues/1077#issuecomment-1431247266
return deleteKnowledgeBase({ http, resource: resource ?? undefined });
},
{
onError: (error: IHttpFetchError<ResponseErrorBody>) => {
if (error.name !== 'AbortError') {
toasts?.addError(
error.body && error.body.message ? new Error(error.body.message) : error,
{
title: i18n.translate('xpack.elasticAssistant.knowledgeBase.deleteError', {
defaultMessage: 'Error deleting Knowledge Base',
}),
}
);
}
},
onSettled: () => {
invalidateKnowledgeBaseStatus();
},
}
);
};

View file

@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import type { IToasts } from '@kbn/core-notifications-browser';
import { i18n } from '@kbn/i18n';
import { useCallback } from 'react';
import { getKnowledgeBaseStatus } from '../../assistant/api';
const KNOWLEDGE_BASE_STATUS_QUERY_KEY = ['elastic-assistant', 'knowledge-base-status'];
export interface UseKnowledgeBaseStatusParams {
http: HttpSetup;
resource?: string;
toasts?: IToasts;
}
export interface GetKnowledgeBaseStatusResponse {
elser_exists: boolean;
esql_exists?: boolean;
index_exists: boolean;
pipeline_exists: boolean;
}
/**
* Hook for getting the status of the Knowledge Base. Provide a resource name to include
* the status for that specific resource within the KB.
*
* @param {Object} options - The options object.
* @param {HttpSetup} options.http - HttpSetup
* @param {IToasts} [options.toasts] - IToasts
*
* @returns {useQuery} hook for getting the status of the Knowledge Base
*/
export const useKnowledgeBaseStatus = ({
http,
resource,
toasts,
}: UseKnowledgeBaseStatusParams): UseQueryResult<
GetKnowledgeBaseStatusResponse,
IHttpFetchError
> => {
return useQuery(
KNOWLEDGE_BASE_STATUS_QUERY_KEY,
async ({ signal }) => {
return getKnowledgeBaseStatus({ http, resource, signal });
},
{
retry: false,
keepPreviousData: true,
// Deprecated, hoist to `queryCache` w/in `QueryClient. See: https://stackoverflow.com/a/76961109
onError: (error: IHttpFetchError<ResponseErrorBody>) => {
if (error.name !== 'AbortError') {
toasts?.addError(
error.body && error.body.message ? new Error(error.body.message) : error,
{
title: i18n.translate('xpack.elasticAssistant.knowledgeBase.statusError', {
defaultMessage: 'Error fetching Knowledge Base Status',
}),
}
);
}
},
}
);
};
/**
* Use this hook to invalidate the Knowledge Base Status cache. For example,
* Knowledge Base actions setting up, adding resources, or deleting should lead
* to cache invalidation.
*
* @returns {Function} - Function to invalidate the Knowledge Base Status cache
*/
export const useInvalidateKnowledgeBaseStatus = () => {
const queryClient = useQueryClient();
return useCallback(() => {
queryClient.invalidateQueries(KNOWLEDGE_BASE_STATUS_QUERY_KEY, {
refetchType: 'active',
});
}, [queryClient]);
};

View file

@ -0,0 +1,59 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useMutation } from '@tanstack/react-query';
import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import type { IToasts } from '@kbn/core-notifications-browser';
import { i18n } from '@kbn/i18n';
import { postKnowledgeBase } from '../../assistant/api';
import { useInvalidateKnowledgeBaseStatus } from '../use_knowledge_base_status/use_knowledge_base_status';
const SETUP_KNOWLEDGE_BASE_MUTATION_KEY = ['elastic-assistant', 'post-knowledge-base'];
export interface UseSetupKnowledgeBaseParams {
http: HttpSetup;
toasts?: IToasts;
}
/**
* Hook for setting up the Knowledge Base. Provide a resource name to set
* up a specific part of the KB.
*
* @param {Object} options - The options object.
* @param {HttpSetup} options.http - HttpSetup
* @param {IToasts} [options.toasts] - IToasts
*
* @returns {useMutation} mutation hook for setting up the Knowledge Base
*/
export const useSetupKnowledgeBase = ({ http, toasts }: UseSetupKnowledgeBaseParams) => {
const invalidateKnowledgeBaseStatus = useInvalidateKnowledgeBaseStatus();
return useMutation(
SETUP_KNOWLEDGE_BASE_MUTATION_KEY,
(resource?: string | void) => {
// Optional params workaround: see: https://github.com/TanStack/query/issues/1077#issuecomment-1431247266
return postKnowledgeBase({ http, resource: resource ?? undefined });
},
{
onError: (error: IHttpFetchError<ResponseErrorBody>) => {
if (error.name !== 'AbortError') {
toasts?.addError(
error.body && error.body.message ? new Error(error.body.message) : error,
{
title: i18n.translate('xpack.elasticAssistant.knowledgeBase.setupError', {
defaultMessage: 'Error setting up Knowledge Base',
}),
}
);
}
},
onSettled: () => {
invalidateKnowledgeBaseStatus();
},
}
);
};

View file

@ -133,3 +133,10 @@ export type { PromptContextTemplate } from './impl/assistant/prompt_context/type
* can be displayed when corresponding PromptContext's are registered.
*/
export type { QuickPrompt } from './impl/assistant/quick_prompts/types';
/**
* Knowledge Base API Responses
*/
export type { DeleteKnowledgeBaseResponse } from './impl/assistant/api';
export type { GetKnowledgeBaseStatusResponse } from './impl/assistant/api';
export type { PostKnowledgeBaseResponse } from './impl/assistant/api';

View file

@ -7,3 +7,8 @@ This plugin does NOT contain UI components. See `x-pack/packages/kbn-elastic-ass
## Maintainers
Maintained by the Security Solution team
### Testing
To run the tests for this plugin, run `node scripts/jest --watch x-pack/plugins/elastic_assistant/jest.config.js --coverage` from the Kibana root directory.

View file

@ -11,3 +11,6 @@ export const PLUGIN_NAME = 'elasticAssistant';
export const BASE_PATH = '/internal/elastic_assistant';
export const POST_ACTIONS_CONNECTOR_EXECUTE = `${BASE_PATH}/actions/connector/{connectorId}/_execute`;
// Knowledge Base
export const KNOWLEDGE_BASE = `${BASE_PATH}/knowledge_base/{resource?}`;

View file

@ -5,7 +5,29 @@
* 2.0.
*/
import { httpServerMock } from '@kbn/core/server/mocks';
import { KNOWLEDGE_BASE } from '../../common/constants';
export const requestMock = {
create: httpServerMock.createKibanaRequest,
};
export const getGetKnowledgeBaseStatusRequest = (resource?: string) =>
requestMock.create({
method: 'get',
path: KNOWLEDGE_BASE,
query: { resource },
});
export const getPostKnowledgeBaseRequest = (resource?: string) =>
requestMock.create({
method: 'post',
path: KNOWLEDGE_BASE,
query: { resource },
});
export const getDeleteKnowledgeBaseRequest = (resource?: string) =>
requestMock.create({
method: 'delete',
path: KNOWLEDGE_BASE,
query: { resource },
});

View file

@ -4,8 +4,16 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { coreMock } from '@kbn/core/server/mocks';
import { coreMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { licensingMock } from '@kbn/licensing-plugin/server/mocks';
import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock';
import { MockedKeys } from '@kbn/utility-types-jest';
import { AwaitedProperties } from '@kbn/utility-types';
import {
ElasticAssistantApiRequestHandlerContext,
ElasticAssistantRequestHandlerContext,
} from '../types';
import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server';
export const createMockClients = () => {
const core = coreMock.createRequestHandlerContext();
@ -14,6 +22,10 @@ export const createMockClients = () => {
return {
core,
clusterClient: core.elasticsearch.client,
elasticAssistant: {
actions: actionsClientMock.create(),
logger: loggingSystemMock.createLogger(),
},
savedObjectsClient: core.savedObjects.client,
licensing: {
@ -28,17 +40,39 @@ export const createMockClients = () => {
type MockClients = ReturnType<typeof createMockClients>;
const convertRequestContextMock = <T>(context: T) => {
return coreMock.createCustomRequestHandlerContext(context);
export type ElasticAssistantRequestHandlerContextMock = MockedKeys<
AwaitedProperties<Omit<ElasticAssistantRequestHandlerContext, 'resolve'>>
> & {
core: MockClients['core'];
};
const createMockConfig = () => ({});
const createAppClientMock = () => ({});
const createRequestContextMock = (clients: MockClients = createMockClients()) => {
const createRequestContextMock = (
clients: MockClients = createMockClients()
): ElasticAssistantRequestHandlerContextMock => {
return {
core: clients.core,
elasticAssistant: createElasticAssistantRequestContextMock(clients),
};
};
const convertRequestContextMock = (
context: AwaitedProperties<ElasticAssistantRequestHandlerContextMock>
): ElasticAssistantRequestHandlerContext => {
return coreMock.createCustomRequestHandlerContext(
context
) as unknown as ElasticAssistantRequestHandlerContext;
};
const createElasticAssistantRequestContextMock = (
clients: MockClients
): jest.Mocked<ElasticAssistantApiRequestHandlerContext> => {
return {
actions: clients.elasticAssistant.actions as unknown as ActionsPluginStart,
logger: clients.elasticAssistant.logger,
};
};

View file

@ -33,7 +33,10 @@ const buildResponses = (method: Method, calls: MockCall[]): ResponseCall[] => {
case 'custom':
return calls.map(([call]) => ({
status: call.statusCode,
body: JSON.parse(call.body),
body:
Buffer.isBuffer(call.body) || typeof call.body === 'string'
? JSON.parse(call.body)
: call.body,
}));
case 'customError':
return calls.map(([call]) => ({

View file

@ -0,0 +1,14 @@
### Knowledge Base Assets
This directory contains assets for the Knowledge Base feature. The assets are used by the Elastic AI Assistant to answer questions about content that the underlying model may not have been trained on. Initial assets are provided for the following categories:
* ES|QL
* General Documentation as from: https://github.com/elastic/elasticsearch/tree/main/docs/reference/esql
* Excluding `functions/signature/*.svg`
* ANTLR Language Definitions as from: https://github.com/elastic/elasticsearch/tree/main/x-pack/plugin/esql/src/main/antlr
The assets are stored in their original source format, so `.asciidoc` for documentation, and `.g4` and `.tokens` for the ANTLR language definitions. File names have been updated to be snake_case to satisfy Kibana linting rules.
### Future
Once asset format and chunking strategies are finalized, we may want to either move the assets to a shared package so they can be consumed by other plugins, or potentially ship the pre-packaged ELSER embeddings as part of a Fleet Integration. For now though, the assets will be included in their source format within the plugin, and can then be processed and embedded at runtime.

View file

@ -0,0 +1,30 @@
[[esql-agg-functions]]
== {esql} aggregation functions
++++
<titleabbrev>Aggregation functions</titleabbrev>
++++
<<esql-stats-by>> support these functions:
// tag::functions[]
* <<esql-agg-avg>>
* <<esql-agg-count>>
* <<esql-agg-count-distinct>>
* <<esql-agg-max>>
* <<esql-agg-median>>
* <<esql-agg-median-absolute-deviation>>
* <<esql-agg-min>>
* <<esql-agg-percentile>>
* <<esql-agg-sum>>
// end::functions[]
include::aggregation-functions/avg.asciidoc[]
include::aggregation-functions/count.asciidoc[]
include::aggregation-functions/count-distinct.asciidoc[]
include::aggregation-functions/max.asciidoc[]
include::aggregation-functions/median.asciidoc[]
include::aggregation-functions/median-absolute-deviation.asciidoc[]
include::aggregation-functions/min.asciidoc[]
include::aggregation-functions/percentile.asciidoc[]
include::aggregation-functions/sum.asciidoc[]

View file

@ -0,0 +1,14 @@
[[esql-agg-avg]]
=== `AVG`
The average of a numeric field.
[source.merge.styled,esql]
----
include::{esql-specs}/stats.csv-spec[tag=avg]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats.csv-spec[tag=avg-result]
|===
The result is always a `double` not matter the input type.

View file

@ -0,0 +1,18 @@
[[esql-agg-count]]
=== `COUNT`
Counts field values.
[source.merge.styled,esql]
----
include::{esql-specs}/stats.csv-spec[tag=count]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats.csv-spec[tag=count-result]
|===
Can take any field type as input and the result is always a `long` not matter
the input type.
NOTE: There isn't yet a `COUNT(*)`. Please count a single valued field if you
need a count of rows.

View file

@ -0,0 +1,43 @@
[[esql-agg-count-distinct]]
=== `COUNT_DISTINCT`
The approximate number of distinct values.
[source.merge.styled,esql]
----
include::{esql-specs}/stats_count_distinct.csv-spec[tag=count-distinct]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats_count_distinct.csv-spec[tag=count-distinct-result]
|===
Can take any field type as input and the result is always a `long` not matter
the input type.
==== Counts are approximate
Computing exact counts requires loading values into a set and returning its
size. This doesn't scale when working on high-cardinality sets and/or large
values as the required memory usage and the need to communicate those
per-shard sets between nodes would utilize too many resources of the cluster.
This `COUNT_DISTINCT` function is based on the
https://static.googleusercontent.com/media/research.google.com/fr//pubs/archive/40671.pdf[HyperLogLog++]
algorithm, which counts based on the hashes of the values with some interesting
properties:
include::../../aggregations/metrics/cardinality-aggregation.asciidoc[tag=explanation]
==== Precision is configurable
The `COUNT_DISTINCT` function takes an optional second parameter to configure the
precision discussed previously.
[source.merge.styled,esql]
----
include::{esql-specs}/stats_count_distinct.csv-spec[tag=count-distinct-precision]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats_count_distinct.csv-spec[tag=count-distinct-precision-result]
|===

View file

@ -0,0 +1,12 @@
[[esql-agg-max]]
=== `MAX`
The maximum value of a numeric field.
[source.merge.styled,esql]
----
include::{esql-specs}/stats.csv-spec[tag=max]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats.csv-spec[tag=max-result]
|===

View file

@ -0,0 +1,21 @@
[[esql-agg-median]]
=== `MEDIAN`
The value that is greater than half of all values and less than half of
all values, also known as the 50% <<esql-agg-percentile>>.
[source.merge.styled,esql]
----
include::{esql-specs}/stats_percentile.csv-spec[tag=median]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats_percentile.csv-spec[tag=median-result]
|===
NOTE: Like <<esql-agg-percentile>>, `MEDIAN` is <<esql-agg-percentile-approximate,usually approximate>>.
[WARNING]
====
`MEDIAN` is also {wikipedia}/Nondeterministic_algorithm[non-deterministic].
This means you can get slightly different results using the same data.
====

View file

@ -0,0 +1,28 @@
[[esql-agg-median-absolute-deviation]]
=== `MEDIAN_ABSOLUTE_DEVIATION`
The median absolute deviation, a measure of variability. It is a robust
statistic, meaning that it is useful for describing data that may have outliers,
or may not be normally distributed. For such data it can be more descriptive than
standard deviation.
It is calculated as the median of each data points deviation from the median of
the entire sample. That is, for a random variable `X`, the median absolute deviation
is `median(|median(X) - Xi|)`.
[source.merge.styled,esql]
----
include::{esql-specs}/stats_percentile.csv-spec[tag=median-absolute-deviation]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats_percentile.csv-spec[tag=median-absolute-deviation-result]
|===
NOTE: Like <<esql-agg-percentile>>, `MEDIAN_ABSOLUTE_DEVIATION` is
<<esql-agg-percentile-approximate,usually approximate>>.
[WARNING]
====
`MEDIAN_ABSOLUTE_DEVIATION` is also {wikipedia}/Nondeterministic_algorithm[non-deterministic].
This means you can get slightly different results using the same data.
====

View file

@ -0,0 +1,12 @@
[[esql-agg-min]]
=== `MIN`
The minimum value of a numeric field.
[source.merge.styled,esql]
----
include::{esql-specs}/stats.csv-spec[tag=min]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats.csv-spec[tag=min-result]
|===

View file

@ -0,0 +1,28 @@
[[esql-agg-percentile]]
=== `PERCENTILE`
The value at which a certain percentage of observed values occur. For example,
the 95th percentile is the value which is greater than 95% of the observed values and
the 50th percentile is the <<esql-agg-median>>.
[source.merge.styled,esql]
----
include::{esql-specs}/stats_percentile.csv-spec[tag=percentile]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats_percentile.csv-spec[tag=percentile-result]
|===
[[esql-agg-percentile-approximate]]
==== `PERCENTILE` is (usually) approximate
include::../../aggregations/metrics/percentile-aggregation.asciidoc[tag=approximate]
[WARNING]
====
`PERCENTILE` is also {wikipedia}/Nondeterministic_algorithm[non-deterministic].
This means you can get slightly different results using the same data.
====

View file

@ -0,0 +1,12 @@
[[esql-agg-sum]]
=== `SUM`
The sum of a numeric field.
[source.merge.styled,esql]
----
include::{esql-specs}/stats.csv-spec[tag=sum]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/stats.csv-spec[tag=sum-result]
|===

View file

@ -0,0 +1,134 @@
[[esql-functions]]
== {esql} functions
++++
<titleabbrev>Functions</titleabbrev>
++++
<<esql-row,`ROW`>>, <<esql-eval,`EVAL`>> and <<esql-where,`WHERE`>> support
these functions:
* <<esql-abs>>
* <<esql-acos>>
* <<esql-asin>>
* <<esql-atan>>
* <<esql-atan2>>
* <<esql-auto_bucket>>
* <<esql-case>>
* <<esql-ceil>>
* <<esql-cidr_match>>
* <<esql-coalesce>>
* <<esql-concat>>
* <<esql-cos>>
* <<esql-cosh>>
* <<esql-date_extract>>
* <<esql-date_format>>
* <<esql-date_parse>>
* <<esql-date_trunc>>
* <<esql-e>>
* <<esql-floor>>
* <<esql-greatest>>
* <<esql-is_finite>>
* <<esql-is_infinite>>
* <<esql-is_nan>>
* <<esql-least>>
* <<esql-length>>
* <<esql-log10>>
* <<esql-ltrim>>
* <<esql-rtrim>>
* <<esql-mv_avg>>
* <<esql-mv_concat>>
* <<esql-mv_count>>
* <<esql-mv_dedupe>>
* <<esql-mv_max>>
* <<esql-mv_median>>
* <<esql-mv_min>>
* <<esql-mv_sum>>
* <<esql-now>>
* <<esql-pi>>
* <<esql-pow>>
* <<esql-round>>
* <<esql-sin>>
* <<esql-sinh>>
* <<esql-split>>
* <<esql-starts_with>>
* <<esql-substring>>
* <<esql-left>>
* <<esql-tan>>
* <<esql-tanh>>
* <<esql-tau>>
* <<esql-to_boolean>>
* <<esql-to_datetime>>
* <<esql-to_degrees>>
* <<esql-to_double>>
* <<esql-to_integer>>
* <<esql-to_ip>>
* <<esql-to_long>>
* <<esql-to_radians>>
* <<esql-to_string>>
* <<esql-to_unsigned_long>>
* <<esql-to_version>>
* <<esql-trim>>
include::functions/abs.asciidoc[]
include::functions/acos.asciidoc[]
include::functions/asin.asciidoc[]
include::functions/atan.asciidoc[]
include::functions/atan2.asciidoc[]
include::functions/auto_bucket.asciidoc[]
include::functions/case.asciidoc[]
include::functions/ceil.asciidoc[]
include::functions/cidr_match.asciidoc[]
include::functions/coalesce.asciidoc[]
include::functions/concat.asciidoc[]
include::functions/cos.asciidoc[]
include::functions/cosh.asciidoc[]
include::functions/date_extract.asciidoc[]
include::functions/date_format.asciidoc[]
include::functions/date_parse.asciidoc[]
include::functions/date_trunc.asciidoc[]
include::functions/e.asciidoc[]
include::functions/floor.asciidoc[]
include::functions/greatest.asciidoc[]
include::functions/is_finite.asciidoc[]
include::functions/is_infinite.asciidoc[]
include::functions/is_nan.asciidoc[]
include::functions/least.asciidoc[]
include::functions/length.asciidoc[]
include::functions/log10.asciidoc[]
include::functions/ltrim.asciidoc[]
include::functions/rtrim.asciidoc[]
include::functions/mv_avg.asciidoc[]
include::functions/mv_concat.asciidoc[]
include::functions/mv_count.asciidoc[]
include::functions/mv_dedupe.asciidoc[]
include::functions/mv_max.asciidoc[]
include::functions/mv_median.asciidoc[]
include::functions/mv_min.asciidoc[]
include::functions/mv_sum.asciidoc[]
include::functions/now.asciidoc[]
include::functions/pi.asciidoc[]
include::functions/pow.asciidoc[]
include::functions/round.asciidoc[]
include::functions/sin.asciidoc[]
include::functions/sinh.asciidoc[]
include::functions/split.asciidoc[]
include::functions/sqrt.asciidoc[]
include::functions/starts_with.asciidoc[]
include::functions/substring.asciidoc[]
include::functions/left.asciidoc[]
include::functions/tan.asciidoc[]
include::functions/tanh.asciidoc[]
include::functions/tau.asciidoc[]
include::functions/to_boolean.asciidoc[]
include::functions/to_datetime.asciidoc[]
include::functions/to_degrees.asciidoc[]
include::functions/to_double.asciidoc[]
include::functions/to_integer.asciidoc[]
include::functions/to_ip.asciidoc[]
include::functions/to_long.asciidoc[]
include::functions/to_radians.asciidoc[]
include::functions/to_string.asciidoc[]
include::functions/to_unsigned_long.asciidoc[]
include::functions/to_version.asciidoc[]
include::functions/trim.asciidoc[]

View file

@ -0,0 +1,39 @@
[[esql-processing-commands]]
== {esql} processing commands
++++
<titleabbrev>Processing commands</titleabbrev>
++++
{esql} processing commands change an input table by adding, removing, or changing
rows and columns.
image::images/esql/processing-command.svg[A processing command changing an input table,align="center"]
{esql} supports these processing commands:
* <<esql-dissect>>
* <<esql-drop>>
* <<esql-enrich>>
* <<esql-eval>>
* <<esql-grok>>
* <<esql-keep>>
* <<esql-limit>>
* <<esql-mv_expand>>
* <<esql-rename>>
* <<esql-sort>>
* <<esql-stats-by>>
* <<esql-where>>
include::processing-commands/dissect.asciidoc[]
include::processing-commands/drop.asciidoc[]
include::processing-commands/enrich.asciidoc[]
include::processing-commands/eval.asciidoc[]
include::processing-commands/grok.asciidoc[]
include::processing-commands/keep.asciidoc[]
include::processing-commands/limit.asciidoc[]
include::processing-commands/mv_expand.asciidoc[]
include::processing-commands/rename.asciidoc[]
include::processing-commands/sort.asciidoc[]
include::processing-commands/stats.asciidoc[]
include::processing-commands/where.asciidoc[]

View file

@ -0,0 +1,20 @@
[[esql-source-commands]]
== {esql} source commands
++++
<titleabbrev>Source commands</titleabbrev>
++++
An {esql} source command produces a table, typically with data from {es}.
image::images/esql/source-command.svg[A source command producing a table from {es},align="center"]
{esql} supports these source commands:
* <<esql-from>>
* <<esql-row>>
* <<esql-show>>
include::source-commands/from.asciidoc[]
include::source-commands/row.asciidoc[]
include::source-commands/show.asciidoc[]

View file

@ -0,0 +1,162 @@
[[esql-syntax]]
== {esql} syntax reference
++++
<titleabbrev>Syntax reference</titleabbrev>
++++
[discrete]
[[esql-basic-syntax]]
=== Basic syntax
An {esql} query is composed of a <<esql-source-commands,source command>> followed
by an optional series of <<esql-processing-commands,processing commands>>,
separated by a pipe character: `|`. For example:
[source,esql]
----
source-command
| processing-command1
| processing-command2
----
The result of a query is the table produced by the final processing command.
For readability, this documentation puts each processing command on a new line.
However, you can write an {esql} query as a single line. The following query is
identical to the previous one:
[source,esql]
----
source-command | processing-command1 | processing-command2
----
[discrete]
[[esql-comments]]
=== Comments
{esql} uses C++ style comments:
* double slash `//` for single line comments
* `/*` and `*/` for block comments
[source,esql]
----
// Query the employees index
FROM employees
| WHERE height > 2
----
[source,esql]
----
FROM /* Query the employees index */ employees
| WHERE height > 2
----
[source,esql]
----
FROM employees
/* Query the
* employees
* index */
| WHERE height > 2
----
[discrete]
[[esql-operators]]
=== Operators
These binary comparison operators are supported:
* equality: `==`
* inequality: `!=`
* less than: `<`
* less than or equal: `<=`
* larger than: `>`
* larger than or equal: `>=`
The `IN` operator allows testing whether a field or expression equals
an element in a list of literals, fields or expressions:
[source,esql]
----
include::{esql-specs}/row.csv-spec[tag=in-with-expressions]
----
For string comparison using wildcards or regular expressions, use `LIKE` or
`RLIKE`:
* Use `LIKE` to match strings using wildcards. The following wildcard characters
are supported:
+
--
** `*` matches zero or more characters.
** `?` matches one character.
[source,esql]
----
FROM employees
| WHERE first_name LIKE "?b*"
| KEEP first_name, last_name
----
--
* Use `RLIKE` to match strings using <<regexp-syntax,regular expressions>>:
+
[source,esql]
----
FROM employees
| WHERE first_name RLIKE ".leja.*"
| KEEP first_name, last_name
----
The following boolean operators are supported:
* `AND`
* `OR`
* `NOT`
[discrete]
[[esql-predicates]]
=== Predicates
For NULL comparison use the `IS NULL` and `IS NOT NULL` predicates:
[source.merge.styled,esql]
----
include::{esql-specs}/null.csv-spec[tag=is-null]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/null.csv-spec[tag=is-null-result]
|===
[source.merge.styled,esql]
----
include::{esql-specs}/null.csv-spec[tag=is-not-null]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/null.csv-spec[tag=is-not-null-result]
|===
[discrete]
[[esql-timespan-literals]]
=== Timespan literals
Datetime intervals and timespans can be expressed using timespan literals.
Timespan literals are a combination of a number and a qualifier. These
qualifiers are supported:
* `millisecond`/`milliseconds`
* `second`/`seconds`
* `minute`/`minutes`
* `hour`/`hours`
* `day`/`days`
* `week`/`weeks`
* `month`/`months`
* `year`/`years`
Timespan literals are not whitespace sensitive. These expressions are all valid:
* `1day`
* `1 day`
* `1 day`

View file

@ -0,0 +1,17 @@
[[esql-abs]]
=== `ABS`
[.text-center]
image::esql/functions/signature/abs.svg[Embedded,opts=inline]
Returns the absolute value.
[source,esql]
----
FROM employees
| KEEP first_name, last_name, height
| EVAL abs_height = ABS(0.0 - height)
----
Supported types:
include::types/abs.asciidoc[]

View file

@ -0,0 +1,19 @@
[[esql-acos]]
=== `ACOS`
[.text-center]
image::esql/functions/signature/acos.svg[Embedded,opts=inline]
Inverse https://en.wikipedia.org/wiki/Inverse_trigonometric_functions[cosine] trigonometric function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=acos]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=acos-result]
|===
Supported types:
include::types/acos.asciidoc[]

View file

@ -0,0 +1,19 @@
[[esql-asin]]
=== `ASIN`
[.text-center]
image::esql/functions/signature/asin.svg[Embedded,opts=inline]
Inverse https://en.wikipedia.org/wiki/Inverse_trigonometric_functions[sine] trigonometric function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=asin]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=asin-result]
|===
Supported types:
include::types/asin.asciidoc[]

View file

@ -0,0 +1,19 @@
[[esql-atan]]
=== `ATAN`
[.text-center]
image::esql/functions/signature/atan.svg[Embedded,opts=inline]
Inverse https://en.wikipedia.org/wiki/Inverse_trigonometric_functions[tangent] trigonometric function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=atan]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=atan-result]
|===
Supported types:
include::types/atan.asciidoc[]

View file

@ -0,0 +1,20 @@
[[esql-atan2]]
=== `ATAN2`
[.text-center]
image::esql/functions/signature/atan2.svg[Embedded,opts=inline]
The https://en.wikipedia.org/wiki/Atan2[angle] between the positive x-axis and the
ray from the origin to the point (x , y) in the Cartesian plane.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=atan2]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=atan2-result]
|===
Supported types:
include::types/atan2.asciidoc[]

View file

@ -0,0 +1,71 @@
[[esql-auto_bucket]]
=== `AUTO_BUCKET`
Creates human-friendly buckets and returns a `datetime` value for each row that
corresponds to the resulting bucket the row falls into. Combine `AUTO_BUCKET`
with <<esql-stats-by>> to create a date histogram.
You provide a target number of buckets, a start date, and an end date, and it
picks an appropriate bucket size to generate the target number of buckets or
fewer. For example, this asks for at most 20 buckets over a whole year, which
picks monthly buckets:
[source.merge.styled,esql]
----
include::{esql-specs}/date.csv-spec[tag=auto_bucket_month]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/date.csv-spec[tag=auto_bucket_month-result]
|===
The goal isn't to provide *exactly* the target number of buckets, it's to pick a
range that people are comfortable with that provides at most the target number of
buckets.
If you ask for more buckets then `AUTO_BUCKET` can pick a smaller range. For example,
asking for at most 100 buckets in a year will get you week long buckets:
[source.merge.styled,esql]
----
include::{esql-specs}/date.csv-spec[tag=auto_bucket_week]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/date.csv-spec[tag=auto_bucket_week-result]
|===
`AUTO_BUCKET` does not filter any rows. It only uses the provided time range to
pick a good bucket size. For rows with a date outside of the range, it returns a
`datetime` that corresponds to a bucket outside the range. Combine `AUTO_BUCKET`
with <<esql-where>> to filter rows.
A more complete example might look like:
[source.merge.styled,esql]
----
include::{esql-specs}/date.csv-spec[tag=auto_bucket_in_agg]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/date.csv-spec[tag=auto_bucket_in_agg-result]
|===
NOTE: `AUTO_BUCKET` does not create buckets that don't match any documents. That's
why the example above is missing `1985-02-01` and other dates.
==== Numeric fields
`auto_bucket` can also operate on numeric fields like this:
[source.merge.styled,esql]
----
include::{esql-specs}/ints.csv-spec[tag=auto_bucket]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/ints.csv-spec[tag=auto_bucket-result]
|===
Unlike the example above where you are intentionally filtering on a date range,
you rarely want to filter on a numeric range. So you have find the `min` and `max`
separately. We don't yet have an easy way to do that automatically. Improvements
coming!

View file

@ -0,0 +1,17 @@
[[esql-case]]
=== `CASE`
Accepts pairs of conditions and values. The function returns the value that
belongs to the first condition that evaluates to `true`. If the number of
arguments is odd, the last argument is the default value which is returned when
no condition matches.
[source,esql]
----
FROM employees
| EVAL type = CASE(
languages <= 1, "monolingual",
languages <= 2, "bilingual",
"polyglot")
| KEEP first_name, last_name, type
----

View file

@ -0,0 +1,23 @@
[[esql-ceil]]
=== `CEIL`
[.text-center]
image::esql/functions/signature/floor.svg[Embedded,opts=inline]
Round a number up to the nearest integer.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=ceil]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=ceil-result]
|===
NOTE: This is a noop for `long` (including unsigned) and `integer`.
For `double` this picks the the closest `double` value to the integer ala
{javadoc}/java.base/java/lang/Math.html#ceil(double)[Math.ceil].
Supported types:
include::types/ceil.asciidoc[]

View file

@ -0,0 +1,15 @@
[[esql-cidr_match]]
=== `CIDR_MATCH`
Returns `true` if the provided IP is contained in one of the provided CIDR
blocks.
`CIDR_MATCH` accepts two or more arguments. The first argument is the IP
address of type `ip` (both IPv4 and IPv6 are supported). Subsequent arguments
are the CIDR blocks to test the IP against.
[source,esql]
----
FROM hosts
| WHERE CIDR_MATCH(ip, "127.0.0.2/32", "127.0.0.3/32")
----

View file

@ -0,0 +1,13 @@
[[esql-coalesce]]
=== `COALESCE`
Returns the first non-null value.
[source.merge.styled,esql]
----
include::{esql-specs}/null.csv-spec[tag=coalesce]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/null.csv-spec[tag=coalesce-result]
|===

View file

@ -0,0 +1,10 @@
[[esql-concat]]
=== `CONCAT`
Concatenates two or more strings.
[source,esql]
----
FROM employees
| KEEP first_name, last_name, height
| EVAL fullname = CONCAT(first_name, " ", last_name)
----

View file

@ -0,0 +1,19 @@
[[esql-cos]]
=== `COS`
[.text-center]
image::esql/functions/signature/cos.svg[Embedded,opts=inline]
https://en.wikipedia.org/wiki/Sine_and_cosine[Cosine] trigonometric function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=cos]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=cos-result]
|===
Supported types:
include::types/cos.asciidoc[]

View file

@ -0,0 +1,19 @@
[[esql-cosh]]
=== `COSH`
[.text-center]
image::esql/functions/signature/cosh.svg[Embedded,opts=inline]
https://en.wikipedia.org/wiki/Hyperbolic_functions[Cosine] hyperbolic function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=cosh]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=cosh-result]
|===
Supported types:
include::types/cosh.asciidoc[]

View file

@ -0,0 +1,14 @@
[[esql-date_extract]]
=== `DATE_EXTRACT`
Extracts parts of a date, like year, month, day, hour.
The supported field types are those provided by https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html[java.time.temporal.ChronoField]
[source.merge.styled,esql]
----
include::{esql-specs}/docs.csv-spec[tag=dateExtract]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/docs.csv-spec[tag=dateExtract-result]
|===

View file

@ -0,0 +1,11 @@
[[esql-date_format]]
=== `DATE_FORMAT`
Returns a string representation of a date in the provided format. If no format
is specified, the `yyyy-MM-dd'T'HH:mm:ss.SSSZ` format is used.
[source,esql]
----
FROM employees
| KEEP first_name, last_name, hire_date
| EVAL hired = DATE_FORMAT(hire_date, "YYYY-MM-dd")
----

View file

@ -0,0 +1,9 @@
[[esql-date_parse]]
=== `DATE_PARSE`
Converts a string to a date, in the provided format. If no format
is specified, the `yyyy-MM-dd'T'HH:mm:ss.SSSZ` format is used.
[source.merge.styled,esql]
----
include::{esql-specs}/docs.csv-spec[tag=dateParse]
----

View file

@ -0,0 +1,12 @@
[[esql-date_trunc]]
=== `DATE_TRUNC`
Rounds down a date to the closest interval. Intervals can be expressed using the
<<esql-timespan-literals,timespan literal syntax>>.
[source,esql]
----
FROM employees
| EVAL year_hired = DATE_TRUNC(1 year, hire_date)
| STATS count(emp_no) BY year_hired
| SORT year_hired
----

View file

@ -0,0 +1,15 @@
[[esql-e]]
=== `E`
[.text-center]
image::esql/functions/signature/e.svg[Embedded,opts=inline]
{wikipedia}/E_(mathematical_constant)[Euler's number].
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=e]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=e-result]
|===

View file

@ -0,0 +1,23 @@
[[esql-floor]]
=== `FLOOR`
[.text-center]
image::esql/functions/signature/floor.svg[Embedded,opts=inline]
Round a number down to the nearest integer.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=floor]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=floor-result]
|===
NOTE: This is a noop for `long` (including unsigned) and `integer`.
For `double` this picks the the closest `double` value to the integer ala
{javadoc}/java.base/java/lang/Math.html#floor(double)[Math.floor].
Supported types:
include::types/floor.asciidoc[]

View file

@ -0,0 +1,24 @@
[[esql-greatest]]
=== `GREATEST`
[.text-center]
image::esql/functions/signature/greatest.svg[Embedded,opts=inline]
Returns the maximum value from many columns. This is similar to <<esql-mv_max>>
except it's intended to run on multiple columns at once.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=greatest]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=greatest-result]
|===
NOTE: When run on `keyword` or `text` fields, this'll return the last string
in alphabetical order. When run on `boolean` columns this will return
`true` if any values are `true`.
Supported types:
include::types/greatest.asciidoc[]

View file

@ -0,0 +1,9 @@
[[esql-is_finite]]
=== `IS_FINITE`
Returns a boolean that indicates whether its input is a finite number.
[source,esql]
----
ROW d = 1.0
| EVAL s = IS_FINITE(d/0)
----

View file

@ -0,0 +1,9 @@
[[esql-is_infinite]]
=== `IS_INFINITE`
Returns a boolean that indicates whether its input is infinite.
[source,esql]
----
ROW d = 1.0
| EVAL s = IS_INFINITE(d/0)
----

View file

@ -0,0 +1,9 @@
[[esql-is_nan]]
=== `IS_NAN`
Returns a boolean that indicates whether its input is not a number.
[source,esql]
----
ROW d = 1.0
| EVAL s = IS_NAN(d)
----

View file

@ -0,0 +1,24 @@
[[esql-least]]
=== `LEAST`
[.text-center]
image::esql/functions/signature/least.svg[Embedded,opts=inline]
Returns the minimum value from many columns. This is similar to <<esql-mv_min>>
except it's intended to run on multiple columns at once.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=least]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=least-result]
|===
NOTE: When run on `keyword` or `text` fields, this'll return the first string
in alphabetical order. When run on `boolean` columns this will return
`false` if any values are `false`.
Supported types:
include::types/least.asciidoc[]

View file

@ -0,0 +1,14 @@
[[esql-left]]
=== `LEFT`
Return the substring that extract 'length' chars
from string starting from 0.
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=left]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=left-result]
|===

View file

@ -0,0 +1,10 @@
[[esql-length]]
=== `LENGTH`
Returns the character length of a string.
[source,esql]
----
FROM employees
| KEEP first_name, last_name, height
| EVAL fn_length = LENGTH(first_name)
----

View file

@ -0,0 +1,22 @@
[[esql-log10]]
=== `LOG10`
[.text-center]
image::esql/functions/signature/log10.svg[Embedded,opts=inline]
Returns the log base 10. The input can be any numeric value, the return value
is always a double.
Logs of negative numbers are NaN. Logs of infinites are infinite, as is the log of 0.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=log10]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=log10-result]
|===
Supported types:
include::types/log10.asciidoc[]

View file

@ -0,0 +1,12 @@
[[esql-ltrim]]
=== `LTRIM`
Removes leading whitespaces from strings.
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=ltrim]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=ltrim-result]
|===

View file

@ -0,0 +1,16 @@
[[esql-mv_avg]]
=== `MV_AVG`
Converts a multivalued field into a single valued field containing the average
of all of the values. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=mv_avg]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=mv_avg-result]
|===
NOTE: The output type is always a `double` and the input type can be any number.

View file

@ -0,0 +1,25 @@
[[esql-mv_concat]]
=== `MV_CONCAT`
Converts a multivalued string field into a single valued field containing the
concatenation of all values separated by a delimiter:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=mv_concat]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=mv_concat-result]
|===
If you want to concat non-string fields call <<esql-to_string>> on them first:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=mv_concat-to_string]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=mv_concat-to_string-result]
|===

View file

@ -0,0 +1,15 @@
[[esql-mv_count]]
=== `MV_COUNT`
Converts a multivalued field into a single valued field containing a count of the number
of values:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=mv_count]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=mv_count-result]
|===
NOTE: This function accepts all types and always returns an `integer`.

View file

@ -0,0 +1,14 @@
[[esql-mv_dedupe]]
=== `MV_DEDUPE`
Removes duplicates from a multivalued field. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=mv_dedupe]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=mv_dedupe-result]
|===
NOTE: `MV_DEDUPE` may, but won't always, sort the values in the field.

View file

@ -0,0 +1,24 @@
[[esql-mv_max]]
=== `MV_MAX`
Converts a multivalued field into a single valued field containing the maximum value. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=mv_max]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=mv_max-result]
|===
It can be used by any field type, including `keyword` fields. In that case picks the
last string, comparing their utf-8 representation byte by byte:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=mv_max]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=mv_max-result]
|===

View file

@ -0,0 +1,26 @@
[[esql-mv_median]]
=== `MV_MEDIAN`
Converts a multivalued field into a single valued field containing the median value. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=mv_median]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=mv_median-result]
|===
It can be used by any numeric field type and returns a value of the same type. If the
row has an even number of values for a column the result will be the average of the
middle two entries. If the field is not floating point then the average rounds *down*:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=mv_median_round_down]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=mv_median_round_down-result]
|===

View file

@ -0,0 +1,24 @@
[[esql-mv_min]]
=== `MV_MIN`
Converts a multivalued field into a single valued field containing the minimum value. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=mv_min]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=mv_min-result]
|===
It can be used by any field type, including `keyword` fields. In that case picks the
first string, comparing their utf-8 representation byte by byte:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=mv_min]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=mv_min-result]
|===

View file

@ -0,0 +1,15 @@
[[esql-mv_sum]]
=== `MV_SUM`
Converts a multivalued field into a single valued field containing the sum
of all of the values. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=mv_sum]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=mv_sum-result]
|===
NOTE: The input type can be any number and the output type is the same as the input type.

View file

@ -0,0 +1,8 @@
[[esql-now]]
=== `NOW`
Returns current date and time.
[source,esql]
----
ROW current_date = NOW()
----

View file

@ -0,0 +1,15 @@
[[esql-pi]]
=== `PI`
[.text-center]
image::esql/functions/signature/pi.svg[Embedded,opts=inline]
The {wikipedia}/Pi[ratio] of a circle's circumference to its diameter.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=pi]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=pi-result]
|===

View file

@ -0,0 +1,91 @@
[[esql-pow]]
=== `POW`
[.text-center]
image::esql/functions/signature/pow.svg[Embedded,opts=inline]
Returns the value of a base (first argument) raised to the power of an exponent (second argument).
Both arguments must be numeric.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=powDI]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=powDI-result]
|===
==== Type rules
The type of the returned value is determined by the types of the base and exponent.
The following rules are applied to determine the result type:
* If either of the base or exponent are of a floating point type, the result will be a double
* Otherwise, if either the base or the exponent are 64-bit (long or unsigned long), the result will be a long
* Otherwise, the result will be a 32-bit integer (this covers all other numeric types, including int, short and byte)
For example, using simple integers as arguments will lead to an integer result:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=powII]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=powII-result]
|===
NOTE: The actual power function is performed using double precision values for all cases.
This means that for very large non-floating point values there is a small chance that the
operation can lead to slightly different answers than expected.
However, a more likely outcome of very large non-floating point values is numerical overflow.
==== Arithmetic errors
Arithmetic errors and numeric overflow do not result in an error. Instead, the result will be `null`
and a warning for the `ArithmeticException` added.
For example:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=powULOverrun]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=powULOverrun-warning]
|===
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=powULOverrun-result]
|===
If it is desired to protect against numerical overruns, use `to_double` on either of the arguments:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=pow2d]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=pow2d-result]
|===
==== Fractional exponents
The exponent can be a fraction, which is similar to performing a root.
For example, the exponent of `0.5` will give the square root of the base:
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=powID-sqrt]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=powID-sqrt-result]
|===
==== Table of supported input and output types
For clarity, the following table describes the output result type for all combinations of numeric input types:
include::types/pow.asciidoc[]

View file

@ -0,0 +1,14 @@
[[esql-round]]
=== `ROUND`
Rounds a number to the closest number with the specified number of digits.
Defaults to 0 digits if no number of digits is provided. If the specified number
of digits is negative, rounds to the number of digits left of the decimal point.
[source.merge.styled,esql]
----
include::{esql-specs}/docs.csv-spec[tag=round]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/docs.csv-spec[tag=round-result]
|===

View file

@ -0,0 +1,12 @@
[[esql-rtrim]]
=== `RTRIM`
Removes trailing whitespaces from strings.
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=rtrim]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=rtrim-result]
|===

View file

@ -0,0 +1,19 @@
[[esql-sin]]
=== `SIN`
[.text-center]
image::esql/functions/signature/sin.svg[Embedded,opts=inline]
https://en.wikipedia.org/wiki/Sine_and_cosine[Sine] trigonometric function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=sin]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=sin-result]
|===
Supported types:
include::types/sin.asciidoc[]

View file

@ -0,0 +1,19 @@
[[esql-sinh]]
=== `SINH`
[.text-center]
image::esql/functions/signature/sinh.svg[Embedded,opts=inline]
https://en.wikipedia.org/wiki/Hyperbolic_functions[Sine] hyperbolic function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=sinh]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=sinh-result]
|===
Supported types:
include::types/sinh.asciidoc[]

View file

@ -0,0 +1,17 @@
[[esql-split]]
=== `SPLIT`
Split a single valued string into multiple strings. For example:
[source,esql]
----
include::{esql-specs}/string.csv-spec[tag=split]
----
Which splits `"foo;bar;baz;qux;quux;corge"` on `;` and returns an array:
[%header,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=split-result]
|===
WARNING: Only single byte delimiters are currently supported.

View file

@ -0,0 +1,22 @@
[[esql-sqrt]]
=== `SQRT`
[.text-center]
image::esql/functions/signature/sqrt.svg[Embedded,opts=inline]
Returns the square root of a number. The input can be any numeric value, the return value
is always a double.
Square roots of negative numbers are NaN. Square roots of infinites are infinite.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=sqrt]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=sqrt-result]
|===
Supported types:
include::types/sqrt.asciidoc[]

View file

@ -0,0 +1,13 @@
[[esql-starts_with]]
=== `STARTS_WITH`
Returns a boolean that indicates whether a keyword string starts with another
string:
[source.merge.styled,esql]
----
include::{esql-specs}/docs.csv-spec[tag=startsWith]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/docs.csv-spec[tag=startsWith-result]
|===

View file

@ -0,0 +1,37 @@
[[esql-substring]]
=== `SUBSTRING`
Returns a substring of a string, specified by a start position and an optional
length. This example returns the first three characters of every last name:
[source.merge.styled,esql]
----
include::{esql-specs}/docs.csv-spec[tag=substring]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/docs.csv-spec[tag=substring-result]
|===
A negative start position is interpreted as being relative to the end of the
string. This example returns the last three characters of of every last name:
[source.merge.styled,esql]
----
include::{esql-specs}/docs.csv-spec[tag=substringEnd]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/docs.csv-spec[tag=substringEnd-result]
|===
If length is omitted, substring returns the remainder of the string. This
example returns all characters except for the first:
[source.merge.styled,esql]
----
include::{esql-specs}/docs.csv-spec[tag=substringRemainder]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/docs.csv-spec[tag=substringRemainder-result]
|===

View file

@ -0,0 +1,19 @@
[[esql-tan]]
=== `TAN`
[.text-center]
image::esql/functions/signature/tan.svg[Embedded,opts=inline]
https://en.wikipedia.org/wiki/Sine_and_cosine[Tangent] trigonometric function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=tan]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=tan-result]
|===
Supported types:
include::types/tan.asciidoc[]

View file

@ -0,0 +1,19 @@
[[esql-tanh]]
=== `TANH`
[.text-center]
image::esql/functions/signature/tanh.svg[Embedded,opts=inline]
https://en.wikipedia.org/wiki/Hyperbolic_functions[Tangent] hyperbolic function.
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=tanh]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=tanh-result]
|===
Supported types:
include::types/tanh.asciidoc[]

View file

@ -0,0 +1,15 @@
[[esql-tau]]
=== `TAU`
[.text-center]
image::esql/functions/signature/tau.svg[Embedded,opts=inline]
The https://tauday.com/tau-manifesto[ratio] of a circle's circumference to its radius.
[source.merge.styled,esql]
----
include::{esql-specs}/math.csv-spec[tag=tau]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/math.csv-spec[tag=tau-result]
|===

View file

@ -0,0 +1,24 @@
[[esql-to_boolean]]
=== `TO_BOOLEAN`
Converts an input value to a boolean value.
The input can be a single- or multi-valued field or an expression. The input
type must be of a string or numeric type.
A string value of *"true"* will be case-insensitive converted to the Boolean
*true*. For anything else, including the empty string, the function will
return *false*. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/boolean.csv-spec[tag=to_boolean]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/boolean.csv-spec[tag=to_boolean-result]
|===
The numerical value of *0* will be converted to *false*, anything else will be
converted to *true*.
Alias: TO_BOOL

View file

@ -0,0 +1,46 @@
[[esql-to_datetime]]
=== `TO_DATETIME`
Converts an input value to a date value.
The input can be a single- or multi-valued field or an expression. The input
type must be of a string or numeric type.
A string will only be successfully converted if it's respecting the format
`yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` (to convert dates in other formats, use <<esql-date_parse>>). For example:
[source.merge.styled,esql]
----
include::{esql-specs}/date.csv-spec[tag=to_datetime-str]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/date.csv-spec[tag=to_datetime-str-result]
|===
Note that in this example, the last value in the source multi-valued
field has not been converted. The reason being that if the date format is not
respected, the conversion will result in a *null* value. When this happens a
_Warning_ header is added to the response. The header will provide information
on the source of the failure:
`"Line 1:112: evaluation of [TO_DATETIME(string)] failed, treating result as null. Only first 20 failures recorded."`
A following header will contain the failure reason and the offending value:
`"java.lang.IllegalArgumentException: failed to parse date field [1964-06-02 00:00:00] with format [yyyy-MM-dd'T'HH:mm:ss.SSS'Z']"`
If the input parameter is of a numeric type, its value will be interpreted as
milliseconds since the https://en.wikipedia.org/wiki/Unix_time[Unix epoch].
For example:
[source.merge.styled,esql]
----
include::{esql-specs}/date.csv-spec[tag=to_datetime-int]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/date.csv-spec[tag=to_datetime-int-result]
|===
Alias: TO_DT

View file

@ -0,0 +1,18 @@
[[esql-to_degrees]]
=== `TO_DEGREES`
Converts a number in https://en.wikipedia.org/wiki/Radian[radians]
to https://en.wikipedia.org/wiki/Degree_(angle)[degrees].
The input can be a single- or multi-valued field or an expression. The input
type must be of a numeric type and result is always `double`.
Example:
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=to_degrees]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=to_degrees-result]
|===

View file

@ -0,0 +1,37 @@
[[esql-to_double]]
=== `TO_DOUBLE`
Converts an input value to a double value.
The input can be a single- or multi-valued field or an expression. The input
type must be of a boolean, date, string or numeric type.
Example:
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=to_double-str]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=to_double-str-result]
|===
Note that in this example, the last conversion of the string isn't
possible. When this happens, the result is a *null* value. In this case a
_Warning_ header is added to the response. The header will provide information
on the source of the failure:
`"Line 1:115: evaluation of [TO_DOUBLE(str2)] failed, treating result as null. Only first 20 failures recorded."`
A following header will contain the failure reason and the offending value:
`"java.lang.NumberFormatException: For input string: \"foo\""`
If the input parameter is of a date type, its value will be interpreted as
milliseconds since the https://en.wikipedia.org/wiki/Unix_time[Unix epoch],
converted to double.
Boolean *true* will be converted to double *1.0*, *false* to *0.0*.
Alias: TO_DBL

View file

@ -0,0 +1,37 @@
[[esql-to_integer]]
=== `TO_INTEGER`
Converts an input value to an integer value.
The input can be a single- or multi-valued field or an expression. The input
type must be of a boolean, date, string or numeric type.
Example:
[source.merge.styled,esql]
----
include::{esql-specs}/ints.csv-spec[tag=to_int-long]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/ints.csv-spec[tag=to_int-long-result]
|===
Note that in this example, the last value of the multi-valued field cannot
be converted as an integer. When this happens, the result is a *null* value.
In this case a _Warning_ header is added to the response. The header will
provide information on the source of the failure:
`"Line 1:61: evaluation of [TO_INTEGER(long)] failed, treating result as null. Only first 20 failures recorded."`
A following header will contain the failure reason and the offending value:
`"org.elasticsearch.xpack.ql.QlIllegalArgumentException: [501379200000] out of [integer] range"`
If the input parameter is of a date type, its value will be interpreted as
milliseconds since the https://en.wikipedia.org/wiki/Unix_time[Unix epoch],
converted to integer.
Boolean *true* will be converted to integer *1*, *false* to *0*.
Alias: TO_INT

View file

@ -0,0 +1,27 @@
[[esql-to_ip]]
=== `TO_IP`
Converts an input string to an IP value.
The input can be a single- or multi-valued field or an expression.
Example:
[source.merge.styled,esql]
----
include::{esql-specs}/ip.csv-spec[tag=to_ip]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/ip.csv-spec[tag=to_ip-result]
|===
Note that in the example above the last conversion of the string isn't
possible. When this happens, the result is a *null* value. In this case a
_Warning_ header is added to the response. The header will provide information
on the source of the failure:
`"Line 1:68: evaluation of [TO_IP(str2)] failed, treating result as null. Only first 20 failures recorded."`
A following header will contain the failure reason and the offending value:
`"java.lang.IllegalArgumentException: 'foo' is not an IP string literal."`

View file

@ -0,0 +1,35 @@
[[esql-to_long]]
=== `TO_LONG`
Converts an input value to a long value.
The input can be a single- or multi-valued field or an expression. The input
type must be of a boolean, date, string or numeric type.
Example:
[source.merge.styled,esql]
----
include::{esql-specs}/ints.csv-spec[tag=to_long-str]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/ints.csv-spec[tag=to_long-str-result]
|===
Note that in this example, the last conversion of the string isn't
possible. When this happens, the result is a *null* value. In this case a
_Warning_ header is added to the response. The header will provide information
on the source of the failure:
`"Line 1:113: evaluation of [TO_LONG(str3)] failed, treating result as null. Only first 20 failures recorded."`
A following header will contain the failure reason and the offending value:
`"java.lang.NumberFormatException: For input string: \"foo\""`
If the input parameter is of a date type, its value will be interpreted as
milliseconds since the https://en.wikipedia.org/wiki/Unix_time[Unix epoch],
converted to long.
Boolean *true* will be converted to long *1*, *false* to *0*.

View file

@ -0,0 +1,18 @@
[[esql-to_radians]]
=== `TO_RADIANS`
Converts a number in https://en.wikipedia.org/wiki/Degree_(angle)[degrees] to
https://en.wikipedia.org/wiki/Radian[radians].
The input can be a single- or multi-valued field or an expression. The input
type must be of a numeric type and result is always `double`.
Example:
[source.merge.styled,esql]
----
include::{esql-specs}/floats.csv-spec[tag=to_radians]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/floats.csv-spec[tag=to_radians-result]
|===

View file

@ -0,0 +1,25 @@
[[esql-to_string]]
=== `TO_STRING`
Converts a field into a string. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=to_string]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=to_string-result]
|===
It also works fine on multivalued fields:
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=to_string_multivalue]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=to_string_multivalue-result]
|===
Alias: TO_STR

View file

@ -0,0 +1,37 @@
[[esql-to_unsigned_long]]
=== `TO_UNSIGNED_LONG`
Converts an input value to an unsigned long value.
The input can be a single- or multi-valued field or an expression. The input
type must be of a boolean, date, string or numeric type.
Example:
[source.merge.styled,esql]
----
include::{esql-specs}/ints.csv-spec[tag=to_unsigned_long-str]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/ints.csv-spec[tag=to_unsigned_long-str-result]
|===
Note that in this example, the last conversion of the string isn't
possible. When this happens, the result is a *null* value. In this case a
_Warning_ header is added to the response. The header will provide information
on the source of the failure:
`"Line 1:133: evaluation of [TO_UL(str3)] failed, treating result as null. Only first 20 failures recorded."`
A following header will contain the failure reason and the offending value:
`"java.lang.NumberFormatException: Character f is neither a decimal digit number, decimal point, nor \"e\" notation exponential mark."`
If the input parameter is of a date type, its value will be interpreted as
milliseconds since the https://en.wikipedia.org/wiki/Unix_time[Unix epoch],
converted to unsigned long.
Boolean *true* will be converted to unsigned long *1*, *false* to *0*.
Alias: TO_ULONG, TO_UL

View file

@ -0,0 +1,16 @@
[[esql-to_version]]
=== `TO_VERSION`
Converts an input string to a version value. For example:
[source.merge.styled,esql]
----
include::{esql-specs}/version.csv-spec[tag=to_version]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/version.csv-spec[tag=to_version-result]
|===
The input can be a single- or multi-valued field or an expression.
Alias: TO_VER

View file

@ -0,0 +1,12 @@
[[esql-trim]]
=== `TRIM`
Removes leading and trailing whitespaces from strings.
[source.merge.styled,esql]
----
include::{esql-specs}/string.csv-spec[tag=trim]
----
[%header.monospaced.styled,format=dsv,separator=|]
|===
include::{esql-specs}/string.csv-spec[tag=trim-result]
|===

View file

@ -0,0 +1,8 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
n | result
double | double
integer | integer
long | long
unsigned_long | unsigned_long
|===

View file

@ -0,0 +1,8 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
n | result
double | double
integer | double
long | double
unsigned_long | double
|===

View file

@ -0,0 +1,8 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
n | result
double | double
integer | double
long | double
unsigned_long | double
|===

View file

@ -0,0 +1,8 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
n | result
double | double
integer | double
long | double
unsigned_long | double
|===

View file

@ -0,0 +1,20 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
y | x | result
double | double | double
double | integer | double
double | long | double
double | unsigned_long | double
integer | double | double
integer | integer | double
integer | long | double
integer | unsigned_long | double
long | double | double
long | integer | double
long | long | double
long | unsigned_long | double
unsigned_long | double | double
unsigned_long | integer | double
unsigned_long | long | double
unsigned_long | unsigned_long | double
|===

View file

@ -0,0 +1,5 @@
[%header.monospaced.styled,format=dsv,separator=|]
|===
arg1 | arg2... | result
|===

Some files were not shown because too many files have changed in this diff Show more