Skip to content
Merged
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 @@ -13,4 +13,5 @@ List of functions (by sub-category)
scalar_functions/bitmap_bit_position
scalar_functions/bitmap_bucket_number
scalar_functions/bitmap_bucket_offset
scalar_functions/cardinality
scalar_functions/get_versionstamp_incarnation
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
===========
CARDINALITY
===========

The CARDINALITY function returns the cardinality of an array.

Syntax
======

.. code-block:: sql

CARDINALITY( <expr> )

Parameters
==========

``expr``
An array expression.

Returns
=======

Given an array value, returns a value of type INTEGER representing the number of elements in the array, or 0 if it is
empty. If the argument is NULL, the result is the NULL value.

Example
=======

.. code-block:: sql

CREATE TABLE t1 (id INTEGER, arr INTEGER ARRAY);
INSERT INTO t1 VALUES (1, [1, 2]);
SELECT CARDINALITY(arr) FROM t1; -- yields 2
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public class FunctionNames {
public static final String BITMAP_BIT_POSITION = "bitmap_bit_position";
public static final String BITMAP_BUCKET_OFFSET = "bitmap_bucket_offset";

/* Assorted functions */
public static final String CARDINALITY = "cardinality";

private FunctionNames() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* CardinalityValue.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2023-2030 Apple Inc. and the FoundationDB project authors
*
* Licensed 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 com.apple.foundationdb.record.query.plan.cascades.values;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.annotation.SpotBugsSuppressWarnings;
import com.apple.foundationdb.record.EvaluationContext;
import com.apple.foundationdb.record.FunctionNames;
import com.apple.foundationdb.record.ObjectPlanHash;
import com.apple.foundationdb.record.PlanDeserializer;
import com.apple.foundationdb.record.PlanHashable;
import com.apple.foundationdb.record.PlanSerializationContext;
import com.apple.foundationdb.record.planprotos.PCardinalityValue;
import com.apple.foundationdb.record.planprotos.PValue;
import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStoreBase;
import com.apple.foundationdb.record.query.plan.cascades.AliasMap;
import com.apple.foundationdb.record.query.plan.cascades.BuiltInFunction;
import com.apple.foundationdb.record.query.plan.cascades.SemanticException;
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokens;
import com.apple.foundationdb.record.query.plan.explain.ExplainTokensWithPrecedence;
import com.google.auto.service.AutoService;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.Message;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

/**
* A value representing the {@code CARDINALITY()} function.
*/
@API(API.Status.EXPERIMENTAL)
public class CardinalityValue extends AbstractValue {
private static final ObjectPlanHash BASE_HASH = new ObjectPlanHash("Cardinality-Value");

@Nonnull
private final Value childValue;

public CardinalityValue(@Nonnull final Value childValue) {
SemanticException.check(childValue.getResultType().isArray(), SemanticException.ErrorCode.INCOMPATIBLE_TYPE, "The argument of CARDINALITY() must be an array expression.");

this.childValue = childValue;
}

@Nonnull
@Override
public List<? extends Value> computeChildren() {
return List.of(childValue);
}

@Nonnull
@Override
public Value withChildren(final Iterable<? extends Value> newChildren) {
final var newChildrenList = ImmutableList.copyOf(newChildren);
Verify.verify(newChildrenList.size() == 1);
return new CardinalityValue(newChildrenList.get(0));
}

@Nonnull
@Override
public Type getResultType() {
// Array indexes and sizes are 32-bit integers.
return Type.primitiveType(Type.TypeCode.INT);
}

@Override
public <M extends Message> Object eval(@Nullable final FDBRecordStoreBase<M> store, @Nonnull final EvaluationContext context) {
final Object childResult = childValue.eval(store, context);
if (childResult == null) {
return null;
}
return ((List<?>)childResult).size();
}

@Override
public int hashCodeWithoutChildren() {
return PlanHashable.objectsPlanHash(PlanHashable.CURRENT_FOR_CONTINUATION, BASE_HASH);
}

@Override
public int planHash(@Nonnull final PlanHashMode mode) {
return PlanHashable.objectsPlanHash(mode, BASE_HASH, childValue);
}

@Nonnull
@Override
public ExplainTokensWithPrecedence explain(@Nonnull final Iterable<Supplier<ExplainTokensWithPrecedence>> explainSuppliers) {
return ExplainTokensWithPrecedence.of(new ExplainTokens().addFunctionCall(FunctionNames.CARDINALITY,
Value.explainFunctionArguments(explainSuppliers)));
}

@Override
public int hashCode() {
return semanticHashCode();
}

@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
@SpotBugsSuppressWarnings("EQ_UNUSUAL")
@Override
public boolean equals(final Object other) {
return semanticEquals(other, AliasMap.emptyMap());
}
Comment thread
RobertBrunel marked this conversation as resolved.

@Nonnull
@Override
public PCardinalityValue toProto(@Nonnull final PlanSerializationContext serializationContext) {
return PCardinalityValue.newBuilder()
.setChildValue(childValue.toValueProto(serializationContext))
.build();
}

@Nonnull
@Override
public PValue toValueProto(@Nonnull PlanSerializationContext serializationContext) {
return PValue.newBuilder().setCardinalityValue(toProto(serializationContext)).build();
}

@Nonnull
public static CardinalityValue fromProto(@Nonnull final PlanSerializationContext serializationContext, @Nonnull final PCardinalityValue cardinalityValueProto) {
return new CardinalityValue(Value.fromValueProto(serializationContext, Objects.requireNonNull(cardinalityValueProto.getChildValue())));
}

/**
* The {@code CARDINALITY()} function.
*/
@AutoService(BuiltInFunction.class)
public static class CardinalityFn extends BuiltInFunction<Value> {
public CardinalityFn() {
super(FunctionNames.CARDINALITY,
List.of(Type.any()), (builtInFunction, arguments) -> encapsulateInternal(arguments));
}

private static Value encapsulateInternal(@Nonnull final List<? extends Typed> arguments) {
Verify.verify(arguments.size() == 1);
return new CardinalityValue((Value)arguments.get(0));
Comment thread
RobertBrunel marked this conversation as resolved.
}
}

/**
* Deserializer.
*/
@AutoService(PlanDeserializer.class)
public static class Deserializer implements PlanDeserializer<PCardinalityValue, CardinalityValue> {
@Nonnull
@Override
public Class<PCardinalityValue> getProtoMessageClass() {
return PCardinalityValue.class;
}

@Nonnull
@Override
public CardinalityValue fromProto(@Nonnull final PlanSerializationContext serializationContext,
@Nonnull final PCardinalityValue cardinalityValueProto) {
return CardinalityValue.fromProto(serializationContext, cardinalityValueProto);
}
}
}
5 changes: 5 additions & 0 deletions fdb-record-layer-core/src/main/proto/record_query_plan.proto
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ message PValue {
PEuclideanSquareDistanceRowNumberValue euclidean_square_distance_row_number_value = 60;
PDotProductDistanceRowNumberValue dot_product_distance_row_number_value = 61;
PIncarnationValue incarnation_value = 62;
PCardinalityValue cardinality_value = 63;
}
}

Expand Down Expand Up @@ -1374,6 +1375,10 @@ message PIncarnationValue {
// No fields needed - this function takes no arguments
}

message PCardinalityValue {
optional PValue child_value = 1;
}

message PWindowedValue {
repeated PValue partitioning_values = 1;
repeated PValue argument_values = 2;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* CardinalityValueTest.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2023-2030 Apple Inc. and the FoundationDB project authors
*
* Licensed 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 com.apple.foundationdb.record.query.plan.cascades.values;

import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.List;

class CardinalityValueTest {

private static final Type.Array INT_ARRAY_TYPE = new Type.Array(Type.primitiveType(Type.TypeCode.INT));
private static final Type.Array STRING_ARRAY_TYPE = new Type.Array(Type.primitiveType(Type.TypeCode.STRING));

private static final LiteralValue<List<Integer>> INT_ARRAY_1 =
new LiteralValue<>(INT_ARRAY_TYPE, List.of(1, 2, 3));
private static final LiteralValue<List<Integer>> INT_ARRAY_2 =
new LiteralValue<>(INT_ARRAY_TYPE, List.of(4, 5));
private static final LiteralValue<List<String>> STRING_ARRAY =
new LiteralValue<>(STRING_ARRAY_TYPE, List.of("a", "b"));

@Test
void testEqualsAndHashCode() {
final var c1 = new CardinalityValue(INT_ARRAY_1);
final var c2 = new CardinalityValue(INT_ARRAY_1);
final var cDifferentChild = new CardinalityValue(INT_ARRAY_2);
final var cDifferentElementType = new CardinalityValue(STRING_ARRAY);

// Reflexive
Assertions.assertEquals(c1, c1);

// Two instances with the same child are equal and have the same hash code
Assertions.assertEquals(c1, c2);
Assertions.assertEquals(c1.hashCode(), c2.hashCode());

// Instances with different children are not equal
Assertions.assertNotEquals(cDifferentChild, c1);
Assertions.assertNotEquals(cDifferentElementType, c1);

// Not equal to null or an unrelated type
Assertions.assertNotEquals(null, c1);
Assertions.assertNotEquals("not a Value", c1);
}
Comment thread
RobertBrunel marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ private static ImmutableMap<String, Function<Integer, Optional<BuiltInFunction<?
.put("__internal_array", argumentsCount -> BuiltInFunctionCatalog.resolve("array", argumentsCount))
.put("__pick_value", argumentsCount -> BuiltInFunctionCatalog.resolve("pick", argumentsCount))
.put("get_versionstamp_incarnation", argumentsCount -> BuiltInFunctionCatalog.resolve("get_versionstamp_incarnation", argumentsCount))
.put("cardinality", argumentsCount -> BuiltInFunctionCatalog.resolve("cardinality", argumentsCount))
.build();
}

Expand Down
5 changes: 5 additions & 0 deletions yaml-tests/src/test/java/YamlIntegrationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ void arrays(YamlTest.Runner runner) throws Exception {
runner.runYamsql("arrays.yamsql");
}

@TestTemplate
void arrays_cardinality(YamlTest.Runner runner) throws Exception {
runner.runYamsql("arrays_cardinality.yamsql");
}

@TestTemplate
void arraysOperators(YamlTest.Runner runner) throws Exception {
runner.runYamsql("arrays-operators.yamsql");
Expand Down
27 changes: 27 additions & 0 deletions yaml-tests/src/test/resources/arrays_cardinality.metrics.binpb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
ã
P
arrays-cardinality-tests4EXPLAIN SELECT CARDINALITY("int_arr") FROM "tab1_nn"Ž
’°ñŒ"( Öøµ(0ŽŸ8 @7SCAN([IS tab1_nn]) | MAP (cardinality(_.int_arr) AS _0)·
digraph G {
fontname=courier;
rankdir=BT;
splines=line;
1 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Value Computation</td></tr><tr><td align="left">MAP (cardinality(q2.int_arr) AS _0)</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ];
2 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Scan</td></tr><tr><td align="left">comparisons: [IS tab1_nn]</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS id, ARRAY(INT) AS int_arr)" ];
3 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Primary Storage</td></tr><tr><td align="left">record types: [tab1_nn, tab1]</td></tr></table>> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS id, ARRAY(INT) AS int_arr)" ];
3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
2 -> 1 [ label=<&nbsp;q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
M
arrays-cardinality-tests1EXPLAIN SELECT CARDINALITY("int_arr") FROM "tab1"
’Êɵ( È©“(0°¦!8 @4SCAN([IS tab1]) | MAP (cardinality(_.int_arr) AS _0)»
digraph G {
fontname=courier;
rankdir=BT;
splines=line;
1 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Value Computation</td></tr><tr><td align="left">MAP (cardinality(q2.int_arr) AS _0)</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS _0)" ];
2 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Scan</td></tr><tr><td align="left">comparisons: [IS tab1]</td></tr></table>> color="black" shape="plain" style="solid" fillcolor="black" fontname="courier" fontsize="8" tooltip="RELATION(INT AS id, ARRAY(INT) AS int_arr)" ];
3 [ label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="8"><tr><td align="left">Primary Storage</td></tr><tr><td align="left">record types: [dummy, tab1_nn, tab1]</td></tr></table>> color="black" shape="plain" style="filled" fillcolor="lightblue" fontname="courier" fontsize="8" tooltip="RELATION(INT AS id, ARRAY(INT) AS int_arr)" ];
3 -> 2 [ color="gray20" style="solid" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
2 -> 1 [ label=<&nbsp;q2> label="q2" color="gray20" style="bold" fontname="courier" fontsize="8" arrowhead="normal" arrowtail="none" dir="both" ];
}
Expand Down
23 changes: 23 additions & 0 deletions yaml-tests/src/test/resources/arrays_cardinality.metrics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
arrays-cardinality-tests:
- query: EXPLAIN SELECT CARDINALITY("int_arr") FROM "tab1_nn"
ref: arrays_cardinality.yamsql:57
explain: SCAN([IS tab1_nn]) | MAP (cardinality(_.int_arr) AS _0)
task_count: 146
task_total_time_ms: 71
transform_count: 40
transform_time_ms: 55
transform_yield_count: 13
insert_time_ms: 2
insert_new_count: 12
insert_reused_count: 2
- query: EXPLAIN SELECT CARDINALITY("int_arr") FROM "tab1"
ref: arrays_cardinality.yamsql:61
explain: SCAN([IS tab1]) | MAP (cardinality(_.int_arr) AS _0)
task_count: 146
task_total_time_ms: 9
transform_count: 40
transform_time_ms: 4
transform_yield_count: 13
insert_time_ms: 0
insert_new_count: 12
insert_reused_count: 2
Loading
Loading