STRUMENTA LANGUAGE ENGINES · FIREBIRD 4.0

A .NET component that reads Firebird queries and table definitions.

Not a JVM library with a .NET wrapper: a .NET Standard 2.0 class library, targeting Firebird 4.0, with 93 automated tests, symbol resolution against a schema you supply, and expression typing. It covers queries and table, view and database DDL.

It does not cover PSQL — no stored procedures, no triggers, no EXECUTE BLOCK. That is a real boundary and it is the first thing on this page rather than the last, because it is the thing that decides whether you should be reading further.

The engine at a glance
TARGETFirebird 4.0
RUNTIME.NET Standard 2.0
GRAMMAR785 parser + 577 lexer lines
TESTS93 automated, seven classes
SCOPEqueries · table, view and database DDL
NOT COVEREDPSQL — procedures, triggers, blocks

The word “dialect” means something else here

FirebirdParser.g4 · 785 lines + FirebirdLexer.g4 · 577 lines

Everywhere else on this site, a dialect is a vendor’s flavor of SQL. In Firebird it is a per-database setting, inherited from InterBase 6, that changes the language itself — and nothing in a .sql file tells you which one is in force.

One Firebird statement with two readings, depending on the database SQL dialect In the statement SELECT “Nachname” FROM kunde, the double-quoted text Nachname is a string literal when the database is dialect 1, and a case-sensitive column identifier when the database is dialect 3. SELECT "Nachname" FROM kunde; DIALECT 1 — DOUBLE QUOTES DELIMIT A STRING LITERAL a constant: every row returns the word Nachname a case-sensitive column reference in table kunde DIALECT 3 — DOUBLE QUOTES DELIMIT AN IDENTIFIER

Dialect 1 exists for backward compatibility with InterBase 5 and earlier. In it, DATE is a combined date and time, there is no BIGINT, identifiers cannot be quoted at all, and double quotes delimit a string. Dialect 3 — the recommended one, and the default for new databases — separates DATE, TIME and TIMESTAMP, has 64-bit integers, and makes double quotes delimit a case-sensitive identifier.

So SELECT "Nachname" FROM kunde returns a constant under one setting and a column under the other. Same file, same characters, two different programs. Firebird’s install base is conservative enough that both are still in production, often at different customers of the same software vendor.

We implement exactly one accommodation for this, and we would rather name it precisely than imply we handle the whole split. The parser takes a quotedIdentifiersAreStrings flag at construction. Leave it alone and you get the dialect-3 reading; set it and you get the dialect-1 reading of double quotes.

The rest of dialect 1 — the collapsed DATE type, the truncated 32-bit generator values, the narrower NUMERIC precision — is not modeled, and we do not claim it. If your databases are dialect 1, that flag covers the syntax question and leaves the type questions open. Tell us and we will look at real files.

Input — one statement, parsed with the default flag

kunde_query.sql

SELECT FIRST 25 k."Nachname", k."Ort"
  FROM kunde k
  WHERE k."Ort" CONTAINING 'berlin'
 ORDER BY k."Nachname" COLLATE UNICODE
 NULLS LAST;
German column names in a Delphi-era schema, which is what a real Firebird corpus looks like. With the default flag the quoted names are identifiers; construct the parser with quotedIdentifiersAreStrings: true and the same text becomes five string constants and the ORDER BY stops referring to a column.

Output — parse tree, by grammar rule

intermediate rules elided

statements
└── statement
    └── selectStatement
        ├── ⋯ FIRST 25
        ├── ⋯ projection: "Nachname", "Ort"
        ├── ⋯
        │   └── CONTAINING 'berlin'
        └── ⋯
            └── orderingNullsPlacement NULLS LAST
These are real rule names from the shipped grammar; intermediate rules are shown as elisions rather than invented. Above the tree sits a SharpLasu AST rooted at CompilationUnit, with positions on every node, and an Issue list carrying a message and a position when the input is malformed.

What it handles

Three constructs that a grammar written for ANSI SQL simply has no rules for. They are not obscure Firebird corners — they are what an application written against Firebird actually contains.

UPDATE OR INSERT … MATCHING

Firebird’s upsert, and a three-keyword statement head that no ANSI grammar anticipates. The MATCHING clause names the columns that decide whether the row already exists, and without it the primary key is used.

UPDATE OR INSERT INTO cows
       (name, number, location)
VALUES ('Suzy', 3278823, 'Pastures')
MATCHING (number);

Row limits in two places at once

FIRST n SKIP m is a prefix to the select list, sitting awkwardly between SELECT and the columns. ROWS m TO n is a competing suffix, inclusive and one-based. Both are legal, both are in the grammar, and neither looks like TOP or FETCH FIRST.

SELECT FIRST 10 SKIP 20 name, location
  FROM cows
 ORDER BY name
 ROWS 1 TO 10;

Predicates that look like identifiers

CONTAINING is a case-insensitive substring test and STARTING WITH a prefix test. To a generic lexer they are ordinary words, which is why a generic grammar reads them as a column alias and then fails three tokens later with a message about the wrong thing.

SELECT name FROM cows
 WHERE location CONTAINING 'pasture'
   AND name STARTING WITH 'S';

Coverage, by construct

Rule names in the middle column are rules in the grammar we ship. The lower half of this table is longer than we would like, and printing it is the point: it is what a Firebird prospect needs before a proof of concept, not after one.
Construct Grammar rules Status
SELECT — CTEs including recursive, joins, GROUP BY/HAVING, named windows and frames, UNION selectStatement Supported
Row limits — FIRST/SKIP, ROWS m TO n; ordering with COLLATE and null placement orderingNullsPlacement Supported
INSERT, UPDATE, DELETE, COMMIT insertStatement, updateStatement, deleteStatement, commitStatement Supported
Firebird’s upsert updateOrInsertStatement Supported
Table and view definition, including the RECREATE forms and global temporary tables createTableStatement, recreateTableStatement, createViewStatement, recreateViewStatement Supported
CREATE DATABASE, where the database name is a file path in quotes createDatabaseStatement Supported
Column generation — defaults, COMPUTED BY, GENERATED ALWAYS AS, identity columns column definition rules Supported
Constraints and domains — primary and unique keys USING INDEX, foreign keys with actions, character sets, collations, BLOB SUB_TYPE domainDatatype, foreignKeyAction Supported
Firebird predicates and functions — CONTAINING, STARTING WITH, SIMILAR TO, GEN_ID, NEXT VALUE FOR, IIF, DECODE, LIST, AT TIME ZONE expression rules Supported
Symbol resolution and expression typing against a schema you supply DatabaseModel, TypeSystem Supported
Dialect 1 double-quote semantics quotedIdentifiersAreStrings The quoting rule only — not the dialect-1 type system
Firebird 5.0-only syntax Unverified — the target is 4.0; send us files and we will report
PSQL — stored procedures, triggers, EXECUTE BLOCK, selectable procedures, SUSPEND, local variables, exceptions Not supported
SET TERM, the isql terminator directive Not supported — raw isql exports need preprocessing
ALTER, DROP, GRANT/REVOKE, CREATE INDEX, CREATE SEQUENCE, CREATE EXCEPTION Not supported
EXECUTE PROCEDURE, EXECUTE STATEMENT Not supported

To say what this product is in one line: a Firebird 4.0 query and table-definition parser with symbol resolution and type inference. That is a real and useful thing to own, and it is narrower than “a parser for Firebird”. We prefer the narrow sentence.

One file that only this engine takes

We license five SQL-family engines. Every statement here is Firebird-only spelling, and the middle one is taken verbatim from the test suite.

Input — schema and queries from an application

cows.sql

RECREATE TABLE cows (
    id       INTEGER GENERATED BY DEFAULT AS IDENTITY,
    name     VARCHAR(40) CHARACTER SET UTF8 COLLATE UNICODE,
    number   BIGINT DEFAULT 0 NOT NULL,
    label    COMPUTED BY (name || ' #' || number),
    CONSTRAINT pk_cows PRIMARY KEY (id) USING INDEX ix_pk
);

UPDATE OR INSERT INTO cows (name, number, location)
VALUES ('Suzy Creamcheese', 3278823, 'Green Pastures')
MATCHING (number);

SELECT FIRST 10 SKIP 20 c.name, c.location
FROM   cows c
WHERE  c.location CONTAINING 'pasture'
   AND c.name STARTING WITH 'S'
ORDER  BY c.name COLLATE UNICODE NULLS LAST
ROWS   1 TO 10;
The UPDATE OR INSERT … MATCHING statement is copied from the shipped test suite, so it is a tested shape. Nothing else on the list reads this file: RECREATE, UPDATE OR INSERT … MATCHING, FIRST/SKIP, CONTAINING, STARTING WITH, COMPUTED BY and ROWS m TO n are all Firebird spellings. Oracle writes FETCH FIRST, Transact-SQL writes TOP (n), Teradata writes TOP n, and our generic SQL engine has no DDL at all.
WHAT THIS ENGINE IS FOR

Firebird and SQL Server, behind one symbol solver.

This engine does not ship alone. It arrives in a solution alongside our Transact-SQL component, over one shared AST — which is exactly the shape of the problem a vertical software vendor has when it moves customers off an embedded database and onto a hosted one.

Both parsers build nodes from the same shared SQL AST, and one SQLSymbolSolver resolves either tree behind a single interface, dispatching on node type. A tool you write once walks the Firebird source and the SQL Server target with the same code. Source and target, one API, one model.

Underneath that sits the semantic layer you actually buy the component for: a database model you populate from your own schema, column references that resolve to positioned referents, and a type system exercised by its own test class. Given four hundred on-premises installations of the same product with local schema drift, the useful question is which sites’ queries break under a change — and that is a resolver question, not a parser question.

Being a .NET component is not a detail either. A vertical software vendor with a Delphi or C# codebase and a fifteen-year-old Firebird schema does not want a JVM in its build. This is a class library that loads into .NET Framework 4.6.1 and later, .NET Core 2.0 and later, and modern .NET.

var fb  = new FirebirdSharplasuParser(
              quotedIdentifiersAreStrings: false);

var result = fb.Parse(sqlText);
foreach (var issue in result.Issues)
    Console.WriteLine($"{issue.Position}: {issue.Message}");

// the same solver takes either dialect's tree
var solver = new SQLSymbolSolver(SQLDialect.Auto);

Construction, the positioned issue list, and the shared symbol solver. When the parser cannot match, it reports a short message naming the token and the rule rather than dumping an expected-token set at your users.

93
AUTOMATED TESTS, SEVEN CLASSES
1,362
LINES OF ANTLR GRAMMAR
2
DIALECTS BEHIND ONE SYMBOL SOLVER
4.0
THE FIREBIRD VERSION WE TARGET

What you receive

A .NET class library, a generated API reference that lists the AST node types one by one, and a person to write to. It runs inside your own build, with no connection back to us and none to any customer database.

01 The engine

A .NET Standard 2.0 library, with the Transact-SQL one beside it

C# on SharpLasu — the .NET implementation of Starlasu — built with ANTLR. The solution carries the Firebird parser, the Transact-SQL parser, the shared SQL AST and the shared symbol resolution, so a vendor with both back ends integrates one component.

.NET Standard 2.0 · no JVM required
02 The model

An AST, and the semantics on top of it

A typed AST rooted at a compilation unit, positions on every node, and a positioned issue list rather than an exception. Above it: a database model you populate, resolved references with referents, and expression typing. The engine supports LionWeb, so the model travels to tooling we did not write.

AST · symbols · types · LionWeb
03 The documents

A generated API reference, node type by node type

The HTML API reference is generated from the source, so it enumerates the real node types — the table statements, the relation and projection nodes, the constraint and column-generation nodes, the Firebird-specific function calls and the Firebird-specific types — rather than describing the shape in prose.

HTML API docs · per node type
04 The license

Standard, Distribution or Service — with support included

Standard for use inside your own organization, Distribution if the engine ships inside a product you sell — which is the usual case here — and Service if it runs behind a service you operate. Support is part of the license. The guarantee runs a year and is extendable, or can be taken pay-as-you-go.

three tiers · support included
Onboarding

Week one is pointing the parser at the statements you already have inside your application code and reading the issue list.

If those statements come from an isql export rather than from source, budget for a preprocessing step first — the PSQL sections have to come out. That is a small, well-understood job, and it is better planned than discovered. Write to products@strumenta.com.

THE REST OF THE ESTATE

In a Firebird product, the SQL lives inside the application.

Firebird’s habitat is embedded and vertical: practice management, point of sale, warehouse and laboratory systems, ERP packages that ship a database file with the installer. The queries are not in the database. They are string constants in Delphi, C# or Java, three layers down.

That changes the shape of the problem. Inventorying the SQL means first finding it, which means parsing the host language — and then holding the host program and the embedded statement in one model, so that “this screen writes this column” is a path through a single tree rather than a join between two tools on a shared string.

Every Strumenta engine is built on Starlasu, so a Firebird tree and a Java or C# tree have the same shape, the same traversal model and the same API. That is what you cannot assemble by gluing unrelated open-source parsers together: five parsers means five models and five sets of edge cases before you have written a line of analysis.

Every engine also supports LionWeb, so the models interchange with LionWeb-compliant tooling instead of being locked inside one process — and every engine is available through bindings from Java, Kotlin, Python, TypeScript and C#. This particular component ships as .NET; what can read its output is a wider list.

Engines in the same estate

one model, one traversal

Transact-SQL engine →
Ships in this same package, over the same AST.

Java engine →
The application layer holding the statements.

Generic SQL engine →
If what you need is an editor rather than a batch.

The whole catalog →
15+ engines, one AST framework underneath.

Why license this rather than start from a public grammar

We can be unusually concrete here, because the most visible public Firebird grammar is ours: an open-source sketch we published and then stopped working on, Strumenta/firebird-sql-parser, honestly described in its own README as a partial Firebird grammar for ANTLR4.

It is 353 lines of Kotlin and ANTLR, last touched in 2023, and it is a fair representation of what is freely available for this dialect. The commercial grammar is 1,362 lines, which is more — and the difference in line count is nowhere near the difference in value.

The grammar is the smaller half of the work. The larger half is the shared SQL AST, the base symbol solver, the database model, the type system and 93 tests including a real German ISV schema with Delphi object-persistence tables in it — the sort of fixture you only get by running a parser over software somebody actually sells.

There is also a plain packaging argument. A .NET shop that wants a Firebird parser and starts from a public ANTLR grammar is looking at either hosting a JVM in its build or generating and maintaining C# targets by hand. Neither is expensive on day one. Both are expensive in year three.

And the same reasoning about interoperability applies here as everywhere in the catalog: because this engine is built on Starlasu and speaks LionWeb, its output is the same kind of object as our Transact-SQL, Java and COBOL output. Independent grammars give you a translation layer per pair.

The method behind this work has a name. Read the Chisel Method for how we make parser work estimable — including the work of extending this engine into PSQL, if that turns out to be what you need.

What to discuss before you license

Six things we would rather tell you now than have you find in week three. The first one is large enough that it should decide the conversation.

  • PSQL is not supported at all. No stored procedures, no triggers, no EXECUTE BLOCK, no selectable procedures, no SUSPEND, no local variables, no exceptions. If your business logic lives in PSQL rather than in the application, this engine reads the wrong half of your system. That is not a shameful secret, it is a qualification question: tell us the proportion and we will tell you whether to buy this, extend it, or wait.
  • SET TERM is not in the grammar, so raw isql exports need preprocessing. The canonical Firebird DDL script changes the statement terminator so that a procedure body’s internal semicolons do not end the outer statement. We do not model that directive, which means a script full of SET TERM ^ ; must have its PSQL sections stripped before the parser sees it. Statements pulled from application source do not have this problem.
  • Schema-changing DDL is absent. ALTER, DROP, GRANT/REVOKE, CREATE INDEX, CREATE SEQUENCE and CREATE EXCEPTION are not in the grammar. Creating and recreating tables, views and databases is; changing them afterwards is not.
  • The target is Firebird 4.0. Not 5.0, not 3.0, not 2.5 — those are simply untested rather than declared broken, and most of the language is stable across them. If your install base is on 2.5, which for Firebird is entirely normal, send us files before you buy.
  • Dialect 1 support is exactly one flag. quotedIdentifiersAreStrings gives you the dialect-1 reading of double quotes. The collapsed DATE type, the 32-bit generator values and the narrower numeric precision are not modeled and we do not claim them.
  • There is no command-line tool and no JSON or XML exporter in this component, and no code generator. It is a library you call, and it reads rather than writes. If you need a serialized tree on disk or Firebird SQL emitted back from a modified model, say so at the first call — both are work, and neither is a switch.

What we will ask you

  1. Which Firebird versions are your customers on — 2.5, 3.0, 4.0 or 5.0? And how many versions at once?
  2. Dialect 1 or dialect 3? If dialect 1, double-quoted text is data, and you need the flag.
  3. Is the logic in the application or in PSQL procedures and triggers? What proportion, roughly?
  4. Are your sources isql scripts with SET TERM, or statements extracted from application code?
  5. Which .NET target framework do you build against?
  6. Do you have the schema, so we can populate a database model, or only the statements?
  7. Is SQL Server anywhere in the estate? Same package, same symbol-solver API.
  8. Do you need the tree serialized to disk, or is an in-process model enough?

Straight answers

Does it parse stored procedures and triggers?

No. PSQL is out of scope in this component — procedures, triggers, EXECUTE BLOCK, selectable procedures, SUSPEND, local variables and exceptions are all absent from the grammar. What it reads is queries and table, view and database definitions, with symbol resolution and typing on top. If your logic is in PSQL, tell us early; extending the engine is a scopeable piece of work, and pretending otherwise would waste your proof of concept.

Can we feed it our isql database export?

Not as it comes out of the tool. A Firebird DDL export uses SET TERM to change the statement terminator around PSQL bodies, and that directive is not in the grammar. The PSQL sections have to be stripped or handled by the caller first. Statements taken from your application source — which is where most Firebird SQL actually lives — need no preprocessing at all.

Our databases are dialect 1. Does that work?

Partly, and here is the exact boundary. Construct the parser with quotedIdentifiersAreStrings set and double-quoted text is read as a string literal, which is the dialect-1 rule and the one that changes what a query means. The rest of dialect 1 — DATE carrying a time component, 32-bit generators, the narrower numeric precision — is not modeled. For syntax you are covered; for types you should send us files.

Is this a JVM library?

No. It is a .NET Standard 2.0 class library and there is no JVM build of it, which is deliberate: the vendors who run Firebird in production are usually .NET or Delphi shops. Separately from how it ships, the engine is built on Starlasu and supports LionWeb, so its output is consumable through bindings from Java, Kotlin, Python, TypeScript and C#. How it runs and what can read its model are two different questions.

We have Firebird and SQL Server. Is that two purchases?

It is one component. The Firebird and Transact-SQL parsers ship in the same solution over a shared SQL AST, and one symbol solver resolves either tree behind a single interface. For a vendor migrating customers from an embedded Firebird database to SQL Server, that means the analysis you write for the source also runs on the target.

Send us a hundred statements and the schema, if you have it

Bring us your queries, and we will tell you what is left over.

A slice of the SQL in your application, plus the DDL if you can export it. We will come back with what parses, what resolves against your schema, how much of your logic sits in PSQL where this engine cannot follow, and whether that gap is small enough to ignore or large enough to plan for. If it is the second, we will say so.

Scroll to Top