Repository navigation
- feat: OpenSearch 3 support (#124) [Amin Ghadersohi]. The OpenSearch dialect uses
_plugins/_sqlby default (OpenSearch 3 removed_opendistro/_sql) and falls back once to_opendistro/_sqlfor Open Distro / Elasticsearch 7.10; single-table columns compile unqualified; aliases are listed as views in v2 mode. - fix: SQLAlchemy 2 and DB-API correctness fixes found by live testing (#124) [Amin Ghadersohi]
- follow the SQL cursor across pages on Elasticsearch and OpenSearch, so results larger than
fetch_sizeare no longer cut to the first page; Elasticsearch cursor pagination by Evan Rusackas (from #123) - never render the dummy
defaultschema, including on projected columns - reflect byte/short as
SmallInteger, unsigned_long asBigIntegerand date_nanos asDateTime; unknown result types no longer raiseKeyError - table/view listing and the OpenSearch
SELECT 1ping work without cluster privileges;has_tableis true for aliases server_version_infofrom the cluster; SQL compilation cache enabled- boolean URL arguments (
v2,verify_certs, ...) accepttrue/1/yes/onandfalse/0/no/off
- follow the SQL cursor across pages on Elasticsearch and OpenSearch, so results larger than
- fix: reflected columns report the field's type (
DOUBLE,FLOAT,INTEGER,LONG,BOOLEAN) instead ofLONG/FLOATfor every numeric and boolean field (#126) [Amin Ghadersohi]. Reflected types retain their generic SQLAlchemy compilation on other dialects, including when copying a table to another database.
Behaviour changes / upgrade notes
-
time_zoneon the OpenSearch dialect (odelasticsearch) is logged as ignored and dropped (UTC,Zand+00:00silently). The SQL plugin always ignored it and returns UTC. Migration: none required; removetime_zonefrom the URL to silence the warning and convert to local time in the application. -
Supported SQL temporal values are decoded to
datetime.datetime/datetime.date/datetime.timeinstead of strings. Timezone-aware Elasticsearch values are normalised to UTC to avoid applying one row's DST offset to an entire result column; decoded OpenSearch timestamps remain naive UTC. This is not a guarantee that every date-mapped column returns an object: conversion depends on the SQL engine's result type and value, and nanosecond or unrecognised representations remain strings. In particular, legacy OpenSearch results can label a full timestamp asdate; previously evenv2=truequeries with LIMIT could take that legacy path. LIMIT queries on a v2 connection now stay on v2, preserving its functions and timestamp result types. Migration: accept both temporal objects and preserved strings; use objects directly (orstr()/isoformat()them), and do not discard precision when parsing preserved strings. Convert UTC-aware values to the desired display timezone explicitly. -
double,float,half_floatandscaled_floatcolumns are reflected asFloat(Pythonfloat) instead ofNumeric(Decimalrounded to 10 places). Migration: code expectingDecimalshould convert explicitly. -
Client exceptions are raised as DB-API exceptions from
es.exceptions: connection and TLS errors, authentication/authorization errors asOperationalError;RequestErrorandNotFoundErrorasProgrammingError; every otherTransportErrorasDatabaseError. The originalelasticsearch.*/opensearchpy.*exception is chained as__cause__. Migration: catches.exceptions.*(or the DB-API classes) instead ofelasticsearch.exceptions.*/opensearchpy.exceptions.*, or inspect__cause__. -
The default OpenSearch
sql_pathis_plugins/_sql, with one fallback to_opendistro/_sqlwhen the cluster has no_plugins/_sqlendpoint (no handler found, or Elasticsearch 7.10'sinvalid_index_name_exceptionfor_plugins, which isindex_not_found_exceptionwithaction.auto_create_index=false). A proxy or IAM policy that only allows_opendistro/_sqlanswers 403, which is not a missing endpoint, so there is no fallback. Migration: addsql_path=_opendistro/_sqlto the URL in that case (an explicitsql_pathis never replaced). -
An unpaged OpenSearch result that may have been cut at
plugins.query.size_limitraisesDataErrorinstead of returning partial rows. Migration: add aLIMITof at most the size limit, or narrow the query. -
Full result sets are held in memory. A DB-API
SELECTwithoutLIMITused to return at mostfetch_size(10 000) rows; every page is now fetched. Migration: add aLIMITto queries over large indices. -
Single-table columns compile unqualified on OpenSearch (
SELECT a FROM t ORDER BY a), which OpenSearch 3 requires. A column keeps its table qualifier when a label of another expression in the same select has its name. Migration: none expected; raw SQL is not affected. -
In v2 mode, GROUP BY, DISTINCT, aggregates, joins and other statements the SQL plugin cannot page are sent without
fetch_size, avoiding the legacy engine's default top-200 result. This does not mean the v2 engine returns every bucket: unpaged aggregations stop at a backend bucket ceiling and cannot be cursor-paged. That ceiling is 1000 buckets on Open Distro and OpenSearch 1.x; OpenSearch 2.x/3.x also stop atplugins.query.size_limitwhen it is below 1000, which is the default on 2.11–2.15 (200), on eithersql_path. A result at the applicable ceiling without an explicit LIMIT of at most that ceiling now raisesDataErrorinstead of silently returning a partial answer, so exactly 200 groups raise on OpenSearch 2.11–2.15 at the default size limit (or when the server version cannot be read), and are accepted where 200 is not a ceiling (Open Distro, OpenSearch 1.x, and 2.19/3.x at their default size limit). Plain SELECTs without LIMIT are still paged on OpenSearch (_plugins/_sql); on Open Distro (_opendistro/_sql), whose v2 engine cannot page, v2 statements are sent unpaged as in 0.2.13, and if OpenSearch refuses a paged request for lack of privileges it is retried once unpaged.v2=falsefrom a URL now means false (it used to switch v2 mode on) and keeps the legacy engine; an aggregation that reaches the legacy engine's 200-bucket cap is asked of the v2 engine, rather than assuming those 200 groups are complete. Migration: narrow high-cardinality aggregations or use an explicit bounded LIMIT; do not assume an unpaged aggregate is complete merely because v2 is enabled. -
Statements over subqueries return correct results (e.g. Superset virtual datasets). A subquery without a LIMIT used to stop at the size limit (200 rows on Open Distro) and silently drop rows from the outer result; the legacy engine (v2 off) ignored the outer WHERE; and Open Distro dropped a row when an outer WHERE and LIMIT met without ORDER BY. Subqueries now get an explicit LIMIT of one search window, run on the v2 engine, and on Open Distro the outer LIMIT is applied to the rows.
-
A Superset virtual dataset (or any statement over a subquery) whose subquery returns more than 10000 rows raises
DataErroron OpenSearch and Open Distro, e.g. a virtual dataset ofSELECT * FROM idxover an index with 10k+ documents. 0.2.13 answered these with silently wrong results (a filteredCOUNTof 0 where the true count was 9). The SQL plugin cannot page a subquery, so no complete answer exists. On Open Distro, which refuses to count past the window, a subquery of exactly 10000 rows raises too. Migration: narrow the subquery (the virtual dataset's SQL) with a WHERE, an aggregation or a LIMIT of at most 10000, or use a physical dataset. -
Reflected column types render differently.
double,float,integer,longandbooleanfields reportDOUBLE,FLOAT,INTEGER,LONGandBOOLEAN; 0.2.13 reportedLONGfordouble,integer,longandboolean.byteandshortreportINTEGER,half_floatFLOAT,scaled_floatDOUBLEandunsigned_longLONG(0.2.13 reportedSTRINGfor all buthalf_float): the SQL plugins cannotCASTto their own names, and Superset's defaultcolumn_type_mappingsdo not know them. Superset recognises every rendered name, so these numeric fields are now numeric and boolean fields are boolean rather than numeric. Migration: none for Superset; code comparing the rendered type strings should expect the new names. -
An explicit
LIMITon a v2 plain SELECT bypassesplugins.query.size_limit, so only the 10000-row search window is checked (an exactly-200-row answer toLIMIT 1001is complete, not truncated). A plain SELECT whose LIMIT is above 10000 is replayed without the LIMIT through SQL cursors and cut at its LIMIT locally; if cursors are unavailable (including a limited cursor whose next page is empty) it raisesDataError. Migration: none; results above 10000 rows need SQL cursors on the cluster. -
Grouped top-N on the v2 engine is validated. The v2 engine sorts
GROUP BY ... ORDER BY COUNT(*) DESC LIMIT 5only after its capped composite aggregation, so it can return the wrong five groups. The driver probes the unsorted group listing (one additional SQL request) and raisesDataErrorwhen that listing reaches its bucket ceiling. Ordering only by grouped fields (GROUP BY k ORDER BY k DESC LIMIT 3) is sorted inside the aggregation, so it is not probed. Migration: narrow the grouping, or usev2=falsefor legacy top-N. -
OFFSET is an exception to the grouped-field ordering shortcut:
SELECT k, COUNT(*) FROM t GROUP BY k ORDER BY k LIMIT 3 OFFSET 999is probed and raisesDataErrorat the bucket ceiling instead of silently returning one row from 5000 groups. -
OpenSearch 1.x v2 plain SELECTs stay on the v2 engine (paging them would hand them to the legacy engine), preserving floating values and timestamp objects. The server version is discovered once per connection, best effort; when it cannot be read, unpaged v2 semantics are preferred to legacy paging.
-
The legacy engine no longer confuses a table-qualified column with a select-list alias of the same name (
ORDER BY grp.knext tov AS ksorted byv): such aliases are renamed in the statement sent and restored in the result. -
Open Distro returns every row of a plain SELECT. Open Distro 1.13 stops a SELECT without its own LIMIT at
opendistro.query.size_limit(200) and has SQL cursors off by default; 0.2.13 returned those 200 rows silently. A capped answer is now asked for again with an explicit LIMIT of the 10000-row search window, and beyond that paged with the SQL cursor. A plain SELECT whose own LIMIT is past that window (e.g. SQL Lab'sLIMIT 100001), which Open Distro refuses, is answered the same way and cut at its LIMIT. Migration: results above 10000 rows needopendistro.sql.cursor.enabled: trueon the cluster, otherwise the query raisesDataErrorsaying so. -
OpenSearch 2.11/2.15 v2 aggregations can stop at
plugins.query.size_limit
(200 by default), on either SQL endpoint. Ambiguous full buckets now raise
DataError, rather than silently returning incomplete groups. Exactly 200
remains accepted on Open Distro and OpenSearch 1.x, and on newer servers at
their default size limit. A custom size limit below 1000 is also checked on
OpenSearch 2.x/3.x. -
An explicit v2 SELECT
LIMITbypasses the query size limit; only the search
window is checked. Plain SELECTs with a LIMIT above 10000 are replayed without
LIMIT using SQL cursors, then capped locally; unavailable cursors raise
DataError, including a limited cursor whose next page is empty. -
The v2 engine sorts grouped top-N results after its capped composite
aggregation, soGROUP BY ... ORDER BY COUNT(*) DESC LIMIT 5can select the
wrong groups even though only five rows are returned. The driver now probes
the underlying group listing and raisesDataErrorat its bucket ceiling.
Narrow the grouping or usev2=falsefor legacy top-N. Safe small listings
incur one additional SQL request. Ordering only by grouped fields (e.g.
GROUP BY k ORDER BY k DESC LIMIT 3) is sorted inside the aggregation and is
not probed. -
OpenSearch 1.x v2 plain SELECTs stay on the v2 engine to preserve floating
values and timestamp objects. Version discovery is best effort and cached;
when unavailable, unpaged v2 semantics are preferred to legacy paging.