diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index 2ca2528d5c0f..efde33f3465b 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -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; @@ -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); diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index 4b9b48041fad..4519a9d25e69 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -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; @@ -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. + * + *

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. + * + *

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(); } diff --git a/core/src/main/java/org/apache/calcite/runtime/UuidFunction.java b/core/src/main/java/org/apache/calcite/runtime/UuidFunction.java new file mode 100644 index 000000000000..f0fb5bb8d865 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/runtime/UuidFunction.java @@ -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}. + * + *

{@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 Monotonic Random method (Section 6.2, Method 2): + *

+ * + *

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. + * + *

State packing layout (64 bits): + *

+ *  [63:12]  last unix_ts_ms  (52 bits, enough for ~142 million years)
+ *  [11: 0]  rand_a counter   (12 bits, 0–4095)
+ * 
+ * + *

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. + * + *

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. + * + *

Marked deterministic so that the code generator instantiates one once + * per query, not once per row. */ + @Deterministic public UuidFunction() { + } + + /** 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. + * + *

128-bit layout (MSB → LSB): + *

+   * |<--- unix_ts_ms (48) --->| ver(4)=7 |<rand_a(12)>|
+   * | var(2)=10 |<------------ rand_b (62) ------------>|
+   * 
+ * + *

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); + } +} diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java index 964fa03f8f91..5fc13c58e0dd 100644 --- a/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java +++ b/core/src/main/java/org/apache/calcite/sql/dialect/MssqlSqlDialect.java @@ -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"); } diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java index 9aab15f5e247..79bf2184c945 100644 --- a/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java +++ b/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java @@ -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; diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java index 539d6b327fd0..f32c61f2c253 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java @@ -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); } diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java index 629357b4e07e..c78d6aa9b0c4 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -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; @@ -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), diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java index 01f157893623..e84a85b4d4cd 100644 --- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java +++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java @@ -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. + * + *

{@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. + * + *

{@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]"); + } } diff --git a/core/src/test/java/org/apache/calcite/runtime/UuidFunctionTest.java b/core/src/test/java/org/apache/calcite/runtime/UuidFunctionTest.java new file mode 100644 index 000000000000..518b712b21e5 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/runtime/UuidFunctionTest.java @@ -0,0 +1,112 @@ +/* + * 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.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; + +/** + * Tests for {@link UuidFunction}. + */ +class UuidFunctionTest { + + /** Verifies that {@code UUIDV4()} produces version-4, IETF-variant UUIDs. */ + @Test void testUuidv4Version() { + final UuidFunction f = new UuidFunction(); + for (int i = 0; i < 20; i++) { + final UUID uuid = f.uuidv4(); + assertThat("version", uuid.version(), is(4)); + assertThat("variant", uuid.variant(), is(2)); // IETF = 2 + } + } + + /** Verifies that {@code UUIDV7()} produces version-7, IETF-variant UUIDs. */ + @Test void testUuidv7Version() { + final UuidFunction f = new UuidFunction(); + for (int i = 0; i < 20; i++) { + final UUID uuid = f.uuidv7(); + assertThat("version", uuid.version(), is(7)); + assertThat("variant", uuid.variant(), is(2)); // IETF = 2 + } + } + + /** + * Verifies that the embedded timestamp in a {@code UUIDV7} UUID is within + * a reasonable window of the real clock (within ±1 second). + */ + @Test void testUuidv7Timestamp() { + final UuidFunction f = new UuidFunction(); + final long before = System.currentTimeMillis(); + final UUID uuid = f.uuidv7(); + final long after = System.currentTimeMillis(); + + // The 48-bit unix_ts_ms is the top 48 bits of mostSigBits, + // i.e. mostSigBits >>> 16. + final long embeddedMs = uuid.getMostSignificantBits() >>> 16; + + assertThat("embedded timestamp >= before", embeddedMs, + greaterThanOrEqualTo(before)); + // allow up to 1 s ahead (due to counter overflow advancing the clock) + assertThat("embedded timestamp <= after + 1000", embeddedMs, + lessThanOrEqualTo(after + 1000)); + } + + /** + * Verifies that UUIDs generated by consecutive calls to {@code UUIDV7()} + * are strictly increasing (monotonic) when compared as 128-bit unsigned + * integers, which is the sortability guarantee of RFC 9562 §6.2. + * + *

We generate 4 096 UUIDs in a tight loop to exercise the within-ms + * counter path. + */ + @Test void testUuidv7Monotonicity() { + final UuidFunction f = new UuidFunction(); + final int count = 4096; + final List uuids = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + uuids.add(f.uuidv7()); + } + for (int i = 1; i < count; i++) { + final UUID prev = uuids.get(i - 1); + final UUID curr = uuids.get(i); + // UUID natural ordering uses unsigned comparison of the 128-bit value. + assertThat("UUID[" + i + "] > UUID[" + (i - 1) + "]", + curr.compareTo(prev), greaterThanOrEqualTo(1)); + } + } + + /** + * Verifies that the 12-bit rand_a field is properly masked and never exceeds + * its capacity (0x0FFF = 4095). + */ + @Test void testUuidv7RandAField() { + final UuidFunction f = new UuidFunction(); + for (int i = 0; i < 100; i++) { + final UUID uuid = f.uuidv7(); + final long randA = uuid.getMostSignificantBits() & 0x0FFFL; + assertThat("rand_a <= 0x0FFF", randA, lessThanOrEqualTo(0x0FFFL)); + } + } +} diff --git a/site/_docs/reference.md b/site/_docs/reference.md index 56fbf8a86ca4..363f6e0a4987 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -3136,6 +3136,7 @@ In the following: | p r | STRING_TO_ARRAY(string, delimiter [, nullString ]) | Returns a one-dimensional string[] array by splitting the input string value into subvalues using the specified string value as the "delimiter". Optionally, allows a specified string value to be interpreted as NULL. | b m p r s h | MD5(string) | Calculates an MD5 128-bit checksum of *string* and returns it as a hex string | m | MONTHNAME(date) | Returns the name, in the connection's locale, of the month in *datetime*; for example, for a locale of en, it will return 'February' for both DATE '2020-02-10' and TIMESTAMP '2020-02-10 10:10:10', and for a locale of zh, it will return '二月' +| q | NEWID() | Generates a random UUID (version 4), as defined by RFC 4122 | o r s | NVL(value1, value2) | Returns *value1* if *value1* is not null, otherwise *value2* | o r s | NVL2(value1, value2, value3) | Returns *value2* if *value1* is not null, otherwise *value3* | b | OFFSET(index) | When indexing an array, wrapping *index* in `OFFSET` returns the value at the 0-based *index*; throws error if *index* is out of bounds @@ -3230,6 +3231,9 @@ In the following: | b s | UNIX_DATE(date) | Returns the number of days since 1970-01-01 | s | URL_DECODE(string) | Decodes a *string* in 'application/x-www-form-urlencoded' format using a specific encoding scheme, returns original *string* when decoded error | s | URL_ENCODE(string) | Translates a *string* into 'application/x-www-form-urlencoded' format using a specific encoding scheme +| o | UUID() | Generates a random UUID (version 4), as defined by RFC 4122 +| c | UUIDV4() | Generates a random UUID (version 4), as defined by RFC 4122 +| c | UUIDV7() | Generates a time-ordered UUID (version 7), as defined by RFC 9562; values generated in sequence sort in creation order | o | XMLTRANSFORM(xml, xslt) | Applies XSLT transform *xslt* to XML string *xml* and returns the result Note: diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 387915bca6b5..53085ca32a13 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -8227,6 +8227,44 @@ void checkRegexpExtract(SqlOperatorFixture f0, FunctionAlias functionAlias) { f.checkScalar("rand_integer(2, 11)", 1, "INTEGER NOT NULL"); } + @Test void testUuidv4Func() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlLibraryOperators.UUIDV4, VmName.EXPAND); + // UUIDV4 is non-deterministic; we just check the return type. + final Consumer consumer = + fixture -> { + fixture.checkFails("^uuidv4^", "Column 'UUIDV4' not found in any table", false); + fixture.checkType("uuidv4()", "UUID NOT NULL"); + }; + f.forEachLibrary(list(SqlLibrary.CALCITE), consumer); + } + + @Test void testUuidv7Func() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlLibraryOperators.UUIDV7, VmName.EXPAND); + // UUIDV7 is non-deterministic; we just check the return type. + final Consumer consumer = + fixture -> { + fixture.checkFails("^uuidv7^", "Column 'UUIDV7' not found in any table", false); + fixture.checkType("uuidv7()", "UUID NOT NULL"); + }; + f.forEachLibrary(list(SqlLibrary.CALCITE), consumer); + } + + /** Tests the {@code NEWID()} function (SQL Server dialect function, used as + * the target when translating {@code UUIDV4()}). */ + @Test void testNewIdFunc() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlLibraryOperators.NEWID, VmName.EXPAND); + final Consumer consumer = + fixture -> fixture.checkType("newid()", "UUID NOT NULL"); + f.forEachLibrary(list(SqlLibrary.MSSQL), consumer); + } + + // Note: Oracle 23ai's UUID() function is a reserved keyword in Calcite's parser + // and cannot be invoked directly in Calcite SQL. It is only used as a translation + // target when UUIDV4() is unparsed for Oracle dialect. See RelToSqlConverterTest. + /** Test case for * [CALCITE-6283] Function array_append with a NULL array argument crashes with * NullPointerException. */