Creates a custom HTTP handler defined from SQL, without editing the server configuration file. SQL-defined handlers are an alternative to the configuration-based HTTP interface handlers.
Syntax
CREATE HANDLER [IF NOT EXISTS] name [ON CLUSTER cluster]
[PROTOCOL protocol_name|ANY]
URL [PREFIX|REGEXP] '/path'
[METHODS (GET, POST)]
[TYPE query]
AS [SELECT|INSERT|...] ...Creates a handler with a specified name. The name is used for managing handlers with SQL queries, for diagnostic messages, and for ordering handlers.
Clauses
PROTOCOL— optional. If a protocol name is specified, the handler is active only for the specified composable protocol. Otherwise, the handler is active on all HTTP endpoints: the built-inhttp/httpsports and every HTTP-type composable protocol listener.PROTOCOL ANYexplicitly selects the latter default behavior; inALTER HANDLERit removes a previously set protocol restriction. A protocol literally namedanycan be referenced with back quotes:PROTOCOL `any`.URL— mandatory. Can be in the form of an exact URL, aURL PREFIX, or aURL REGEXP. For exact URLs and prefixes, ambiguity is checked at creation/alter time and an exception is thrown if there is ambiguity. For regexp, ambiguity cannot be checked. The URL is matched without the?query string and the#fragment identifier. AURL PREFIXis matched as a base path, on a path-segment boundary — the same semantics as theurl_prefixrule of configuration-defined handlers:URL PREFIX '/api/v1'matches/api/v1,/api/v1/and/api/v1/write, but not/api/v1beta. A trailing/in the prefix is ignored, so'/api/v1/'and'/api/v1'behave the same.METHODS— optional. The list of allowed HTTP methods. By default, it is onlyGET. The supported methods areGET,POST,PUTandDELETE. The mutating methodsPOST,PUTandDELETEare allowed to run modifying queries; the safe methods such asGETandHEADare always executed inreadonlymode. Consequently, a handler whose query modifies data (for exampleINSERTor DDL) must allow at least one mutating method - creating such a handler with only read-only methods (for example the defaultGET) throws an exception. Queries whose side effects survive thereadonlymode are a special case:BACKUPandRESTOREhave durable side effects, the session-mutating statementsSET,SET ROLE,USE,BEGIN TRANSACTION,COMMIT,ROLLBACKandSET TRANSACTION SNAPSHOTchange session or transaction state that persists across requests whensession_idis in use, andCREATE TEMPORARY TABLE/CREATE TEMPORARY VIEWcreate an object living in the session - yet thereadonlymode of safe methods blocks none of them. Mutations of an existing temporary table are equally unblocked by thereadonlymode, so queries that may target one are treated the same way: anINSERTwhose target table is not qualified with a database (an unqualified name may resolve to a session temporary table), aDROP TEMPORARY TABLE, aDROP TABLE/TRUNCATE TABLEof a table not qualified with a database, and anALTERof a table not qualified with a database (ALTER TEMPORARY TABLEis the same statement). A database-qualified target can never be a temporary table, so such queries are not subject to this rule. HTTP requires safe methods to be side-effect-free (a handler declared forGETis also served forHEAD, where the response body is suppressed and the effect would be invisible). So a handler running such a query must list only mutating methods - creating or altering it to include a safe method throws an exception. Composite statements are looked through: forstatement1 PARALLEL WITH statement2 ...andEXECUTE AS <user> <statement>the rules above apply to the wrapped statements, because they are the ones that run (each under a copy of the handler’s context, which keeps thereadonlymode). A bareEXECUTE AS <user>makes the whole session run as another user, so it counts as session-mutating itself. In addition, anyEXECUTE AShandler - bare or wrapping a statement - must allow at least one mutating method: impersonation needs theIMPERSONATEprivilege, which thereadonlymode of safe methods denies.TYPE— optional. The only supported type for now isquery.AS— the SQL query that will be invoked by this handler. The query can be parameterized. The query is parsed for syntactic correctness during handler creation/alter, but not analyzed - for example, the tables referenced by the query can be missing at the time of the handler creation. TheFORMATand similar clauses belong to the query, not to the wholeCREATE/ALTERstatement. The query can be put in parentheses for disambiguation. AnINSERTquery must not contain inline data after theVALUESorFORMATclause - creating or altering such a handler throws an exception, because the inline payload cannot be preserved in the handler definition; the data is expected to be provided in the HTTP body (or computed by anINSERT ... SELECT). A request to a handler whose query reads the body - anINSERTtaking its data from the body, or a query using the_request_bodyparameter - must declare its length: a non-chunked request without aContent-Lengthheader is answered with411 Length Required, because the body would otherwise be read until end of stream and a dropped connection would be accepted as a complete request. Every method of such a handler must also be body-carrying (POST,PUTorDELETE) - creating it with a safe method in theMETHODSclause (for example the defaultGET) throws an exception, because a safe method never supplies a request body and the query would silently read an empty one; a declaredGETis served forHEADtoo, so mixing safe and body-carrying methods would keep those invocations reachable. AnINSERT ... SELECTdoes not read the body (its data comes from theSELECT), so it is not subject to these requirements - unless itsSELECTreads from theinputtable function, which is fed from the request body. A body-readingINSERTmust be the handler’s own query:EXECUTE ASandPARALLEL WITHrun the statements they wrap without the request body, so wrapping one in them is rejected at creation instead of silently discarding every upload. A body-reading query also must not use the_request_bodyparameter: there is a single request body, and binding_request_bodyconsumes it before the query reads its input data, so such a handler is rejected at creation instead of silently losing every upload - use either the query’s own body input or_request_body, not both. Handlers that do not read the body have no such requirement; the body of a request to such a handler is ignored and is never appended to the handler’s query. The stored query text is re-parsed by the server with unlimited parser depth and backtracks whenever the handler is reloaded or invoked, so a handler created in a session with raisedmax_parser_depth/max_parser_backtracksstays loadable and invokable under ordinary session limits.
Priority
Handlers defined in the server configuration have priority over SQL-defined handlers. SQL-defined handlers are matched in the lexicographical order of their names.
Parameters
Query parameters for parameterized queries are supplied, just as with configuration-defined handlers, from:
- HTTP URL parameters in the query string, using the
param_<name>convention (for example?param_id=42binds{id:Type}); - named capture groups in a
URL REGEXP(for exampleURL REGEXP '/users/(?P<id>\d+)'binds{id:Type}); - form fields of the request body, for a handler whose query declares parameters: an
application/x-www-form-urlencodedbody (for examplecurl -d 'param_id=42') and the fields of amultipart/form-databody bind{name:Type}parameters the same way as URL parameters, on the body-carrying methodsPOST,PUTandDELETE. A parameter present both in the URL and in the body takes its value from the URL. A body parsed as a form is consumed by the handler layer: it is not fed to the query asINSERTdata. A handler whose only body use is_request_bodygets the raw body instead of form parsing; a handler that declares_request_bodyalongside other parameters gets both - a copy of the raw, unparsed body is preserved in_request_body(subject tohttp_max_request_param_data_size) before the body is parsed as a form.
Standard ClickHouse HTTP headers (such as X-ClickHouse-Database, X-ClickHouse-User, X-ClickHouse-Key) are honored as usual when invoking a handler.
The functions currentHandler and currentRequestURL can be used to customize query behavior depending on the invoked handler and request URL.
Access control
CREATE HANDLER, DROP HANDLER and ALTER HANDLER require the CREATE HANDLER, DROP HANDLER and ALTER HANDLER grants respectively.
Reading the system.handlers table requires the SHOW HANDLERS grant. Secrets that may be embedded in a handler’s query are masked there unless the user is additionally allowed to see secrets (see system.handlers).
Invoking a handler does not require any separate grant, but grants are checked as usual during the query invocation, and authentication works in the usual way. To encapsulate access to certain queries, create a VIEW with SQL SECURITY DEFINER and define a handler that selects from that view.
Storage
Handlers are saved in a storage, which can be a local or Keeper storage, similarly to named collections, configured in the query_rules_storage section of the configuration file:
<query_rules_storage>
<type>local</type> <!-- or zookeeper -->
<path>/var/lib/clickhouse/handlers/</path>
</query_rules_storage>With Keeper storage, handlers are kept in sync across all replicas automatically, so an explicit ON CLUSTER clause is redundant and would make every replica try to create the same handler. Enable the ignore_on_cluster_for_replicated_handler_queries setting to make CREATE, ALTER and DROP HANDLER ignore ON CLUSTER when the storage is replicated, mirroring ignore_on_cluster_for_replicated_named_collections_queries.
ALTER HANDLER
ALTER HANDLER name
[PROTOCOL protocol_name|ANY]
[URL [PREFIX|REGEXP] '/path']
[METHODS (GET, POST)]
[TYPE query]
[AS SELECT ...]Replaces the handler with a new one. The ALTER query can include only a subset of clauses, e.g., it can be used to only change the URL or the query. The unspecified clauses keep their previous values. PROTOCOL ANY removes an existing protocol restriction, making the handler active on all HTTP endpoints again.
DROP HANDLER
DROP HANDLER [IF EXISTS] nameDrops the handler with the specified name.
Introspection
The system.handlers table lists all SQL-defined handlers. The system.query_log table records the handler name and the HTTP request path (without the query string) of each query in the http_handler_name and http_request_url columns.
Example
CREATE HANDLER my_handler URL '/my_handler' AS SELECT version();$ curl 'http://localhost:8123/my_handler'A parameterized handler with a regexp URL:
CREATE HANDLER get_user URL REGEXP '/users/(?P<id>\d+)' AS SELECT * FROM users WHERE id = {id:UInt64};$ curl 'http://localhost:8123/users/42'Related statements
CREATE HANDLER is part of the CREATE statement family and is related to ALTER and DROP.