Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@
import static org.apache.calcite.sql.fun.SqlLibraryOperators.UN_BASE64;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.URL_DECODE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.URL_ENCODE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.UUIDV4;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.UUIDV7;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.XML_TRANSFORM;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ABS;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ACOS;
Expand Down Expand Up @@ -871,6 +873,8 @@ void populate1() {
defineReflective(RAND_INTEGER, BuiltInMethod.RAND_INTEGER.method,
BuiltInMethod.RAND_INTEGER_SEED.method);
defineReflective(RANDOM, BuiltInMethod.RAND.method);
defineReflective(UUIDV4, BuiltInMethod.UUIDV4.method);
defineReflective(UUIDV7, BuiltInMethod.UUIDV7.method);

defineMethod(ACOS, BuiltInMethod.ACOS.method, NullPolicy.STRICT);
defineMethod(ACOSD, BuiltInMethod.ACOSD.method, NullPolicy.STRICT);
Expand Down
33 changes: 33 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
Expand Down Expand Up @@ -329,6 +330,38 @@ public static boolean throwUnless(boolean condition, String message) {
return condition;
}

/** Generates a random UUID (version 4). Implements the SQL UUIDV4() function. */
@NonDeterministic
public static UUID uuidv4() {
return UUID.randomUUID();
}

/**
* Generates a time-ordered UUID (version 7). Implements the SQL UUIDV7() function.
*
* <p>This static helper is a single-call, stateless variant: it produces a
* valid RFC 9562 UUID v7 but does not guarantee monotonic ordering across
* multiple calls in the same millisecond. For proper per-query monotonicity
* the code-generator uses {@link UuidFunction#uuidv7()} instead.
*
* <p>128-bit layout: 48-bit unix_ts_ms | 4-bit ver=7 | 12-bit rand_a |
* 2-bit var=10 | 62-bit rand_b.
*/
@NonDeterministic
public static UUID uuidv7() {
final ThreadLocalRandom rng = ThreadLocalRandom.current();
final long millis = System.currentTimeMillis();
// rand_a: 12 random bits (bits [11:0] of MSB)
final long randA = rng.nextLong() & 0x0FFFL;
// rand_b: 62 random bits (bits [61:0] of LSB)
final long randB = rng.nextLong() & 0x3FFF_FFFF_FFFF_FFFFL;
// MSB: timestamp(48) | version(4)=7 | rand_a(12)
final long msb = (millis << 16) | 0x7000L | randA;
// LSB: variant(2)=0b10 | rand_b(62)
final long lsb = 0x8000_0000_0000_0000L | randB;
return new UUID(msb, lsb);
}

public static String uuidToString(UUID uuid) {
return uuid.toString();
}
Expand Down
145 changes: 145 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/UuidFunction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.runtime;

import org.apache.calcite.linq4j.function.Deterministic;

import java.security.SecureRandom;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

/**
* Function object for {@code UUIDV4} and {@code UUIDV7}.
*
* <p>{@code UUIDV4} generates a random (version 4) UUID as per RFC 4122.
* {@code UUIDV7} generates a time-ordered (version 7) UUID as per RFC 9562,
* using the <em>Monotonic Random</em> method (Section 6.2, Method 2):
* <ul>
* <li>A 48-bit Unix timestamp (millisecond precision) occupies the high field.
* <li>Version = 7 in the 4-bit ver field.
* <li>The 12-bit rand_a field is seeded freshly (random) at the start of each
* millisecond, then acts as a monotonically incrementing counter within
* the same millisecond, guaranteeing strict ordering.
* <li>Variant = 0b10 (IETF RFC 4122 / RFC 9562).
* <li>62 fresh random bits occupy rand_b.
* </ul>
*
* <p>Monotonicity and thread-safety are achieved without a lock: the timestamp
* and rand_a counter are packed into a single {@link AtomicLong} and updated
* via a compare-and-swap (CAS) retry loop.
*
* <p>State packing layout (64 bits):
* <pre>
* [63:12] last unix_ts_ms (52 bits, enough for ~142 million years)
* [11: 0] rand_a counter (12 bits, 0–4095)
* </pre>
*
* <p>Marked {@link Deterministic} so that the code generator instantiates one
* instance per query, not once per row.
*/
@SuppressWarnings("unused")
public class UuidFunction {

/** Source of random bits.
*
* <p>RFC 9562 section 6.9 recommends that implementations use a
* cryptographically secure pseudorandom number generator, so that generated
* values are unguessable. {@link SecureRandom} is thread-safe, and is the
* same source that {@link UUID#randomUUID()} uses for {@code UUIDV4}. */
private static final SecureRandom RANDOM = new SecureRandom();

/** Bitmask for the 12-bit rand_a field (bits [11:0]). */
private static final long RAND_A_MASK = 0x0FFFL;

/** Bitmask for the 62-bit rand_b field (bits [61:0]). */
private static final long RAND_B_MASK = 0x3FFF_FFFF_FFFF_FFFFL;

/** Bit pattern that encodes UUID version 7 in bits [15:12] of the MSB. */
private static final long VERSION_7 = 0x7000L;

/** Bit pattern that encodes the IETF variant (0b10) in bits [63:62] of the LSB. */
private static final long VARIANT_IETF = 0x8000_0000_0000_0000L;

/**
* Packed monotonic state: high 52 bits = last timestamp (ms), low 12 bits = seqA.
* Initial value 0 ensures the first call's real timestamp always exceeds it,
* triggering a fresh random seed for rand_a.
*/
private final AtomicLong state = new AtomicLong(0L);

/** Creates a UuidFunction.
*
* <p>Marked deterministic so that the code generator instantiates one once
* per query, not once per row. */
@Deterministic public UuidFunction() {

Check failure on line 88 in core/src/main/java/org/apache/calcite/runtime/UuidFunction.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ_J5suMKItL4neUykMy&open=AZ_J5suMKItL4neUykMy&pullRequest=5146
}

/** Implements the {@code UUIDV4()} SQL function.
* Returns a random (version 4) UUID as per RFC 4122. */
public UUID uuidv4() {
return UUID.randomUUID();
}

/**
* Implements the {@code UUIDV7()} SQL function.
* Returns a time-ordered (version 7) UUID as per RFC 9562.
*
* <p>128-bit layout (MSB → LSB):
* <pre>
* |&lt;--- unix_ts_ms (48) ---&gt;| ver(4)=7 |&lt;rand_a(12)&gt;|
* | var(2)=10 |&lt;------------ rand_b (62) ------------&gt;|
* </pre>
*
* <p>Thread-safety is achieved lock-free via a CAS loop on {@link #state}.
* In the common case (no contention) the loop executes exactly once.
*/
public UUID uuidv7() {
long ms;
long seqA;
long current;
long next;
do {
current = state.get();
final long lastMs = current >>> 12; // high 52 bits
final long lastSeq = current & RAND_A_MASK; // low 12 bits
ms = System.currentTimeMillis();
if (ms > lastMs) {
// New millisecond: advance the clock and seed rand_a randomly so that
// the initial value of rand_a for this ms is unpredictable.
seqA = RANDOM.nextLong() & RAND_A_MASK;
} else {
// Same millisecond (or a clock regression): increment the counter to
// preserve monotonic order.
ms = lastMs;
seqA = lastSeq + 1;
if (seqA > RAND_A_MASK) {
// rand_a overflowed 12 bits; bump the logical clock by 1 ms.
ms = lastMs + 1;
seqA = 0;
}
}
next = (ms << 12) | seqA;
// CAS: if another thread has already updated state, retry with fresh reads.
} while (!state.compareAndSet(current, next));

// MSB: unix_ts_ms (48) | version 7 (4) | rand_a (12)
final long msb = (ms << 16) | VERSION_7 | seqA;
// LSB: IETF variant (2) | rand_b (62) — always fresh random bits
final long lsb = VARIANT_IETF | (RANDOM.nextLong() & RAND_B_MASK);
return new UUID(msb, lsb);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,13 @@ private static SqlNode createDatetimeCastSpec(String typeAlias, RelDataType type

@Override public void unparseCall(SqlWriter writer, SqlCall call,
int leftPrec, int rightPrec) {
if (call.getOperator() == SqlStdOperatorTable.SUBSTRING) {
if (call.getOperator() == SqlLibraryOperators.UUIDV4) {
// UUIDV4() → NEWID() in SQL Server, which returns a random RFC 4122
// version 4 UUID.
super.unparseCall(writer,
SqlLibraryOperators.NEWID.createCall(call.getParserPosition()),
leftPrec, rightPrec);
} else if (call.getOperator() == SqlStdOperatorTable.SUBSTRING) {
if (call.operandCount() != 3) {
throw new IllegalArgumentException("MSSQL SUBSTRING requires FROM and FOR arguments");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ public OracleSqlDialect(Context context) {
return;
}

if (call.getOperator() == SqlLibraryOperators.UUIDV4) {
// UUIDV4() → UUID() in Oracle 23ai+.
// Oracle's UUID() returns RAW(16) and is RFC 4122 v4 compliant.
// UUIDV7 has no Oracle equivalent and is left untranslated.
super.unparseCall(writer,
SqlLibraryOperators.ORACLE_UUID.createCall(call.getParserPosition()),
leftPrec, rightPrec);
return;
}

if (call.getOperator().getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT) {
writer.sep(call.getOperator().getName());
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2824,4 +2824,56 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding
OperandTypes.family(SqlTypeFamily.TIMESTAMP),
OperandTypes.family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.TIMESTAMP)),
SqlFunctionCategory.TIMEDATE);

/** The {@code UUIDV4()} function, which generates a random (version 4) UUID.
* This is a Calcite extension; not yet in any SQL standard.
* Dialect-specific targets: {@code NEWID()} on SQL Server,
* {@code uuidv4()} on PostgreSQL 17+, {@code UUID()} on Oracle 23ai+. */
@LibraryOperator(libraries = {CALCITE})
public static final SqlBasicFunction UUIDV4 =
SqlBasicFunction.create("UUIDV4",
ReturnTypes.explicit(SqlTypeName.UUID),
OperandTypes.NILADIC,
SqlFunctionCategory.SYSTEM)
.withDeterministic(false)
.withDynamic(true);

/** The {@code UUIDV7()} function, which generates a time-ordered (version 7) UUID.
* This is a Calcite extension; not yet in any SQL standard.
* Dialect-specific targets: {@code uuidv7()} on PostgreSQL 17+.
* SQL Server and Oracle have no native equivalent. */
@LibraryOperator(libraries = {CALCITE})
public static final SqlBasicFunction UUIDV7 =
SqlBasicFunction.create("UUIDV7",
ReturnTypes.explicit(SqlTypeName.UUID),
OperandTypes.NILADIC,
SqlFunctionCategory.SYSTEM)
.withDeterministic(false)
.withDynamic(true);

/** The {@code NEWID()} function for SQL Server.
* Generates a random RFC 4122 version 4 UUID, and may be used anywhere an
* expression is allowed. This is the SQL Server target when translating
* {@code UUIDV4()}. */
@LibraryOperator(libraries = {MSSQL})
public static final SqlBasicFunction NEWID =
SqlBasicFunction.create("NEWID",
ReturnTypes.explicit(SqlTypeName.UUID),
OperandTypes.NILADIC,
SqlFunctionCategory.SYSTEM)
.withDeterministic(false)
.withDynamic(true);

/** The {@code UUID()} function for Oracle 23ai+.
* Generates an RFC 4122 version 4 (random) UUID.
* Returns {@code RAW(16)}; use {@code RAWTOHEX()} or cast to format as a string.
* This is the Oracle 23ai target when translating {@code UUIDV4()}. */
@LibraryOperator(libraries = {ORACLE})
public static final SqlBasicFunction ORACLE_UUID =
SqlBasicFunction.create("UUID",
ReturnTypes.explicit(SqlTypeName.UUID),
OperandTypes.NILADIC,
SqlFunctionCategory.SYSTEM)
.withDeterministic(false)
.withDynamic(true);
}
3 changes: 3 additions & 0 deletions core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
import org.apache.calcite.runtime.SqlFunctions.FlatProductInputType;
import org.apache.calcite.runtime.UrlFunctions;
import org.apache.calcite.runtime.Utilities;
import org.apache.calcite.runtime.UuidFunction;
import org.apache.calcite.runtime.XmlFunctions;
import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
import org.apache.calcite.runtime.variant.VariantNull;
Expand Down Expand Up @@ -618,6 +619,8 @@ public enum BuiltInMethod {
RAND_INTEGER(RandomFunction.class, "randInteger", int.class),
RAND_INTEGER_SEED(RandomFunction.class, "randIntegerSeed", int.class,
int.class),
UUIDV4(UuidFunction.class, "uuidv4"),
UUIDV7(UuidFunction.class, "uuidv7"),
SAFE_ADD(SqlFunctions.class, "safeAdd", double.class, double.class),
SAFE_DIVIDE(SqlFunctions.class, "safeDivide", double.class, double.class),
SAFE_MULTIPLY(SqlFunctions.class, "safeMultiply", double.class, double.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12819,4 +12819,47 @@ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) {
+ "FROM \"SCOTT\".\"EMP\"\n"
+ "WHERE \"DEPTNO\" = \"t1\".\"DEPTNO\")");
}

/** Tests that {@code UUIDV4()} is translated to the correct dialect-specific
* function name when generating SQL for different databases.
*
* <p>{@code UUIDV4()} is a Calcite extension ({@link SqlLibrary#CALCITE});
* {@code withLibrary(CALCITE)} is required so the parser recognises it.
* PostgreSQL 17+ passes through unchanged; SQL Server maps to
* {@code NEWID()}; Oracle 23ai+ maps to {@code UUID()}. */
@Test void testUuidv4Func() {
final String query = "SELECT uuidv4() FROM \"employee\"";
final String expectedDefault = "SELECT UUIDV4()\n"
+ "FROM \"foodmart\".\"employee\"";
final String expectedMssql = "SELECT NEWID()\n"
+ "FROM [foodmart].[employee]";
final String expectedOracle = "SELECT UUID()\n"
+ "FROM \"foodmart\".\"employee\"";
// withLibrary(CALCITE) is required so that the parser recognises UUIDV4().
sql(query).withLibrary(SqlLibrary.CALCITE)
.ok(expectedDefault)
.withMssql().ok(expectedMssql)
// PostgreSQL 17+ has native uuidv4(); name passes through unchanged.
.withPostgresql().ok(expectedDefault)
.withOracle().ok(expectedOracle);
}

/** Tests that {@code UUIDV7()} is translated to the correct dialect-specific
* function name when generating SQL for different databases.
*
* <p>{@code UUIDV7()} is a Calcite extension ({@link SqlLibrary#CALCITE});
* {@code withLibrary(CALCITE)} is required so the parser recognises it.
* PostgreSQL 17+ has native {@code uuidv7()}; MSSQL and Oracle have no
* native UUID v7 equivalent and emit {@code UUIDV7()} unchanged. */
@Test void testUuidv7Func() {
final String query = "SELECT uuidv7() FROM \"employee\"";
final String expectedDefault = "SELECT UUIDV7()\n"
+ "FROM \"foodmart\".\"employee\"";
// withLibrary(CALCITE) is required so that the parser recognises UUIDV7().
// PostgreSQL 17+ has native uuidv7(). MSSQL and Oracle have no equivalent.
sql(query).withLibrary(SqlLibrary.CALCITE)
.ok(expectedDefault)
.withPostgresql().ok(expectedDefault)
.withMssql().ok("SELECT UUIDV7()\nFROM [foodmart].[employee]");
}
}
Loading
Loading