RonSQL Restrictions and MySQL Compatibility#
RonSQL deliberately implements a subset of MySQL. The design rule is that a query is either executed with MySQL-compatible semantics or rejected with an error message; unsupported constructs are not silently ignored or approximated. This chapter is the consolidated reference for what is not supported, for the restricted forms of the supported clauses, and for the ways in which RonSQL behaves differently from MySQL even for accepted queries.
The supported functionality is described in the RonSQL overview, Single-table queries in RonSQL, Join queries in RonSQL, CTEs and subqueries in RonSQL and Data types and output. Remember that RonSQL reads ordinary RonDB tables: any query that RonSQL rejects can still be executed through a MySQL server against the same tables, with full MySQL semantics.
Statement-level restrictions#
-
SELECTis the only statement. The full statement shape is[EXPLAIN] [FRAGS_PER_WORKER = n] [WITH ...] SELECT ... ; -
The statement must be terminated by a semicolon, and exactly one statement is allowed per request. A missing semicolon is reported as unexpected end of input, and anything other than whitespace following the semicolon is an error. Multi-statement requests are not supported.
-
No data modification:
INSERT,UPDATE,DELETEandREPLACEare not supported. -
No DDL:
CREATE,ALTER,DROPandTRUNCATEare not supported. Tables are created and altered through a MySQL server. -
No
SET,SHOW,USEorDESCRIBE. The database is selected per request (thedatabasefield in the REST API, or the-Doption ofronsql_cli), and all tables of a query are read from that database. A table name may be qualified with that database; a qualifier naming another database is rejected withCross-database table references are not supported. -
No transaction control:
BEGIN,START TRANSACTION,COMMITandROLLBACKare not supported. Every query executes independently as a committed read (see the semantic differences section below). -
No prepared statements or parameter placeholders, and no user or session variables; the
?and@characters are not valid anywhere in a RonSQL statement outside string literals. -
No stored procedures or stored functions.
-
Views are not visible to RonSQL. A view exists only in the MySQL server layer, so querying one fails with a table-not-found error. The same applies to tables in storage engines other than RonDB: RonSQL can only read
ENGINE=NDBtables. -
WITH RECURSIVEis not supported; theWITHclause only accepts the CTE forms described in the CTE chapter. -
EXPLAINoutput is only available in the text output formats, not in the JSON output formats.
No SQL comments#
RonSQL does not support SQL comments in any form. This regularly
surprises users, so it deserves emphasis: -- ... line comments,
# ... line comments and /* ... */ block comments (including
/*! ... */ version comments and optimizer hint comments) are all not
recognized. Only whitespace is skipped between tokens.
A # character is reported as an illegal token, and /* causes a
syntax error, so those comment forms fail loudly. The -- form is more
dangerous: RonSQL reads it as two minus signs, which in a numeric
context parses as a double negation. In most places this leads to an
error, but in HAVING clauses and aggregate arguments, text that MySQL
would ignore as a comment can silently change the meaning of the query.
For example:
MySQL treats -- 2 as a comment and evaluates COUNT(*) > 1; RonSQL
evaluates COUNT(*) > 1 - (-2), i.e. COUNT(*) > 3. Always strip
comments from queries before sending them to RonSQL.
Lexical restrictions#
Identifiers and keywords#
-
Keywords are case insensitive, as in MySQL.
-
Identifiers (column, table, CTE and alias names) are case sensitive and must be written exactly as the name is stored. Note that MySQL treats column names case insensitively, so this is a difference for accepted queries as well. Index names in index hints are case insensitive.
-
Identifiers can be unquoted or quoted with backticks. A literal backtick inside a quoted identifier is written as two backticks. Double quotes are not supported for identifiers (or strings), so the
ANSI_QUOTESSQL mode has no RonSQL equivalent. -
An unquoted identifier may not coincide with a MySQL keyword, reserved or not, whether or not RonSQL implements it, nor with the RonSQL keyword
FRAGS_PER_WORKER. For example, unquoted columns namedstatus,name,date,valueorcommentare rejected with an error suggesting backtick quotation. Keywords that RonSQL implements, such asyear,keyorcount, give a plain syntax error when used as unquoted identifiers. MySQL only restricts reserved keywords, so many identifiers that are legal unquoted in MySQL require backticks in RonSQL. -
Identifiers are limited to 64 bytes. Exceeding the limit is an error; RonSQL never truncates identifiers. (The MySQL documentation states these limits in characters, but the actual limit is in bytes; with UTF-8 these differ whenever an identifier contains a character above
U+007F.) -
Aliases after
ASare identifiers, so they are also limited to 64 bytes (MySQL allows 256 for column aliases), and a string literal cannot be used as an alias. TheASkeyword is required, both for output aliases and for table aliases. Without an alias, the output column name is the expression text itself and is subject to the same 64-byte limit, so a select expression longer than 64 bytes requires a shorter alias viaAS. -
Unquoted identifiers may contain ASCII letters, digits,
$and_, plus charactersU+0080--U+FFFF. Quoted identifiers may contain any character in the Basic Multilingual Plane except NUL. Supplementary characters (U+10000and higher, such as most emoji) are not permitted in identifiers, though they are permitted in string literals. -
Outside string literals and quoted identifiers, the characters
",#,:,?,@,[,],{,},~and\are illegal. Whitespace is limited to space, tab, carriage return and newline.
String literals#
-
Strings use single quotes only. Double-quoted strings are not supported.
-
A literal apostrophe is written as
''. The MySQL backslash escapes are supported:\0,\',\",\b,\n,\r,\t,\Zand\\. As in MySQL,\%and\_keep their backslash (for use inLIKEpatterns), and a backslash before any other character is dropped. Adjacent string literals separated by whitespace are concatenated, as in MySQL. -
Character set introducers (such as
_utf8mb4'...') and theCOLLATEclause are not supported. -
Hexadecimal literals (
0x...,x'...'), bit literals (b'...'), typed literals such asDATE '2024-01-01', andTRUEandFALSEare not supported.
Numeric literals#
-
Integer literals must fit in a signed 64-bit integer (at most 9223372036854775807). Larger literals are an error; MySQL would instead promote them to unsigned or
DECIMAL. Negative numbers are written with the unary minus operator, so the smallest 64-bit integer, -9223372036854775808, cannot be written as a literal. -
Fractional literals must be written with digits on both sides of the decimal point (
0.5, not.5or5.). They are parsed as double-precision floating point rather than asDECIMALas in MySQL, except when compared with aDECIMALcolumn, where the literal text is converted exactly. -
Scientific notation is not supported;
1e5is read as an identifier, which typically results in an unknown-column error.
Character encoding#
-
RonSQL requires UTF-8 for the input SQL and produces UTF-8 output. The input is strictly validated: overlong encodings, surrogate code points, code points above
U+10FFFFand stray continuation bytes are all rejected with specific errors. -
NUL bytes are not allowed anywhere in the input. A NUL character inside a string value can be written with the
\0escape sequence. -
No Unicode normalization is performed. Identifiers must match the stored names byte for byte.
Unsupported clauses and expressions#
Select list#
-
SELECT *is not supported, nor ist.*. Every output column must be written explicitly. -
A select expression can only be: a (possibly qualified) column name; an aggregate function
COUNT,SUM,MIN,MAXorAVGover an arithmetic expression;COUNT(*);AVRO(column)in projection-only queries; a correlated aggregate subquery; orGREATEST/LEASTover scalar CTE outputs. Each may be aliased withAS. -
Arbitrary expressions are not allowed in the select list:
SELECT a + 1,SELECT 1,SELECT UPPER(name)and expressions over aggregate results such asSELECT SUM(a) / SUM(b)are all rejected. Arithmetic inside an aggregate argument is fine:SUM(a * b + 1)is supported.
Features with no support at all#
Each of the following is absent from the RonSQL grammar and produces an error (for most keywords, an “unimplemented keyword” error) if used:
-
DISTINCT, includingCOUNT(DISTINCT ...). -
The aggregate functions
STD,STDDEV,VARIANCE,VAR_POP,BIT_AND,GROUP_CONCATand similar. -
UNION,INTERSECTandEXCEPT. -
BETWEEN; writex >= a AND x <= binstead. -
The infix
NOTformsx NOT IN (...)andx NOT LIKE ...are syntax errors; prefixNOTworks, e.g.NOT (x IN (1, 2)). TheESCAPEclause ofLIKEis not supported. -
REGEXP/RLIKEandSOUNDS LIKE. -
IS TRUE,IS FALSEandIS UNKNOWN. -
CASTandCONVERT. -
Window functions (
OVER,ROW_NUMBER,RANKand so on). -
GROUP BY ... WITH ROLLUP. -
The null-safe equality operator
<=>. Also, theNULLkeyword can only appear inIS NULL/IS NOT NULL; a comparison likex = NULLis a syntax error. -
Derived tables:
FROM (SELECT ...)is not supported. Use aWITHclause where the supported CTE forms suffice. -
INNER JOIN(writeJOIN),RIGHT JOIN,FULL JOIN, theCROSS JOINkeyword,NATURALjoins,STRAIGHT_JOIN,LATERALandJOIN ... USING. -
ANY,SOMEandALLsubqueries, andNOT IN (SELECT ...). -
Nearly all of MySQL’s built-in functions: string functions (
CONCAT,SUBSTRING,UPPER, ...), mathematical functions (ABS,ROUND,FLOOR, ...), control-flow functions (IF,IFNULL,COALESCE,NULLIF), and date and time functions (NOW,CURDATE,DATEDIFF,EXTRACT, ...). The only scalar functions RonSQL supports areGREATESTandLEAST,DATE_ADDandDATE_SUBwith constant arguments in theWHEREclause, andAVROin theSELECTlist of projection-only queries. A function call with one column argument in theSELECTlist gives the error “Unknown function. The only non-aggregate function RonSQL supports in the SELECT list is AVRO(column).”; other function calls give a syntax error or an unimplemented keyword error.
Restricted forms of supported clauses#
The following clauses are supported, but in a more restricted form than in MySQL. The linked chapters describe the supported forms in detail; this section summarizes the restrictions.
-
WHERE: Every term must be explicitly boolean; MySQL’s implicit truth test of a bare column (WHERE flag_col) is rejected. Every comparison must have a column as one operand, so constant comparisons likeWHERE 1 = 1are rejected. Arithmetic is not supported in conditions, neither on columns (WHERE a + 1 > b) nor between constants (WHERE a > 1 + 2); only a unary minus on a literal and theDATE_ADDandDATE_SUBfunctions are evaluated. The operator!is not supported inWHERE; useNOT. A literal must have the type of the column it is compared with; see Data types and output. AWHEREclause may have at most 64 top-levelANDconjuncts. Comparisons between columns of the same table require identical column types. Comparisons between columns of different tables are only supported in aggregate queries, or when they can be used as index bounds of a join.CASEcannot be used as aWHEREpredicate.EXISTSsubqueries must be top-levelANDconjuncts.GREATEST/LEASTinWHEREcan only be compared to a constant with a range operator (<,<=,>,>=) and only over column and integer arguments. -
LIKE: The left operand must be a stored-table column and the right operand a string literal pattern. -
DATE_ADD/DATE_SUB: The first argument must be a string literal (or a nestedDATE_ADD/DATE_SUB) and theINTERVALamount a constant; these functions cannot be applied to column values, and they are only available in theWHEREclause. The supportedINTERVALunits areMICROSECOND,SECOND,MINUTE,HOUR,DAY,WEEK,MONTH,QUARTERandYEAR; the composite units such asDAY_HOURare not supported. -
Aggregate arguments: Only integer literals can be used in arithmetic.
CASEmust have exactly oneWHENand anELSEbranch, theTHENandELSEvalues cannot beNULL, and the condition must be a comparison or a chain of comparisons combined only withANDor only withOR.GREATESTandLEASToperands must be columns of integer types, integer literals or nestedGREATESTandLEAST, with at least one column. -
GROUP BY: Column names only (bare or table-qualified). Expressions, aliases and ordinal positions are not supported. -
ORDER BY: Column names and output aliases only, each optionally withASCorDESC. Expressions and ordinal positions are not supported;ORDER BY SUM(x)is a syntax error, so alias the aggregate and order by the alias. -
LIMIT: Only the single-argument formLIMIT n. NoOFFSETand noLIMIT offset, count. -
HAVING: Conditions may combine aggregate function calls and numeric literals using+,-,*,/, comparisons,IS [NOT] NULL,AND,ORandNOT. Aliases of aggregate outputs are not supported (except aliases ofSELECT-list subqueries): repeat the aggregate expression instead. Plain table columns are not supported inHAVING; put conditions on them inWHERE. The conditions are evaluated in double precision. -
Joins: Only
JOIN ... ON(inner) andLEFT [OUTER] JOIN ... ON. TheONclause must be a conjunction of plain column equalities (a.col = b.col) linking the joined table to an earlier table, and the joined table must have a primary key, unique index or ordered index on the join columns; any other condition belongs inWHERE. A comma cross-join is only supported for scalar and single-row key-lookup CTEs. In aggregate join queries, aggregates andGROUP BYcolumns can only use the last joined table and the tables on its path to the first table.WHEREconditions on a left-joined stored table must rejectNULL. A pushed join may contain at most 32 internal operations (a unique-index lookup counts as two). -
Index hints:
FORCE/USE/IGNOREINDEXare only honored on the root table of a query or CTE body; a hint on a joined table is an error. OnlyFORCE INDEXreports index names that do not exist. -
CTE bodies and subqueries: Bodies must be aggregate queries, single-row key lookups (a single-table
SELECTof plain columns whoseWHEREbinds the full primary key by equality), or last-N bodies (a single-tableSELECTof plain columns withORDER BYandLIMIT); any other non-aggregating CTE body is rejected. A last-N CTE must be the first table of the query that reads it, and when that query joins or groups the rows, the body must select the whole primary key of its table. Stored tables joined onto a CTE that is the first table must be joined by their primary key or a unique key, and join keys must have the same type on both sides, also for CTE outputs.HAVINGis not supported in CTE bodies.ORDER BY ... LIMITinside a CTE body selects the kept group set (top-N; keys must be body outputs, at most 8, no stringMIN/MAXkeys, noOFFSET); inside a subquery both are rejected rather than silently ignored. The argument of aSUM/MIN/MAX/AVGCTE output must be a plain column (AVGadditionally rejects string and temporal arguments). Column-vs-column comparisons in a CTE body’sWHEREclause require two identical-type columns of the body’s firstFROMtable. Subquery results and correlation columns must be numeric. See the CTE chapter for details.
Data type restrictions#
Data types and output describes in detail which data types can be used where. In summary:
-
BLOBandTEXTcolumns (all sizes) cannot be used anywhere in a RonSQL query: not as join columns, not in the select list, not inGROUP BY, not as aggregate arguments and not inWHEREcomparisons.JSONand spatial (GEOMETRY) columns are stored as BLOBs in RonDB and share these restrictions. Other columns of a table containing such columns can be used normally. -
BITcolumns are not supported in any position. -
BINARYandVARBINARYcolumns can be selected in projection-only queries (base64 encoded in JSON output) and decoded withAVRO(), but they cannot be aggregated, grouped or compared inWHERE. -
ENUMandSETcolumns are stored by the MySQL server in an internal binary encoding that RonSQL does not decode, so values would not be returned as their labels. Do not useENUMorSETcolumns in RonSQL queries. -
DECIMAL: Aggregates overDECIMALcolumns with a nonzero scale are computed in double precision (see the next section). Aggregates over scale-zeroDECIMALcolumns are computed with 64-bit integers, so values outside the 64-bit range are not supported; inside CTE bodies, aggregating such a column with a declared precision above 18 (signed) or 19 (unsigned) is rejected. -
Temporal types:
DATE,YEAR,DATETIME,TIMEandTIMESTAMPcolumns can be selected and used withMIN/MAX, butSUMandAVGover them are rejected.WHEREcomparisons against string literals are supported forDATE,DATETIMEandTIMESTAMPcolumns, but not forTIMEorYEARcolumns, which also cannot be grouped on. -
CHARandVARCHARcolumns supportMINandMAX, but notSUMorAVG. Comparing non-ASCII string literals with columns using character sets other than UTF-8 is not supported. -
Tables using the pre-MySQL-5.6 storage formats for temporal or
DECIMALcolumns are not supported.
Semantic differences from MySQL#
Even for accepted queries, the following behaviors differ from a MySQL server with default settings.
-
Time zone: RonSQL has no time zone support and operates as if the time zone were UTC. In particular,
TIMESTAMPvalues are displayed in UTC, andTIMESTAMPliterals are interpreted as UTC. To get identical results from a MySQL server, run it withSET time_zone = '+00:00'. -
No implicit type conversion: A literal compared with a column must have the column’s type; for example, an integer column cannot be compared with
1.5or'1', aDATETIMEcolumn cannot be compared with a date without a time part, and an integer literal outside the range of the column’s type is an error rather than a comparison that is always true or false. -
Integer overflow:
SUMover integer columns is computed in 64 bits, and arithmetic in aggregate arguments never wraps around. An overflow fails the query with NDB error 1860, whereas MySQL returns a widerDECIMALresult. -
Floating-point aggregation order:
SUMandAVGoverFLOATorDOUBLEcolumns are computed as partial sums on many table fragments in parallel and then combined. Floating-point addition is not associative, so the result can differ from MySQL in the last digits, and can even differ slightly between two runs of the same query. -
Division: The
/operator performs floating-point division, whereas MySQL performs exactDECIMALdivision. Integer operands of/whose magnitude exceeds 2 to the power of 53 (about 9 * 10^15) produce an overflow error.DIVperforms integer division as in MySQL. -
DECIMAL aggregation:
SUM,MIN,MAXandAVGoverDECIMALcolumns with a nonzero scale, and arithmetic involving such columns, are computed in double precision and can therefore differ from MySQL’s exact decimal arithmetic in the last digits, in particular for values with more than about 15 significant digits.SUM,MINandMAXdirectly over a column are displayed with the column’s scale when its precision is at most 15; other results are displayed as double values, e.g.26777.986559999998where MySQL displays26777.986560. -
AVG:
AVGis computed as a double-precision division ofSUMbyCOUNT, and displayed with the same number of decimals as in MySQL. The displayed value matches MySQL except for sums beyond the exact range of double precision. -
HAVING arithmetic:
HAVINGconditions are evaluated in double precision, so comparisons involving 64-bit integer aggregate values beyond 2 to the power of 53 can round. -
Floating-point display:
FLOATvalues are displayed as in MySQL.DOUBLEvalues and aggregate results computed in double precision are displayed in the shortest form that represents the value, in fixed-point notation without an exponent, which can differ from MySQL’s formatting. Infinities and NaN are displayed asNULL. -
Row order: Without
ORDER BY, the order of the result rows is unspecified, and it often differs from the order MySQL returns. In particular, groups are not returned inGROUP BYorder. -
Correlated COUNT subqueries: In a
WHEREcondition comparing a column with a correlated scalar subquery, outer rows without matching inner rows never satisfy the condition, even when the subquery is aCOUNTthat MySQL evaluates to 0. -
Identifier length: An over-long identifier is an error; MySQL truncates some identifiers instead.
-
Index hints:
USE INDEXandIGNORE INDEXwith names of indexes that do not exist are accepted, whereas MySQL reports an error. -
Committed reads over multiple batches: RonSQL reads committed data without taking locks, matching RonDB’s read committed isolation level and the way a MySQL server reads RonDB tables. A query that scans a large table fetches rows in many batches, so if other transactions commit changes while the query runs, the result can reflect a mix of states that never coexisted at a single point in time. This is the expected behavior at the read committed isolation level.
Operational limits#
-
REST request size:
Internal.maxReqSizecaps the HTTP request body (default 4 MiB). Larger requests are refused with HTTP status 413. -
REST response size:
Internal.MaxRespSizecaps the response body the server will build (default 64 MiB; 0 means unlimited). A query whose result exceeds the cap fails with HTTP status 500 and an error naming the cap and suggesting to narrow the query, addLIMIT, or raise the configured limit. -
Concurrency: the REST API server executes at most
RonSQL.NumThreadsqueries at once (default 16) and queues at mostRonSQL.MaxQueuedRequestsmore (default 1024); further requests fail with HTTP status 503. There is no query timeout. -
ORDER BYon a projection-only query that no ordered index can serve buffers the full result for a client-side sort. The sort buffer is capped at 1000000 rows and 256 MB; exceeding the cap is a permanent error suggesting a tighterWHEREclause or an aggregate query. -
Aggregation: at most 127
GROUP BYcolumns and 255 aggregate results per query (AVGcounts as two), and the aggregate results of one group, including stringMIN/MAXvalues, must fit in 16 KB. The number of groups is limited by the query memory of the data nodes. -
INlists: up to 4095 values are executed as primary key lookups or index ranges; longer lists are evaluated as filters. -
An
INsubquery, a correlated scalar subquery and anEXISTSsubquery may produce at most 1000 values or rows. At most 128 subqueries can be used in theSELECTlist. -
A
WHEREclause is limited to 64 top-level conjuncts, and a condition on CTE outputs to 16 top-levelORbranches. -
A pushed join is limited to 32 internal operations (a unique-index lookup counts as two), including the operations of CTE bodies, with at most 8 key columns per join and at most 16 columns passed from other tables to the aggregating table. A statement can define fewer than 64 CTEs.
-
Identifiers and aliases are limited to 64 bytes; integer literals to the signed 64-bit range.
When a query is rejected#
RonSQL rejects queries with an error describing the unsupported construct, usually pointing at the offending position in the statement, together with an error class and HTTP status as described in the RonSQL overview. Common ways to bring a query into the supported subset:
-
Remove comments,
DISTINCT, and expressions from the select list; alias aggregates withASand reference the aliases inORDER BY, but repeat the aggregate expressions inHAVING. -
Rewrite predicates into the supported forms: replace
BETWEENwith two comparisons, compute constant arithmetic yourself, move arithmetic off the column side of a comparison, write literals of the column’s type, replacex NOT IN (...)withNOT (x IN (...)), and move non-equality join conditions fromONintoWHERE. -
Backtick-quote identifiers that collide with MySQL keywords.
-
Make sure join conditions are equalities on indexed columns of identical types, and give CTE bodies an aggregate, make them single-row primary key lookups, or select the last N rows with
ORDER BYandLIMIT. -
If the query cannot be expressed in the RonSQL subset, run it through a MySQL server instead: the same tables are fully accessible with complete MySQL semantics, typically at a higher cost for large analytical scans.