Documentation
¶
Overview ¶
Package functions holds SQL-value operations that don't need connection/session state: checked integer arithmetic, numeric + bitwise operators with SQL semantics, type-coercion helpers used by scalar-function arguments, and (in future PRs) the scalar function dispatcher + protoreflect <-> driver.Value marshaling.
Mirrors Java's fdb-relational-core/recordlayer/query/functions/ package plus the arithmetic helpers in ArithmeticValue. PhysicalOperator. All functions here are pure — no *Session, no *EmbeddedConnection — so the naive planner, Cascades planner, and any future frontend can call them uniformly.
This is the first move of RFC-021 Phase 1c1. Future commits add castValue, convertToProtoValue, protoValueToDriver and the scalar function core.
Index ¶
- Constants
- func AddInt64Checked(a, b int64) (int64, bool)
- func ApplyBitOp(left, right any, op string) (any, error)
- func ApplyMathOp(left, right any, op string) (any, error)
- func CastValue(v any, typeName string) (any, error)
- func CompareValues(a, b driver.Value) int
- func ConvertToProtoValue(fd protoreflect.FieldDescriptor, val any) (protoreflect.Value, error)
- func FlattenRecordWithOneField(v values.Value) values.Value
- func FormatDate(t time.Time) string
- func FormatTimestamp(t time.Time) string
- func FullIdToName(fid antlrgen.IFullIdContext) string
- func IsTruthy(v any) bool
- func LiteralMatchesPKKind(val any, kind protoreflect.Kind) bool
- func MulInt64Checked(a, b int64) (int64, bool)
- func ParseTimestamp(s string) (time.Time, bool)
- func ProtoValueToDriver(fd protoreflect.FieldDescriptor, v protoreflect.Value) driver.Value
- func ResolveQualifiedTableName(dottedName, schemaName string) (string, error)
- func StripIdentifierQuotes(s string) string
- func StripStringLiteralQuotes(s string) string
- func SubInt64Checked(a, b int64) (int64, bool)
- func ToFloat64(v any) (float64, bool)
- func ToIntegerArg(v any, funcName, argName string) (int64, error)
Constants ¶
const DateLayout = "2006-01-02"
DateLayout is the canonical ISO 8601 format used for DATE values.
const TimestampLayout = "2006-01-02 15:04:05"
TimestampLayout is the canonical ISO 8601 format used for TIMESTAMP values in proto storage and SQL display.
const UUIDProtoMessageName = "com.apple.foundationdb.record.UUID"
UUIDProtoMessageName is the fully-qualified name of the tuple_fields.UUID proto message that fdb-relational uses to store UUID column values (matches Java's TupleFieldsProto.UUID).
Variables ¶
This section is empty.
Functions ¶
func AddInt64Checked ¶
AddInt64Checked returns a+b and a success flag. Overflow iff the signs of a and b are the same and the result's sign flips. Matches Java's Math.addExact semantics.
func ApplyBitOp ¶
ApplyBitOp evaluates a bitwise operator. SQL standard + Java both require integer operands; float / string operands are an error (not a silent cast). The grammar exposes bitOperator tokens as concatenated text, so `<<` comes through as "<<" and `>>` as ">>".
Bit-shift operators `<<` / `>>` are intentionally NOT registered — matching fdb-relational 4.11.1.0's behaviour. Java tokenizes the operators but has no entry in the function registry, so its planner returns `RelationalException: Unsupported operator <<`. The Go embedded engine mirrors this by NOT having `<<` / `>>` cases here, so they fall through to the default ErrCodeUnsupportedOperation arm. Same architectural reason in both engines: no evaluator registered for shift operators. Per CLAUDE.md "Java↔Go conformance gotchas": doesn't work in Java → doesn't work in Go.
func ApplyMathOp ¶
ApplyMathOp evaluates one of +, -, *, /, % on two driver-level values with SQL semantics: NULL propagates, int64×int64 stays int64 and is overflow-checked (matching Java's ArithmeticValue. PhysicalOperator.*_LL), mixed int/float widens to float64. Division by zero errors with ErrCodeDivisionByZero; overflow errors with ErrCodeNumericValueOutOfRange.
func CastValue ¶
CastValue implements SQL CAST(v AS typeName). Splits INTEGER (32-bit) from BIGINT (64-bit) per Java CastValue range-check semantics: `CAST(9223372036854775807 AS INTEGER)` errors 22F3H because the value exceeds Integer.MAX_VALUE, even though the runtime representation of int64 doesn't need narrowing.
NULL casts to NULL of the target type. Unsupported source/target combinations error with ErrCodeUnsupportedOperation.
func CompareValues ¶
CompareValues returns -1/0/1 for a < b / a == b / a > b under SQL ordering semantics. NULL sorts before non-NULL (sort-site callers should honour NULLS FIRST / LAST before reaching here). Numeric promotion (int64 ↔ float64) mirrors ORDER BY rules; cross-type comparisons fall back to a stable type-name-based order so `=` correctly fails without a runtime panic.
func ConvertToProtoValue ¶
func ConvertToProtoValue(fd protoreflect.FieldDescriptor, val any) (protoreflect.Value, error)
ConvertToProtoValue converts a SQL-level driver.Value (int64, float64, string, bool, []byte) to a protoreflect.Value matching the field descriptor's kind. Range checks match Java's CastValue behaviour (LONG_TO_INT / DOUBLE_TO_LONG / DOUBLE_TO_FLOAT etc.).
A FLOAT/DOUBLE column carries every IEEE-754 value, NaN and ±Infinity included — Java's does, and so does the executor-side converter that UPDATE and INSERT … SELECT reach. The only float rejection left is the DOUBLE→FLOAT narrowing range check, and it is about what the NARROWING would CHANGE (a finite double past ±MaxFloat32 silently becomes Infinity), not about finiteness as such.
An ARRAY column takes the evaluated array literal — the []any an ArrayConstructorValue produces (Java's LightArrayConstructorValue evals to a List) — and converts each element through the same scalar lanes, mirroring Java's MessageHelpers.coerceArray element-wise coercion. A NON-nullable array is a flat repeated field; a NULLABLE array is stored through the NullableArrayWrapper (`message W { repeated E values = 1; }`), where a present wrapper with an empty list keeps [] distinct from NULL — Java's exact wire shape.
func FlattenRecordWithOneField ¶
FlattenRecordWithOneField is Java's SqlFunctionCatalog.flattenRecordWithOneField (SqlFunctionCatalog.java:98-108).
WHY IT EXISTS. SQL's grammar gives `(expr)` and a one-element record literal the SAME parse: there is no one-tuple literal syntax to tell them apart, so the parser cannot decide. Java resolves the ambiguity by POSITION instead of by parse, and the split is the whole design:
- the record constructor ALWAYS builds a record — ExpressionVisitor's visitRecordConstructor (ExpressionVisitor.java:918-925) goes straight to RecordConstructorValue.ofColumns with no one-element unwrap, which is why `SELECT (1 + 2)` is a one-field STRUCT and `SELECT ((1 + 2))` nests twice;
- every FUNCTION ARGUMENT is then flattened back down by this function, which is why `(3 + 4) * 5` multiplies by the scalar 7 and not by a record. Java states the precedence outright: when a value can be read as a single-item record or as the value itself, "the precedence is always given to the latter" (SqlFunctionCatalog.java:88-92).
So a parenthesised scalar survives as a record only where nothing consumes it as an argument — projection position. That is not a quirk to be normalised away; it is the observable difference between the two positions.
WHERE IT APPLIES. Java hangs it off resolveScalarFunction's argument mapping (SemanticAnalyzer.java:991-994, :1109, :1121, :1166), and BaseVisitor's resolveFunction (BaseVisitor.java:253-261) defaults flattenSingleItemRecords to true. Because ExpressionVisitor routes arithmetic (:731), comparison (:699), bitwise (:691), logical (:509), NOT (:501), IS NULL (:581), LIKE (:615), IN (:627) and BETWEEN (:716-722) all through resolveFunction, every one of those operand positions flattens.
THE ONE OPT-OUT is flattenSingleItemRecords=false, passed only to the `__internal_array` that builds the row list of the VALUES and inline-table paths (QueryVisitor.java:720 and :802). There a one-column row MUST stay a row: flattening it would turn `VALUES (1), (2)` into a bare element list and destroy the row structure the explode depends on. Callers on that path must therefore NOT call this function.
The recursion has two arms, exactly as Java does: a one-field record collapses to its single element and is re-examined (so nested parens peel completely), and any other node rebuilds itself with flattened children (so a record buried under an array or a cast is still reached).
func FormatDate ¶
FormatDate formats a time.Time as the canonical DATE string (date only).
func FormatTimestamp ¶
FormatTimestamp formats a time.Time as the canonical TIMESTAMP string.
func FullIdToName ¶
func FullIdToName(fid antlrgen.IFullIdContext) string
FullIdToName converts a FullId parse-tree node to a dot-separated, quote-stripped name. Used for table names in INSERT / UPDATE / DELETE and by the scalar function library when an argument is a qualified column reference.
func IsTruthy ¶
IsTruthy returns true when v is a non-nil, non-zero boolean or non-zero numeric. Used by SELECT-projection boolean coercion and by the `IF(cond, a, b)` scalar function.
func LiteralMatchesPKKind ¶
func LiteralMatchesPKKind(val any, kind protoreflect.Kind) bool
LiteralMatchesPKKind reports whether a driver-value literal is a safe tuple element for a column of the given proto kind. Only numeric / string / bytes kinds are in scope — booleans and enums can be columns in theory but are unusual and left to the scan path for now.
func MulInt64Checked ¶
MulInt64Checked returns a*b and a success flag. Mirrors Java's Math.multiplyExact. Uses the textbook "divide back" check: overflow iff (a*b)/b != a. The first special case (a == MinInt64 && b == -1) is REQUIRED: p/b would compute MinInt64 / -1, which traps with SIGFPE on amd64 — we must detect and bail before the divide. The second symmetric case is redundant (divide-back would flag it without a hardware trap, because the divisor is MinInt64 not -1) but kept for parallelism with the first so the intent is obvious.
func ParseTimestamp ¶
ParseTimestamp attempts to parse a string as a TIMESTAMP using multiple common layouts. Returns the parsed time in UTC or false.
func ProtoValueToDriver ¶
func ProtoValueToDriver(fd protoreflect.FieldDescriptor, v protoreflect.Value) driver.Value
ProtoValueToDriver maps a protoreflect.Value (read off a record) into a driver.Value for SQL-level consumption. Widens all integer kinds to int64 so the SQL evaluator doesn't need per-kind fan-out.
func ResolveQualifiedTableName ¶
ResolveQualifiedTableName validates and strips a schema qualifier from a dotted table name. Ports Java's SemanticAnalyzer.tableExists qualifier validation (lines 189-207):
- No dot → returns the name as-is.
- One dot (schema.table) → validates qualifier matches schemaName (case-insensitive), returns just the table name.
- Two+ dots → error (Java returns INTERNAL_ERROR).
schemaName is the current schema context (e.g., from session). Returns (tableName, errCode, errMsg). errCode is "" on success.
func StripIdentifierQuotes ¶
StripIdentifierQuotes normalizes an identifier's raw parse text to its canonical lookup form: quoted identifiers are stripped of their surrounding `"` or backticks and otherwise preserved case-for-case; unquoted identifiers are folded to upper case. Mirrors Java's SemanticAnalyzer.normalizeString (case-sensitive=false default).
func StripStringLiteralQuotes ¶
StripStringLiteralQuotes removes a single pair of surrounding single-quotes and unescapes doubled-quote ” sequences to a single quote. Used by every SQL string literal that reaches our code via the parser (the parser leaves the literal's raw source text, including quotes).
func SubInt64Checked ¶
SubInt64Checked returns a-b and a success flag. Overflow iff the signs of a and b differ and the sign of the result flips against a.
func ToFloat64 ¶
ToFloat64 coerces int64 / float64 to float64 for mixed-type arithmetic. Returns false for any other input type — callers error out with "requires numeric operands" on the failure path.
func ToIntegerArg ¶
ToIntegerArg coerces v to int64 for integer-typed function arguments (position, length, count). Whole-value floats are accepted as a convenience (`LEFT('hi', 2.0)` works); fractional floats and non-numeric types error rather than silently truncating to 0.
Types ¶
This section is empty.