Designing EventQL, an Event Query Language¶ When we built EventSourcingDB, we didn't just create a storage engine for events. We wanted to give developers the right tools to work with those events in ways that are both practical and efficient. Very early on, we realized something important: while projections are great for predefined, recurring questions, they don't cover everything. Sometimes you need answers on the fly. You know those moments when you're debugging a production issue and want to see every event of a certain type within the last day? Or when you're exploring a new feature and want to check how often a specific command was issued, but no projection exists yet? Creating a new projection for that one-off need feels wrong. It takes time to model, deploy, and maintain – just for a question you might ask once. That gap – between the recurring and the ad hoc – is where EventQL came from. Why We Needed Our Own Query Language¶ From the beginning, we wrote EventSourcingDB's storage engine ourselves. It's not built on top of an existing database; it's a purpose-built event store. That meant we weren't constrained by someone else's query layer – but it also meant nothing existed out of the box. Of course, we could have exposed only the raw event stream and left filtering to the client. But asking every user to pull the entire event history just to answer one question is painfully inefficient. We knew we needed server-side querying. We also briefly considered simply reusing SQL. It's familiar, widely understood, and powerful. But we ran into two problems: Conceptual mismatch. SQL evolved for relational tables, not for an append-only event log. Some SQL patterns don't make sense when your "records" are immutable facts with metadata and JSON payloads. Design inconsistencies. Over the years we've personally tripped over oddities in SQL's design. A classic example: queries start with SELECT , even though the real starting point is the data source in FROM . That ordering never felt natural to us. So rather than bolt SQL onto something it wasn't designed for, we decided to create something purpose-built: a small, clear language shaped by the way you actually think about events. A Query Flow That Matches How You Think¶ EventQL is intentionally structured to read like the mental model of exploring an event stream. A query begins with the data you're iterating over: FROM e IN events Here you're saying: "take each event from the event store and bind it to e ." From there, you can filter, transform, and finally project the result. One of the earliest syntax choices we made was to avoid the classic SELECT … FROM … style. In SQL, a query starts with SELECT , but conceptually you first define your data source and only later decide what you want to project out of it. We always found that ordering unintuitive. So in EventQL a query begins with FROM e IN events . Only at the end do you specify what you want to output, using PROJECT INTO . By the way, we deliberately chose PROJECT INTO rather than a purely technical term like map. While mapping is a correct description from an implementation perspective, it misses the intention behind the operation. Event Sourcing and CQRS are about making purpose explicit – capturing why something happens, not just how data is transformed. Using PROJECT INTO keeps that intent visible: you're shaping a meaningful projection, not just moving bytes around. We also made a conscious decision not to support multiple equivalent syntaxes. We could have said "why not allow both MAP and PROJECT INTO and let developers decide?", but that approach has proven to cause more confusion than freedom. Every extra way to do the same thing leads to unnecessary discussions in teams about which style to choose and turns style preferences into friction points. We prefer one clear, well-reasoned way so that people can focus on what their query does – not how to write it. The result is a query flow that feels natural once you see it: FROM e IN events WHERE e.type == "io.eventsourcingdb.library.book-acquired" PROJECT INTO { id: e.id, title: e.data.title } Strong Typing and Explicit Conversions¶ Another big decision was type safety. We debated whether EventQL should be dynamically typed, letting values convert implicitly, or strictly typed and explicit. We chose the latter. Our principle is simple: explicit is better than implicit. Silent conversions can lead to subtle bugs – especially with event data that's partly user-defined and partly system-defined. Take event IDs as an example. According to the CloudEvents spec, IDs are strings. But in EventSourcingDB we generate them numerically (monotonically increasing numbers). If you treat them as strings and compare "10" < "2" , you'll get the wrong result because string ordering doesn't match numeric ordering. In EventQL, you must explicitly cast when you want numeric semantics: FROM e IN events WHERE e.id AS INT > 100 PROJECT INTO { id: e.id, title: e.data.title } If you forget to cast, the query will error instead of silently doing the wrong thing. We'd rather fail loudly than give misleading answers. We've also kept the type system simple but expressive: primitives like string, int, bool, float; plus date and time support. Payload fields can vary per event type, so we added checks to safely navigate dynamic JSON: you can test if a field exists before accessing it. Query Power Without Hidden Surprises¶ Although EventQL is lightweight, it's not just sugar for one or two filters. We built a proper parser and execution engine. That means you can do more than trivial lookups: Multiple FROM clauses let you combine streams, like events with subjects or event types, effectively giving you join-like capabilities. clauses let you combine streams, like events with subjects or event types, effectively giving you join-like capabilities. Grouping and aggregation let you summarize data directly on the server. Subqueries allow complex filtering and reshaping. But we've been careful to avoid promising "free performance." Flexibility doesn't mean you can query arbitrarily deep payload structures at production scale without cost. Indexing in EventSourcingDB is primarily on metadata (like type, subject, timestamps). If you filter on arbitrary JSON payload fields, you may trigger a full scan. That's by design and by honesty: we don't hide the trade-offs. That's also why we say use EventQL wisely. It's perfect for ad hoc analysis, debugging, or one-off data extraction. If a query is needed repeatedly or must run at scale, build a projection – projections are precomputed and optimized. Designed for the Event World, Not Tables¶ Another big difference from SQL is that EventQL doesn't assume a table as the anchor. With normal read or observe calls in EventSourcingDB, you typically navigate by subject hierarchy – e.g. "give me all events under this customer or this order." EventQL, by contrast, lets you break free from that and query across everything. That's essential for advanced patterns like Dynamic Consistency Boundaries. When you need to gather events from multiple subjects to decide on a transactional scope or to enforce cross-aggregate invariants, EventQL gives you that power. You can slice through the event log in arbitrary ways, without first defining a subject-oriented path. But with that freedom comes responsibility: queries that ignore subject scoping can be heavy. For typical high-volume reads, stick with the dedicated APIs and projections. The Language Inside the System¶ EventQL isn't bolted on; it's deeply integrated into EventSourcingDB. We wrote a dedicated parser and execution engine – and we created it entirely from scratch. There was no existing event query language to adapt; this is original work built specifically for event streams. Others may follow, but EventQL was first. Queries are streamed back as NDJSON – the same efficient format used for reading and observing events – so you can process large result sets incrementally without blowing up memory. It's also fully API-driven. You can run queries directly through the HTTP API, which means any client or tool can use it. In fact, EventQL powers parts of our own UI: the Management UI includes an EventQL editor with syntax highlighting, auto-completion, auto-formatting, and even integrated AI assistance (EventAI) to help you write queries faster. Hard Trade-offs and Honest Boundaries¶ We've been deliberate about what EventQL does not try to do – at least not yet. For example, we don't have window functions or complex analytics built in. We could have waited to add every advanced SQL feature, but then EventQL would never ship. Like any language, it will evolve, but we believe it's better to have a stable, focused 1.0 than a bloated, unfinished spec. The same goes for type discipline and syntax choices. Sometimes users ask: "why not allow both FOR EACH and FROM ?" or "why not support multiple ways to express a projection?" Our answer: because choice isn't always good. Every alternate spelling creates friction and fragmentation. We prefer one strong, intentional way so teams can focus on meaning, not style wars. A Name Worth Saying Out Loud¶ And yes – we even debated the name. At first we thought of calling the language simply EQL. Short, catchy… until we said it aloud in German. "EQL" sounds like "ekel," which means "disgust." Not exactly the vibe you want for your query engine. So we went with EventQL – clear, descriptive, and safe in any language. Where EventQL Fits Best¶ So where should you use EventQL in practice? Ad hoc analysis: Quick insights, one-time checks, exploration of historical data. Quick insights, one-time checks, exploration of historical data. Debugging: Understand what really happened when something went wrong. Understand what really happened when something went wrong. Dynamic consistency: Collect events across subjects to decide safe transactional boundaries. Collect events across subjects to decide safe transactional boundaries. Occasional custom queries: Situations where projections would be overkill. And where not: High-frequency reads: Use projections or read APIs – they're optimized and indexed. Use projections or read APIs – they're optimized and indexed. Critical latency paths: EventQL trades some speed for flexibility. We built EventQL because we wanted it ourselves. After years of working with event-sourced systems, we were tired of either over-engineering with permanent projections or hacking quick scripts to replay everything. We wanted a first-class, native way to ask "what if?" without slowing down or guessing. That's why EventQL exists – and why it feels opinionated yet practical. It reflects the lessons we've learned from building and running real event stores: the power of events, the cost of too much flexibility, and the importance of clear, consistent tooling. And like the rest of EventSourcingDB, EventQL will continue to evolve. We know there's room for more features, smarter optimization, and richer developer tooling. But we'd rather grow carefully than rush and regret.