Skip to content
Tech News
← Back to articles

Helicone allows users to write SQL directly to a shared ClickHouse

read original more articles
Why This Matters

Helicone's new SQL editor, HQL, enables users to run SQL queries directly against a shared ClickHouse table while maintaining strict data isolation through ClickHouse's row policies and custom settings. This approach enhances flexibility and efficiency for organizations managing large-scale request data, demonstrating how advanced database features can be leveraged for secure multi-tenant environments. The development highlights the importance of combining database security features with application-level controls to enable powerful, yet safe, data access tools in the tech industry.

Key Takeaways

We added a SQL editor to Helicone called HQL. Customers get a text box, they write SELECT statements, and the queries run directly against our ClickHouse cluster. Every org's request data lives in the same table, request_response_rmt , separated only by an organization_id column.

Letting strangers run arbitrary SQL against a shared table sounds like a security incident with extra steps, so before shipping we needed the database itself to guarantee that a query from org A can never read a row belonging to org B. ClickHouse can do this with two features that don't get much attention: row policies and custom settings. Here's how we wired them together, and what the app-side code (including an AST parser) still has to do.

HQL in production. Every query on this screen runs against the same shared table.

The row policy

A row policy in ClickHouse is a filter the server appends to every read, per table, per user. The setup is two statements, straight from our migrations: create a unique user that will have the row policy, then attach the policy to that user.

CREATE USER IF NOT EXISTS hql_user; CREATE ROW POLICY hql_organization_filter ON request_response_rmt FOR SELECT USING organization_id = getSetting( 'SQL_helicone_organization_id' ) TO hql_user;

The unique user is the whole point of the first statement. hql_user exists purely to serve HQL traffic, and the policy binds to it through the TO hql_user clause. Our ingest and dashboard code connects as a different user and never sees the policy. For hql_user , every SELECT on request_response_rmt behaves as if WHERE organization_id = getSetting('SQL_helicone_organization_id') were part of the query, no matter what SQL the customer actually wrote.

getSetting is the interesting part. It reads a setting from the context of the current query. ClickHouse rejects setting names it doesn't recognize, and custom ones are only legal when they start with a declared prefix. If you self-host, you pick that prefix in the server config:

< clickhouse > < custom_settings_prefixes >SQL_</ custom_settings_prefixes > </ clickhouse >

On ClickHouse Cloud, where our cluster runs, there is no picking. You can't touch server config, and the ClickHouse docs state that "it is not possible to specify a custom prefix". Every custom setting begins with SQL_ , no exceptions. So ours is called SQL_helicone_organization_id instead of something cleaner, and that awkward prefix is load-bearing rather than a naming choice. You'll notice there's no custom_settings_prefixes entry anywhere in our repo; on Cloud there's nothing to configure.

... continue reading