Apache.Calcite.Extensions
2.0.1-pre.230
See the version list below for details.
dotnet add package Apache.Calcite.Extensions --version 2.0.1-pre.230
NuGet\Install-Package Apache.Calcite.Extensions -Version 2.0.1-pre.230
<PackageReference Include="Apache.Calcite.Extensions" Version="2.0.1-pre.230" />
<PackageVersion Include="Apache.Calcite.Extensions" Version="2.0.1-pre.230" />
<PackageReference Include="Apache.Calcite.Extensions" />
paket add Apache.Calcite.Extensions --version 2.0.1-pre.230
#r "nuget: Apache.Calcite.Extensions, 2.0.1-pre.230"
#:package Apache.Calcite.Extensions@2.0.1-pre.230
#addin nuget:?package=Apache.Calcite.Extensions&version=2.0.1-pre.230&prerelease
#tool nuget:?package=Apache.Calcite.Extensions&version=2.0.1-pre.230&prerelease
Apache.Calcite.Extensions
Apache.Calcite.Extensions is what .NET adds on top of Apache Calcite running under IKVM: a calling convention that runs a query plan as compiled .NET code, the prepare pipeline that takes a statement from SQL text to such a plan, and the interop types both need.
No Java compiler runs when a statement is prepared, and a .NET user-defined function can be called from SQL.
Most people get this package as a dependency of Apache.Calcite.Data or Apache.Calcite.Adapter.AdoNet and never call it directly — every statement on a CalciteConnection is already planned and run this way, with nothing to configure. Reference it yourself when you want to drive Calcite's planner without an ADO.NET connection, or want typed access to Calcite's connection properties.
Targets .NET 8, and is verified on .NET 8 and .NET 10.
Install
dotnet add package Apache.Calcite.Extensions
Why you might want it
Calcite normally executes a query by generating Java source and compiling it at runtime with Janino. Under IKVM that works, but a Java compiler runs every time you prepare a statement, and any function you call from SQL has to be reachable by a Java class name.
This package replaces that step. A query plan is compiled into a System.Linq.Expressions tree and turned into a delegate, so:
- No Java compiler runs when you prepare a statement.
- A .NET method can be a SQL function, and no class name is written out. Calcite's own engine reaches one only through the class-loader stamp
IKVM.Maven.Sdkputs oncalcite-core, which IKVM 8.14.0 and 8.15.0 could not read — under those a .NET user-defined function had no plan underEnumerableConventionat all, Janino refusing thecli.-prefixed name IKVM gives a CLR class. IKVM 8.16.0 fixes it; here it never mattered, because nothing writes a name.
ClrEnumerableConvention mirrors Calcite's EnumerableConvention node for node and uses the same row types, and converter rules exist in both directions. A plan may hold nodes of both conventions: anything this convention has no rule for is planned by Calcite as usual, and rows cross between the two untouched.
One plan, read either way. A plan of this convention is compiled to an IEnumerable<object> or an IAsyncEnumerable<object>, and which is decided when it is compiled rather than when it is planned. There is one convention, one set of rules and one tree of nodes; each node carries two bodies, both naming ClrEnumerableDefaults, whose pulled operators and Async-suffixed awaiting ones are the two sets. The implementor offers a call hierarchy per kind rather than a mode to set. So the same prepared statement can be read synchronously by one caller and awaited by another, and an EXPLAIN cannot tell you which will happen.
Running a plan yourself
Put this convention's rules on the planner, run Programs.standard(), then compile the root with ClrEnumerableRelImplementor. This example is executed by a test in the repository, so it cannot go stale silently:
using Apache.Calcite.Extensions.Adapter.Enumerable;
using org.apache.calcite;
using org.apache.calcite.tools;
var calcRules = new java.util.ArrayList();
foreach (var rule in ClrEnumerableRules.CalcRules())
calcRules.add(rule);
// Programs.standard(), with this convention's rules put on the planner in front of it -- a
// Frameworks planner carries Calcite's alone -- and its calc rules run afterwards, which is
// Programs.calc once more over this convention's list. standard's own calc pass still runs
var config = Frameworks.newConfigBuilder()
.defaultSchema(rootSchema)
.programs(
Programs.sequence(
new AddRulesProgram(ClrEnumerableRules.Rules()),
Programs.standard(),
Programs.hep(calcRules, true, org.apache.calcite.rel.metadata.DefaultRelMetadataProvider.INSTANCE)))
.build();
var planner = Frameworks.getPlanner(config);
var logical = planner.rel(planner.validate(planner.parse(sql))).project();
// one sequence, so one transform, exactly as Programs.standard is driven.
// the logical root's own traits, not an empty set: they carry the collation the ORDER BY produced,
// and SortRemoveRule takes the sort away as unwanted if the required traits do not ask for it
var traits = logical.getTraitSet().replace(ClrEnumerableConvention.Instance).simplify();
var physical = (ClrEnumerableRel)planner.transform(0, traits, logical);
// the root is a node of this convention; build its plan and compile it
var implementor = new ClrEnumerableRelImplementor(
physical.getCluster().getRexBuilder(), new java.util.HashMap());
var lambda = implementor.ImplementRoot(physical, ClrEnumerablePrefer.Array);
var plan = (Func<DataContext, System.Collections.IEnumerable>)lambda.Compile();
foreach (var current in plan(dataContext))
{
// a one-column result is the value itself, not a row of one
var row = current as object[] ?? [current];
Console.WriteLine(string.Join('\t', row));
}
To await the rows instead, call ImplementRootAsync on the same implementor and compile to a Func<DataContext, IAsyncEnumerable<object>>. Nothing else changes: the same planned root, the same rules, the same physical types, the same instance. Each node's awaiting body is called instead of its pulled one, and a node that can only produce one kind of sequence is read across at that node.
ClrEnumerableInterpretable.ToBindable(...) and ToAsyncBindable(...) are the alternative endings: each does the same work and hands back an IClrBindable or an IClrAsyncBindable, which you bind to a DataContext and enumerate. Use the implementor when you want the LambdaExpression itself.
Three things about this program are deliberate and worth knowing before you substitute your own:
- The calc rules are a separate pass.
VolcanoCost.isLtcompares row counts and nothing else, so a project and a calc are never cheaper than one another and the planner keeps whichever it saw first. Rewriting unconditionally afterwards as a hep pass is what makes a project's refusal to implement itself safe.Programs.standard()does the same thing for the same reason. - The planner pass registers Calcite's rules, then this convention's.
Programs.standard()installs none and plans with whatever is on the planner, which works becauseRelOptUtil.registerDefaultRuleshas already put Calcite's there. Nothing has heard of this convention, soRules()registers — but it registers Calcite's set as well as ours, not instead of it. Dropping Calcite's takes with it the logical rewrites that belong to no convention, andAVG, everyDISTINCTaggregate and everyOVERwindow each need one of those before any planner sees them. It is also what lets a node this convention has no rule for be planned inEnumerableConventionand carried across a converter. - The decorrelation is Calcite's and is run. It was left out for a while, on the grounds that it rewrites a correlated sub-query into a join and would leave
ClrEnumerableCorrelateunreachable. That is not so: a scalar sub-query and anEXISTSdo become joins, but anUNNESTover a correlation variable cannot be decorrelated and keeps its correlate — which is how Calcite reaches its ownEnumerableCorrelateunderPrograms.standard()as well.
A Spark handler is not supported: ToBindable throws UnsupportedOperationException if one is enabled, because a Spark handler compiles generated Java source and a plan of this convention is an expression tree.
ClrCursorConvention: one plan, opened either way, read either way
A second calling convention, ClrCursorConvention, compiles a plan to a ClrCursorFactory rather than to a sequence. The factory has two members, Open(DataContext) and OpenAsync(DataContext, CancellationToken), and both hand back the same kind of object: a ClrCursor, a forward-only cursor with Read() and ReadAsync(CancellationToken) over one position. A consumer chooses how to open, and then chooses again on every advance how to read, and a row read with one member and the next with the other are consecutive rows of one result.
That is the shape DbDataReader has, and it is what the sequence convention cannot give it: an IEnumerable or an IAsyncEnumerable states once, at GetEnumerator or GetAsyncEnumerator, whether it will be pulled or awaited, and takes its cancellation at the same moment, so ReadAsync(token) had nowhere to put its token and Read over an awaiting plan blocked a thread per row by construction. A cursor takes the token per advance and hands it down to the leaf.
using Apache.Calcite.Extensions.Adapter.Cursor;
// the same planner set-up as above, with ClrCursorRules.Rules() and CalcRules() in place of the
// sequence convention's, and ClrCursorConvention.Instance asked of the root
var implementor = new ClrCursorRelImplementor(
physical.getCluster().getRexBuilder(), new java.util.HashMap());
var factory = implementor.ImplementRoot((ClrCursorRel)physical, ClrEnumerablePrefer.Array);
// opening runs the plan's acquisition -- a sort drains, a leaf executes -- and reading reads rows
await using var cursor = await factory.OpenAsync(dataContext, cancellationToken);
while (await cursor.ReadAsync(cancellationToken))
Console.WriteLine(cursor.Current);
// or synchronously, over the same factory
using var pulled = factory.Open(dataContext);
while (pulled.Read())
Console.WriteLine(pulled.Current);
ImplementRoot walks the tree twice, through a node's Implement and its ImplementAsync, and puts both opens on one factory; each is compiled the first time it is called. A node's two bodies differ only in what is acquired at open — one drains a sort by blocking, the other by awaiting — and produce the same cursor class, whose two advances step the same fields. An operator that acquires a source later than at its own open, as linq4j's concat does inside moveNext, takes both opens of that source and calls the one matching the advance it is in.
The convention shares everything about a row with the sequence convention — the physical type, the row formats, the Rex translation and ClrEnumerablePrefer — and both directions of converter against EnumerableConvention exist, so a statement it has no node for is still planned. It has every node the sequence convention has, and the prepare pipeline plans into it.
Key public types
| Type | Purpose |
|---|---|
ClrEnumerableConvention |
The calling convention itself. ClrEnumerableConvention.Instance is the singleton trait. |
ClrEnumerableRules |
The convention's rules: Rules() and CalcRules(). Add these to a planner you built yourself. |
ClrEnumerableRelImplementor |
Builds the expression tree for a plan. Two parallel hierarchies over one instance: ImplementRoot and VisitChild produce an IEnumerable, ImplementRootAsync and VisitChildAsync an IAsyncEnumerable. It carries no mode. Pulled and Awaited cross between them. |
ClrEnumerableResult / ClrAsyncEnumerableResult |
What a node's two bodies answer, one type per kind, built by Result and ResultAsync. |
ClrEnumerableInterpretable |
ToBindable and ToAsyncBindable — implement, compile, and return an IClrBindable or an IClrAsyncBindable. |
IClrBindable / IClrAsyncBindable |
A compiled plan. Bind(DataContext) returns the rows; ElementType says what one row is. |
ClrEnumerablePrefer |
How a caller wants rows represented — Array is what a prepared statement asks for. |
ClrEnumerableRelFactories |
RelBuilder factories producing nodes of this convention. |
IClrScannableTable / IClrQueryableTable / IClrCursorTable |
The table SPI: a table hands back .NET sequences rather than linq4j ones, or a cursor. One interface per table kind, carrying both halves. Scan, GetExpression and Open are required; ScanAsync, GetAsyncExpression and OpenAsync default to reading them across. A table whose rows only ever arrive asynchronously overrides those and drains its own sequence for the required half. A cursor table is for a source that is a forward-only cursor already, a DbDataReader say: under the cursor convention its cursor is the plan's leaf and the token of each ReadAsync reaches it. |
ClrEnumerableRel |
The interface every node of this convention implements. Two bodies: Implement over the pulled operators, required, and ImplementAsync over the awaiting ones, optional and defaulting to Implement. That default is safe exactly when a body does not visit a child, which is not the same as having no input: a body that asks for its input as a sequence and takes the default composes an awaited input into a pulled operator, which Expression.Call refuses. |
ClrCursorConvention |
The cursor calling convention. ClrCursorConvention.Instance is the singleton trait, and ClrCursorRules carries its Rules() and CalcRules(). |
ClrCursorRelImplementor |
Builds both opens of a plan of that convention and hands back a ClrCursorFactory. VisitChild and VisitChildAsync are the two hierarchies; Opener and OpenerAsync defer an input's open for an operator that acquires it later. |
IClrCursorFactory / ClrCursorFactory |
A compiled plan of the cursor convention: Open(DataContext) and OpenAsync(DataContext, CancellationToken) each hand back an IClrCursor, and ElementType says what one row is. ClrCursorFactory is the one the implementor builds. |
IClrCursor / IClrCursor<T> |
A forward-only cursor with Read() and ReadAsync(CancellationToken) over one position, and Current. ClrCursor and ClrCursor<T> are the abstract bases every cursor of this project derives from; a source that is a cursor already implements the interface directly. |
ClrCursorRel |
The interface every node of the cursor convention implements: Implement composes opens that acquire synchronously and ImplementAsync opens that await, the second optional and defaulting to the first with the same caveat as ClrEnumerableRel's. |
CalciteConnectionProperties |
Typed .NET properties over Calcite's java.util.Properties. |
CalciteConnectionPropertiesSchemaMap |
The schema.* sub-properties, as a dictionary. |
The nodes (ClrEnumerableCalc, ClrEnumerableHashJoin, ClrEnumerableWindow, and the rest) and their rules are public too, so you can subclass or re-register them.
The operator sets are not public. ClrEnumerableDefaults, which holds both, and the ClrBuiltInMethod table that names them are internal to this package, which is what they have always been, and ClrCursorDefaults and ClrCursorBuiltInMethod are internal for the same reason. A node you write outside it builds calls to its own methods with Expression.Call, and an awaiting one appends its own trailing CancellationToken as Expression.Default(typeof(CancellationToken)) — an expression tree does not apply a default argument, and that default is what [EnumeratorCancellation] reads.
The SQL-text prepare pipeline is internal to these packages. ClrPrepareImpl, ClrSignature and the rest of Apache.Calcite.Extensions.Prepare are not part of the public API surface — Apache.Calcite.Data reaches them through InternalsVisibleTo. To run SQL text, use Apache.Calcite.Data; to drive the planner directly, use the public types above.
CalciteConnectionProperties
Strongly-typed .NET properties over a Calcite java.util.Properties map. Instead of reading and writing raw string keys, you get compile-time-checked access to Calcite's connection options:
using Apache.Calcite.Extensions.Config;
using java.util;
using org.apache.calcite.avatica.util;
var props = new CalciteConnectionProperties();
// Typed setters — no magic strings needed.
props.Lex = Lex.MYSQL_ANSI;
props.CaseSensitive = false;
props.DefaultNullCollation = NullCollation.LOW;
props.Fun = "oracle,spatial";
props.TimeZone = "UTC";
props.ForceDecorrelate = true;
props.MaterializationsEnabled = false;
| Property | Type | Default | Description |
|---|---|---|---|
Model |
string |
— | URI or inline JSON model. |
Schema |
string |
— | Default schema name. |
CaseSensitive |
bool |
from Lex (true under ORACLE) |
Case-sensitive identifier matching. |
Lex |
Lex |
ORACLE |
Lexical policy (ORACLE, MYSQL, MYSQL_ANSI, SQL_SERVER, JAVA, BIG_QUERY). |
Quoting |
Quoting |
from Lex |
Identifier quote character. |
QuotedCasing |
Casing? |
from Lex |
Storage of quoted identifiers. |
UnquotedCasing |
Casing? |
from Lex |
Storage of unquoted identifiers. |
Fun |
string |
standard |
Function libraries, e.g. oracle,spatial. |
Conformance |
SqlConformanceEnum |
DEFAULT |
SQL conformance level. |
DefaultNullCollation |
NullCollation |
HIGH |
NULL sort order when NULLS FIRST/LAST is omitted. |
TimeZone |
string |
JVM default | Session time zone. |
Locale |
string |
Locale.ROOT |
Session locale. |
ForceDecorrelate |
bool |
true |
Aggressive subquery de-correlation. |
TopDownGeneralDecorrelationEnabled |
bool |
false |
De-correlate with TopDownGeneralDecorrelator rather than RelDecorrelator. |
MaterializationsEnabled |
bool |
true |
Use materializations in the planner. |
CreateMaterializations |
bool |
true |
Create materializations on the fly. |
TypeCoercion |
bool |
true |
Implicit type coercion during validation. |
ApproximateDecimal |
bool |
false |
Allow approximate DECIMAL aggregate results. |
ApproximateDistinctCount |
bool |
false |
Allow approximate COUNT(DISTINCT ...). |
ApproximateTopN |
bool |
false |
Allow approximate Top-N results. |
AutoTemp |
bool |
false |
Store query results in a temporary table. |
NullEqualToEmpty |
bool |
true |
Treat empty strings as null, for the Druid adapter. |
Spark |
bool |
false |
Use Spark as the in-process execution engine. |
TopdownOpt |
bool |
calcite.planner.topdown.opt |
Enable top-down optimization in the Volcano planner. |
LenientOperatorLookup |
bool |
false |
Silently create unknown functions during parsing. |
DruidFetch |
int |
16384 |
Rows to fetch per Druid query. |
SchemaFactory |
string |
— | Schema factory class name (when not using a model). |
SchemaType |
string |
— | Schema type: MAP, JDBC, or CUSTOM. |
ParserFactory |
string |
— | Custom SQL parser factory. |
MetaTableFactory / MetaColumnFactory |
string |
— | Avatica metadata factories. |
TypeSystem |
string |
— | Type system class name. |
Defaults are Calcite's own, read from CalciteConnectionProperty in the version this package references (1.43).
CalciteConnectionPropertiesSchemaMap
Exposes the schema.*-prefixed sub-properties of a CalciteConnectionProperties instance as a typed dictionary, so operand values can be passed to a custom schema factory:
var props = new CalciteConnectionProperties();
props.SchemaProperties["directory"] = "data/csv";
props.SchemaProperties["flavor"] = "scannable";
Related packages
| Package | Purpose |
|---|---|
Apache.Calcite.Data |
The ADO.NET provider. Executes SQL text through this convention. |
Apache.Calcite.Adapter.AdoNet |
Exposes any ADO.NET data source to Calcite as a federated schema. |
Further reading
License
Apache License 2.0.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- IKVM (>= 8.16.1)
- IKVM.Java.Extensions (>= 8.16.1)
- IKVM.Jdbc.Data (>= 1.0.13)
- IKVM.Maven.Sdk (>= 1.12.1)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Apache.Calcite.Extensions:
| Package | Downloads |
|---|---|
|
Apache.Calcite.Data
An in-process ADO.NET provider for Apache Calcite. Runs Calcite's SQL parser, planner and query engine inside the .NET process via IKVM, with no JDBC driver or server. |
|
|
Apache.Calcite.Adapter.AdoNet
An Apache Calcite adapter that exposes any ADO.NET database as a Calcite schema and pushes filters, projections, joins, aggregations, sorts and set operations down to it. Includes support for SQL Server, SQLite, ODBC, OLE DB and INFORMATION_SCHEMA databases. |
|
|
Apache.Calcite.Cosmos.Adapter
Apache Calcite adapter that plans queries against Azure Cosmos DB by generating Cosmos SQL. |
|
|
Apache.Calcite.Data.Common
The mapping between Apache Calcite SQL types and .NET types used by Apache.Calcite.Data and Apache.Calcite.Adapter.AdoNet: which .NET type each SQL type is read as, and the conversions in both directions. Extensible with custom type resolvers. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.1-pre.267 | 94 | 9/28/2026 |
| 2.0.1-pre.254 | 133 | 9/28/2026 |
| 2.0.1-pre.250 | 73 | 9/27/2026 |
| 2.0.1-pre.236 | 75 | 9/26/2026 |
| 2.0.1-pre.230 | 81 | 9/26/2026 |
| 2.0.1-pre.190 | 174 | 9/20/2026 |
| 2.0.1-pre.188 | 334 | 9/19/2026 |
| 2.0.0-pre.9 | 190 | 9/1/2026 |
| 2.0.0-pre.8 | 260 | 8/31/2026 |
| 2.0.0-pre.7 | 269 | 8/14/2026 |
| 2.0.0-pre.5 | 127 | 8/12/2026 |
| 2.0.0-pre.4 | 119 | 8/11/2026 |
| 2.0.0-pre.3 | 150 | 8/10/2026 |
| 2.0.0-pre.2 | 143 | 8/9/2026 |
| 2.0.0-pre.1 | 155 | 8/9/2026 |
| 1.0.3 | 157 | 6/4/2026 |
| 1.0.2 | 133 | 5/28/2026 |
| 1.0.1 | 132 | 5/26/2026 |
| 1.0.0 | 115 | 5/26/2026 |
| 0.0.4 | 154 | 1/29/2026 |