Apache.Calcite.Extensions
2.0.1-pre.267
dotnet add package Apache.Calcite.Extensions --version 2.0.1-pre.267
NuGet\Install-Package Apache.Calcite.Extensions -Version 2.0.1-pre.267
<PackageReference Include="Apache.Calcite.Extensions" Version="2.0.1-pre.267" />
<PackageVersion Include="Apache.Calcite.Extensions" Version="2.0.1-pre.267" />
<PackageReference Include="Apache.Calcite.Extensions" />
paket add Apache.Calcite.Extensions --version 2.0.1-pre.267
#r "nuget: Apache.Calcite.Extensions, 2.0.1-pre.267"
#:package Apache.Calcite.Extensions@2.0.1-pre.267
#addin nuget:?package=Apache.Calcite.Extensions&version=2.0.1-pre.267&prerelease
#tool nuget:?package=Apache.Calcite.Extensions&version=2.0.1-pre.267&prerelease
Apache.Calcite.Extensions
Apache.Calcite.Extensions runs Apache Calcite query plans as compiled .NET code. Calcite runs on .NET through IKVM; this package adds:
ClrCursorConvention, a Calcite calling convention that compiles a query plan to aSystem.Linq.Expressionstree instead of generating Java source for the Janino compiler.- A prepare pipeline (
ClrPrepareImpl) that takes a SQL statement to such a plan. - A table SPI for .NET: tables that return
IEnumerable,IAsyncEnumerable, an expression tree, or a cursor such as aDbDataReader. CalciteConnectionProperties, typed access to Calcite's connection options.
Most applications get this package as a dependency of Apache.Calcite.Data (the ADO.NET provider) or Apache.Calcite.Adapter.AdoNet, and never call it directly: every statement run through a CalciteConnection is already planned and executed this way. Reference it directly when you want to:
- drive Calcite's planner yourself, without an ADO.NET connection;
- implement tables in .NET;
- build Calcite connection properties in code.
Targets .NET 8; tested on .NET 8 and .NET 10. It references Calcite 1.43 and IKVM 8.16.1, and requires IKVM 8.16.0 or later.
Install
dotnet add package Apache.Calcite.Extensions
What the convention gives you
No Java compiler at prepare time. Preparing a statement builds an expression tree; each compiled plan is JIT-compiled the first time it is opened.
.NET methods as SQL functions. A static .NET method registered with Calcite's usual function API is called directly from the compiled plan:
rootSchema.add("MY_FUNC", org.apache.calcite.schema.impl.ScalarFunctionImpl.create( (java.lang.Class)typeof(MyFunctions), "myFunc"));Synchronous and asynchronous execution from one plan. A compiled plan opens a forward-only cursor with
Open()orOpenAsync(CancellationToken), and the cursor advances withRead()orReadAsync(CancellationToken). You can choose on every row: a row read withReadand the next withReadAsyncare consecutive rows of the same result, and eachReadAsynctoken is passed down to the table the row comes from. This is the shape ofDbDataReader.Works alongside Calcite's own engine.
ClrCursorConventionmirrors Calcite'sEnumerableConventionnode for node and uses the same row types. Calcite's own rules stay registered, and converters connect the two conventions, so anything this convention does not implement is planned by Calcite as usual and its rows pass between the two unchanged.
The results are Calcite's: the conventions are tested against each other by running the same SQL through both and comparing the rows.
Running a plan yourself
To plan and run SQL without an ADO.NET connection, use a Calcite Frameworks planner with this convention's rules added, then compile the physical plan with ClrCursorRelImplementor.
A Frameworks planner registers only Calcite's rules, so add this convention's in a program that runs first. This one does nothing else:
using System.Collections.Generic;
using org.apache.calcite.plan;
using org.apache.calcite.rel;
using org.apache.calcite.tools;
public sealed class AddRulesProgram(IReadOnlyList<RelOptRule> rules) : Program
{
public RelNode run(RelOptPlanner planner, RelNode rel, RelTraitSet requiredOutputTraits, java.util.List materializations, java.util.List lattices)
{
foreach (var rule in rules)
planner.addRule(rule);
return rel;
}
}
Then plan the statement. rootSchema is your Calcite root schema, sql the statement, and dataContext a DataContext over the same schema:
using Apache.Calcite.Extensions.Adapter.Cursor;
using org.apache.calcite.tools;
var calcRules = new java.util.ArrayList();
foreach (var rule in Apache.Calcite.Extensions.Adapter.Cursor.ClrCursorRules.CalcRules())
calcRules.add(rule);
var config = Frameworks.newConfigBuilder()
.defaultSchema(rootSchema)
.programs(
Programs.sequence(
new AddRulesProgram(Apache.Calcite.Extensions.Adapter.Cursor.ClrCursorRules.Rules()),
Programs.standard(Apache.Calcite.Extensions.Rel.Metadata.ClrCursorRelMetadata.Provider),
Programs.hep(calcRules, true, Apache.Calcite.Extensions.Rel.Metadata.ClrCursorRelMetadata.Provider)))
.build();
var planner = Frameworks.getPlanner(config);
var logical = planner.rel(planner.validate(planner.parse(sql))).project();
// keep the root's own traits, which carry any ORDER BY collation, and ask for this convention
var traits = logical.getTraitSet().replace(Apache.Calcite.Extensions.Adapter.Cursor.ClrCursorConvention.Instance).simplify();
var physical = planner.transform(0, traits, logical);
Finally compile the plan and read it, asynchronously or synchronously:
var implementor = new Apache.Calcite.Extensions.Adapter.Cursor.ClrCursorRelImplementor(
physical.getCluster().getRexBuilder(), new java.util.HashMap());
var factory = implementor.ImplementRoot((Apache.Calcite.Extensions.Adapter.Cursor.ClrCursorRel)physical, ClrCursorPrefer.Array);
// opening does the plan's up-front work (a sort drains its input, a table runs its query); reading returns 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);
This code is run by a test in the repository (ReadmeExampleTests).
Things to know about the result:
- A row is an
object[]when you passClrCursorPrefer.Array. A result with one column is the column's value itself, not a one-element array. - Values are Java values:
java.lang.Integer,java.lang.String,java.math.BigDecimaland so on, as Calcite's type factory declares them. - A factory can be opened any number of times. Each open returns an independent cursor, positioned before the first row. Dispose the cursor when you are done; disposing it releases the whole plan.
- Each open is compiled on first use.
ImplementRootbuilds both the synchronous and the asynchronous open; each is JIT-compiled the first time it is called.
Why the program is shaped this way, if you want to write your own:
Rules()goes on the planner;CalcRules()runs afterwards as a separatePrograms.heppass. The calc rules cannot be planner rules: Calcite's Volcano planner compares plans by row count only, so a calc is never cheaper than the project it replaces, and it does not match transformation rules against physical nodes.- Keep Calcite's rules registered.
Rules()adds to Calcite's rules rather than replacing them. Calcite's logical rewrites are needed forAVG,DISTINCTaggregates andOVERwindows, and a node this convention cannot implement is planned by Calcite and connected with a converter. - Decorrelation runs as in Calcite.
Programs.standardturns a scalar sub-query orEXISTSinto a join; anUNNESTover a correlation variable keeps its correlate. - Use
ClrCursorRelMetadata.Providerfor both passes. It is Calcite's default metadata provider with handlers added for this convention's nodes; without it, some row-count bounds, collations and costs are computed as if for a generic node, and the planner can choose different plans from Calcite's. ClrRelOptUtil.RegisterDefaultRulesregisters Calcite's default rules followed byRules(), for a planner you create yourself rather than throughFrameworks.
Writing tables in .NET
Calcite's own ScannableTable and QueryableTable work, but require a linq4j Enumerable. This package adds two table interfaces, in Apache.Calcite.Extensions.Schema, that a table can implement instead. Each has a required synchronous member and an asynchronous member that defaults to it:
| Interface | Required | Asynchronous (optional) | Use for |
|---|---|---|---|
IClrScannableTable |
Scan(DataContext) returning IEnumerable<object?[]> |
ScanAsync(DataContext) returning IAsyncEnumerable<object?[]> |
rows as arrays |
IClrCursorTable |
Open(DataContext) returning IClrCursor<object?[]> |
OpenAsync(DataContext, CancellationToken) |
a source that is already a cursor, such as a DbDataReader; each ReadAsync token reaches it |
- Row values must be the Java values Calcite's type factory declares for each column (
java.lang.IntegerforINTEGER,java.lang.StringforVARCHAR, and so on). They are not converted; a value of the wrong type fails when the column is read. - If your rows arrive over I/O, implement the asynchronous member too. The default reads the synchronous member without suspending, so it would block an asynchronous caller.
- If your source is only asynchronous, implement the asynchronous member and write the synchronous one by blocking on it. Clear
SynchronizationContext.Currentbefore starting the asynchronous call, not only around the wait, or it can deadlock under a UI or ASP.NET-style context. - Cancellation: an
IClrScannableTablereceives the token of the asynchronous open throughGetAsyncEnumerator; anIClrCursorTablereceives a token with everyReadAsync. Calcite-implemented parts of a plan observe cancellation through theDataContext's cancel flag instead.
Key public types
| Type | Purpose |
|---|---|
ClrCursorConvention |
The calling convention. ClrCursorConvention.Instance is the trait. |
ClrCursorRules |
Rules() for the planner and CalcRules() for the pass after it. |
ClrRelOptUtil |
RegisterDefaultRules(planner, enableMaterializations): Calcite's default rules plus this convention's. |
ClrCursorRelMetadata |
Provider, the metadata provider to plan with. |
ClrCursorRelImplementor |
Compiles a physical plan: ImplementRoot returns a ClrCursorFactory. |
IClrCursorFactory / ClrCursorFactory |
A compiled plan: Open(DataContext), OpenAsync(DataContext, CancellationToken) and ElementType. |
IClrCursor / IClrCursor<T> |
A forward-only cursor: Read(), ReadAsync(CancellationToken), Current, and both Dispose forms. ClrCursor and ClrCursor<T> are abstract bases for implementing one. |
ClrCursorPrefer |
The row representation requested; Array gives object[] rows. |
IClrScannableTable / IClrCursorTable |
The .NET table interfaces described above. |
IClrPrepare / ClrPrepareImpl |
The SQL prepare pipeline Apache.Calcite.Data uses: PrepareSql returns an IClrPrepare.Signature, whose Open and OpenAsync run the statement. |
ClrCursorRel |
The interface every node of the convention implements, for writing a node of your own. Implement is required; ImplementAsync defaults to it, which is correct only for a node whose implementation does not visit a child node. |
CalciteConnectionProperties |
Typed properties over Calcite's connection options. |
CalciteConnectionPropertiesSchemaMap |
The schema.* operands passed to a schema factory. |
The convention's nodes (ClrCursorCalc, ClrCursorHashJoin, ClrCursorWindow and the rest) and their rules are public, so you can subclass or re-register them. The runtime operators the nodes compile calls to are internal; a node of your own calls its own methods.
CalciteConnectionProperties
Typed .NET properties over a Calcite java.util.Properties map, so you do not have to spell property names or enum values as strings:
using Apache.Calcite.Extensions.Config;
using org.apache.calcite.avatica.util;
using org.apache.calcite.config;
var props = new CalciteConnectionProperties();
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;
Pass an existing Properties to the constructor to read or modify it in place.
| Property | Type | Calcite's default | Description |
|---|---|---|---|
Model |
string |
— | Model URI, or an inline JSON model prefixed with inline:. |
Schema |
string |
— | Default schema name. |
CaseSensitive |
bool |
from Lex (true for 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 |
How quoted identifiers are stored. |
UnquotedCasing |
Casing? |
from Lex |
How unquoted identifiers are stored. |
Fun |
string |
standard |
Function libraries, e.g. oracle,spatial. |
Conformance |
SqlConformanceEnum |
DEFAULT |
SQL conformance level. |
DefaultNullCollation |
NullCollation |
HIGH |
Where nulls sort when NULLS FIRST/LAST is omitted. |
TimeZone |
string |
JVM default | Session time zone. |
Locale |
string |
Locale.ROOT |
Session locale. |
ForceDecorrelate |
bool |
true |
Decorrelate sub-queries as far as possible. |
TopDownGeneralDecorrelationEnabled |
bool |
false |
Decorrelate 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 temporary tables. |
NullEqualToEmpty |
bool |
true |
Treat empty strings as null, for the Druid adapter. |
Spark |
bool |
false |
Use Spark for processing that cannot be pushed to the source. |
TopdownOpt |
bool |
calcite.planner.topdown.opt |
Top-down optimization in the Volcano planner. |
LenientOperatorLookup |
bool |
false |
Accept calls to functions not in the operator table. |
DruidFetch |
int |
16384 |
Rows to fetch per Druid query. |
SchemaFactory |
string |
— | Schema factory class (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. |
A property that has not been set reads as Calcite's declared default. The four whose default comes from Lex read as null when unset (false for CaseSensitive), and Calcite applies the Lex value when the connection is made.
CalciteConnectionPropertiesSchemaMap
CalciteConnectionProperties.SchemaProperties exposes the schema.* entries as a dictionary keyed without the prefix. Calcite passes them as operands to the 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 through this convention. |
Apache.Calcite.Adapter.AdoNet |
Exposes any ADO.NET data source to Calcite as a 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.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.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.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 | 0 | 9/28/2026 |
| 2.0.1-pre.254 | 39 | 9/28/2026 |
| 2.0.1-pre.250 | 41 | 9/27/2026 |
| 2.0.1-pre.236 | 51 | 9/26/2026 |
| 2.0.1-pre.230 | 54 | 9/26/2026 |
| 2.0.1-pre.190 | 163 | 9/20/2026 |
| 2.0.1-pre.188 | 324 | 9/19/2026 |
| 2.0.0-pre.9 | 188 | 9/1/2026 |
| 2.0.0-pre.8 | 258 | 8/31/2026 |
| 2.0.0-pre.7 | 266 | 8/14/2026 |
| 2.0.0-pre.5 | 125 | 8/12/2026 |
| 2.0.0-pre.4 | 117 | 8/11/2026 |
| 2.0.0-pre.3 | 149 | 8/10/2026 |
| 2.0.0-pre.2 | 142 | 8/9/2026 |
| 2.0.0-pre.1 | 151 | 8/9/2026 |
| 1.0.3 | 152 | 6/4/2026 |
| 1.0.2 | 129 | 5/28/2026 |
| 1.0.1 | 128 | 5/26/2026 |
| 1.0.0 | 112 | 5/26/2026 |
| 0.0.4 | 151 | 1/29/2026 |