sgammon/GUST

View on GitHub
java/gust/backend/driver/spanner/SpannerUtil.java

Summary

Maintainability
A
30 mins
Test Coverage
/*
 * Copyright © 2020, The Gust Framework Authors. All rights reserved.
 *
 * The Gust/Elide framework and tools, and all associated source or object computer code, except where otherwise noted,
 * are licensed under the Zero Prosperity license, which is enclosed in this repository, in the file LICENSE.txt. Use of
 * this code in object or source form requires and implies consent and agreement to that license in principle and
 * practice. Source or object code not listing this header, or unless specified otherwise, remain the property of
 * Elide LLC and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to
 * Elide LLC and its suppliers and may be covered by U.S. and Foreign Patents, or patents in process, and are protected
 * by trade secret and copyright law. Dissemination of this information, or reproduction of this material, in any form,
 * is strictly forbidden except in adherence with assigned license requirements.
 */
package gust.backend.driver.spanner;

import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.Value;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import com.google.protobuf.Timestamp;
import com.google.type.Date;
import gust.backend.runtime.Logging;
import gust.util.Pair;
import org.slf4j.Logger;
import tools.elide.core.*;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static gust.backend.model.ModelMetadata.*;


/**
 * Provides utilities related to operations with Spanner, including tools for resolving column names and types from
 * models and annotations, and producing default sets of columns for DDL statements.
 */
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public final class SpannerUtil {
    private static final Logger logging = Logging.logger(SpannerUtil.class);

    private SpannerUtil() { /* disallow construction */ }

    /** Default predicate to use when filtering for eligible Spanner fields. */
    private static final @Nonnull Predicate<FieldPointer> defaultFieldPredicate;

    static {
        defaultFieldPredicate = (fieldPointer) -> {
            var field = fieldPointer.getField();
            var fieldOpts = fieldAnnotation(field, Datamodel.field);
            var columnOpts = fieldAnnotation(field, Datamodel.column);
            var spannerOpts = fieldAnnotation(field, Datamodel.spanner);

            return !(
                // field cannot be present if skipped via `column.ignore`
                (columnOpts.isPresent() && columnOpts.get().getIgnore()) ||

                // field cannot be present if skipped via `spanner.ignore`
                (spannerOpts.isPresent() && spannerOpts.get().getIgnore()) ||

                // properties marked as `INTERNAL` should always be withheld
                (fieldOpts.isPresent() && fieldOpts.get().getVisibility() == FieldVisibility.INTERNAL)
            );
        };
    }

    /**
     * For a given key field pointer, resolve the column name which should be used for the primary key in Spanner,
     * according to the annotation structure present on the key.
     *
     * @see #resolveKeyType(FieldPointer) To resolve the primary key column type.
     * @param idField Resolved field pointer to a given model's ID field.
     * @param driverSettings Settings for the Spanner driver.
     * @return Name of the column we should use for the primary key.
     */
    public static @Nonnull String resolveKeyColumn(@Nonnull FieldPointer idField,
                                                   @Nonnull SpannerDriverSettings driverSettings) {
        var fieldAnnos = fieldAnnotation(idField.getField(), Datamodel.field);
        if (fieldAnnos.orElseThrow().getType() != FieldType.ID) {
            throw new IllegalStateException(
                "Cannot use non-ID field as key column: '" + idField.getField().getFullName() + "'."
            );
        }

        // resolve key field and column name corresponding to that key field
        return resolveColumnName(
            idField,
            spannerOpts(idField),
            columnOpts(idField),
            driverSettings
        );
    }

    /**
     * Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and
     * objects used with Spanner must have such annotations or use generated defaults. This method variant operates from
     * a full message instance.
     *
     * @see #resolveTableName(Descriptors.Descriptor) For the wrapped version of this message.
     * @param message Message type to resolve a table name for.
     * @return Resolved table name, from annotations, or calculated as a default.
     */
    public static @Nonnull String resolveTableName(@Nonnull Message message) {
        return resolveTableName(
            message.getDescriptorForType()
        );
    }

    /**
     * Given a schema-driven model or key object, determine the table name that should be used in Spanner. All keys and
     * objects used with Spanner must have such annotations or use generated defaults.
     *
     * @param message Message type to resolve a table name for.
     * @return Resolved table name, from annotations, or calculated as a default.
     */
    public static @Nonnull String resolveTableName(@Nonnull Descriptors.Descriptor message) {
        return modelAnnotation(
            message,
            Datamodel.table,
            true
        ).orElseThrow(() -> new IllegalArgumentException(
            "Must annotate key or object model '" + message.getFullName() + "' with table name to use with Spanner."
        )).getName();
    }

    /**
     * For a given key field pointer, resolve the column type which should be used for the primary key in Spanner,
     * according to the annotation structure present on the key.
     *
     * @see #resolveKeyColumn(FieldPointer, SpannerDriverSettings) To resolve the primary key column name.
     * @param idField Resolved field pointer to a given model key's ID field.
     * @return Spanner column type to use for this model's ID.
     */
    public static @Nonnull Type resolveKeyType(@Nonnull FieldPointer idField) {
        // resolve the expected key column type. validate it on the way.
        if (idField.getField().isRepeated()) {
            throw new IllegalStateException(
                String.format(
                    "Unsupported key field type: '%s'. Keys cannot be repeated.",
                    idField.getField().getType().name()));
        } else if (idField.getField().getType() == Descriptors.FieldDescriptor.Type.STRING) {
            return Type.string();
        } else if (
            idField.getField().getType() == Descriptors.FieldDescriptor.Type.UINT64 ||
            idField.getField().getType() == Descriptors.FieldDescriptor.Type.FIXED64) {
            return Type.int64();
        } else {
            throw new IllegalStateException(
                String.format("Unsupported key field type: '%s'.", idField.getField().getType().name()));
        }
    }

    /**
     * Given a model field pointer which translates to a `STRING` or `BYTES` column in Spanner, determine the size that
     * should be used when declaring the string column.
     *
     * <p>If an explicit column size is specified via model annotations, that prevails. If not, the default size value
     * is used.</p>
     *
     * @param spannerOpts Spanner-specific options on the field.
     * @param columnOpts Column-generic options on the field.
     * @param settings Settings for the Spanner driver.
     * @return Expected name of the field when expressed as a column in Spanner.
     */
    public static int resolveColumnSize(@Nonnull Descriptors.FieldDescriptor field,
                                        @Nonnull Optional<SpannerFieldOptions> spannerOpts,
                                        @Nonnull Optional<TableFieldOptions> columnOpts,
                                        @Nonnull SpannerDriverSettings settings) {
        if (spannerOpts.isPresent()) {
            var spannerOptsUnwrapped = spannerOpts.get();
            if (spannerOptsUnwrapped.getSize() > 0) {
                return spannerOptsUnwrapped.getSize();
            }
        }
        if (columnOpts.isPresent()) {
            var columnOptsUnwrapped = columnOpts.get();
            if (columnOptsUnwrapped.getSize() > 0) {
                return columnOptsUnwrapped.getSize();
            }
        }
        if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM) {
            return 32;  // special case: string ENUM fields should have a sensible default
        }
        return settings.defaultColumnSize();
    }

    /**
     * Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model
     * field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name.
     *
     * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full
     *      un-sugared version of this method.
     * @param field Pre-resolved model field descriptor.
     * @return Expected name of the field when expressed as a column in Spanner.
     */
    public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field) {
        return resolveColumnName(
            field,
            SpannerDriverSettings.DEFAULTS
        );
    }

    /**
     * Given a resolved field pointer resolve the expected/configured column name in Spanner for a given typed model
     * field. If no specialized Spanner or table column annotations are present, fallback to a calculated default name.
     * Apply any active driver settings as well.
     *
     * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full
     *      un-sugared version of this method.
     * @param field Pre-resolved model field descriptor.
     * @param settings Settings for the Spanner driver.
     * @return Expected name of the field when expressed as a column in Spanner.
     */
    public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field,
                                                    @Nonnull SpannerDriverSettings settings) {
        return resolveColumnName(
            field,
            spannerOpts(field),
            columnOpts(field),
            settings
        );
    }

    /**
     * Given a resolved field pointer and set of annotations, resolve the expected/configured column name in Spanner for
     * a given typed model field. If no specialized Spanner or table column annotations are present, fallback to a
     * calculated default name.
     *
     * @see #resolveColumnName(Descriptors.FieldDescriptor, Optional, Optional, SpannerDriverSettings) For the full
     *      un-sugared version of this method.
     * @param fieldPointer Pre-resolved model field pointer.
     * @param spannerOpts Spanner-specific options on the field.
     * @param columnOpts Column-generic options on the field.
     * @param settings Settings for the Spanner driver.
     * @return Expected name of the field when expressed as a column in Spanner.
     */
    public static @Nonnull String resolveColumnName(@Nonnull FieldPointer fieldPointer,
                                                    @Nonnull Optional<SpannerFieldOptions> spannerOpts,
                                                    @Nonnull Optional<TableFieldOptions> columnOpts,
                                                    @Nonnull SpannerDriverSettings settings) {
        return resolveColumnName(
            fieldPointer.getField(),
            spannerOpts,
            columnOpts,
            settings
        );
    }

    /**
     * Given a resolved Protocol Buffer field descriptor and set of annotations, resolve the expected/configured column
     * name in Spanner for a given typed model field. If no specialized Spanner or table column annotations are present,
     * fallback to a calculated default name.
     *
     * <p>{@link SpannerFieldOptions} always outweigh {@link TableFieldOptions}. If two similar or congruent properties
     * are set between options, generic options are applied first, and then specialized options override.</p>
     *
     * <p>If {@link SpannerDriverSettings#preserveFieldNames()} is activated when this method is called, default names
     * will use the literal field name from the Protocol Buffer definition. Otherwise, JSON-style names are calculated
     * and used as default names.</p>
     *
     * <p>Similarly, if {@link SpannerDriverSettings#defaultCapitalizedNames()} is activated when this method is called,
     * default names will use JSON-style naming but with initial capitals. For example, `name` turns into `Name` and
     * `contact_info` turns into `ContactInfo`. In all cases, explicit property names from specialized or generic
     * annotations prevail, then {@link SpannerDriverSettings#preserveFieldNames()} prevails, then the default form of
     * naming with Spanner capitalized names active.</p>
     *
     * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For a version of this method
     *      which operates on {@link FieldPointer} objects.
     * @param field Protocol Buffer field descriptor for which we should resolve a Spanner column name.
     * @param spannerOpts Spanner options applied to this field as annotations.
     * @param columnOpts Generic table column settings applied to this field as annotations.
     * @param settings Settings for the Spanner driver.
     * @return Resolved column name, from explicit annotations, or by way of default calculation, as described above.
     */
    public static @Nonnull String resolveColumnName(@Nonnull Descriptors.FieldDescriptor field,
                                                    @Nonnull Optional<SpannerFieldOptions> spannerOpts,
                                                    @Nonnull Optional<TableFieldOptions> columnOpts,
                                                    @Nonnull SpannerDriverSettings settings) {
        // resolve the expected column name in Spanner.
        String columnName;
        if (spannerOpts.isPresent() && !spannerOpts.get().getColumn().isBlank()) {
            columnName = spannerOpts.get().getColumn();
        } else if (columnOpts.isPresent() && !columnOpts.get().getName().isBlank()) {
            columnName = columnOpts.get().getName();
        } else {
            // generate a default name
            if (settings.preserveFieldNames()) {
                columnName = field.getName();
            } else {
                // use JSON field names
                if (settings.defaultCapitalizedNames()) {
                    columnName = String.format(
                        "%s%s",
                        field.getJsonName().substring(0, 1).toUpperCase(),
                        field.getJsonName().substring(1)
                    );
                } else {
                    // use unmodified JSON names
                    columnName = field.getJsonName();
                }
            }
        }

        if (logging.isTraceEnabled())
            logging.trace("Resolved column name for field '{}': '{}'",
                field.getName(),
                columnName);
        return columnName;
    }

    /**
     * Given a Spanner row result expressed as a {@link Struct} and a {@link FieldPointer} which is expected to be
     * present, with a pre-resolved column name, return the numeric column index.
     *
     * @param source Row result from Spanner which we should resolve the column index from.
     * @param fieldPointer Pointer to the field for which we are resolving an index. Pre-resolved.
     * @param name Translated name of the column for which we are resolving an index. Pre-resolved.
     * @return Integer index for the column in the provided row result.
     */
    public static int resolveColumnIndex(@Nonnull Struct source,
                                         @Nonnull FieldPointer fieldPointer,
                                         @Nonnull String name) {
        var columnIndex = source.getColumnIndex(name);
        if (logging.isTraceEnabled())
            logging.trace("Resolved column index for field '{}': '{}'",
                fieldPointer.getName(),
                columnIndex);
        return columnIndex;
    }

    /**
     * Given a Spanner row result expressed as a {@link Struct} and a {@link FieldPointer} which is expected to be
     * present, resolve any present {@link Value}.
     *
     * <p>This method additionally resolves the expected column name for the provided field.</p>
     *
     * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of model
     *      field column name calculations and annotation behavior.
     * @param source Row result from Spanner from which we should resolve any present value.
     * @param fieldPointer Pointer to the model field for which we are resolving a value.
     * @param spannerOpts Spanner-specific options and annotations present on the field.
     * @param columnOpts Column-generic options and annotations present on the field.
     * @param driverSettings Settings for the Spanner driver.
     * @return Resolved Spanner value, as applicable.
     */
    public static @Nonnull Value resolveColumnValue(@Nonnull Struct source,
                                                    @Nonnull FieldPointer fieldPointer,
                                                    @Nonnull Optional<SpannerFieldOptions> spannerOpts,
                                                    @Nonnull Optional<TableFieldOptions> columnOpts,
                                                    @Nonnull SpannerDriverSettings driverSettings) {
        var columnValue = source.getValue(resolveColumnIndex(
            source,
            fieldPointer,
            resolveColumnName(
                fieldPointer,
                spannerOpts,
                columnOpts,
                driverSettings
            )
        ));
        if (logging.isTraceEnabled())
            logging.trace("Resolved column value for field '{}': '{}'",
                fieldPointer.getName(),
                columnValue.toString());
        return columnValue;
    }

    /**
     * Given a resolved and eligible {@link FieldPointer} for a model field which should interact with Spanner, resolve
     * an expected Spanner {@link Type}, including any nested structure or complex objects, as mediated and regulated by
     * annotations on the model field.
     *
     * <p>If {@link SpannerFieldOptions#getType()} returns a non-default value, it prevails first, with
     * {@link TableFieldOptions#getSptype()} after that. If no explicit type is resolvable from the field definition,
     * a default type is generated (see method references for more information).</p>
     *
     * @see #resolveDefaultType(FieldPointer, SpannerDriverSettings) Fallback behavior if no explicit type is specified.
     * @param fieldPointer Pointer to the model field for which we should resolve a Spanner column type.
     * @param spannerOpts Spanner-specific options or annotations present on the field definition, as applicable.
     * @param columnOpts Column-generic options or annotations present on the field definition, as applicable.
     * @param settings Active settings for the Spanner driver.
     * @return Expected Spanner column type corresponding to the provided model field, considering all annotations.
     */
    public static @Nonnull Type resolveColumnType(@Nonnull FieldPointer fieldPointer,
                                                  @Nonnull Optional<SpannerFieldOptions> spannerOpts,
                                                  @Nonnull Optional<TableFieldOptions> columnOpts,
                                                  @Nonnull SpannerDriverSettings settings) {
        //noinspection deprecation
        return spannerOpts.isPresent() && spannerOpts.get().getType() != SpannerOptions.SpannerType.UNSPECIFIED_TYPE ?
                 resolveType(settings, fieldPointer, spannerOpts.get().getType()) :
               columnOpts.isPresent() && columnOpts.get().getSptype() != SpannerOptions.SpannerType.UNSPECIFIED_TYPE ?
                 resolveType(settings, fieldPointer, columnOpts.get().getSptype()) :
               resolveDefaultType(fieldPointer, settings);
    }

    /**
     * Calculate a default projection of Spanner columns, as configured on the provided default model instance. Results
     * are returned as a stream of fields paired to their column counterparts.
     *
     * @param descriptor Default model schema to generate a default set of Spanner columns from.
     * @param driverSettings Settings for the Spanner driver.
     * @return Default list of Spanner columns.
     */
    public static @Nonnull Stream<Pair<String, String>> calculateDefaultFieldStream(
            @Nonnull Descriptors.Descriptor descriptor,
            @Nonnull SpannerDriverSettings driverSettings) {
        // pluck the ID field first, and then concatenante it to a stream of all other fields.
        return Stream.concat(Stream.of(idField(descriptor).orElseThrow()), forEachField(
            descriptor,
            Optional.of(onlySpannerEligibleFields(driverSettings))
        )).map((fieldPointer) -> {
            var fieldOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.field);
            if (fieldOpts.orElse(FieldPersistenceOptions.getDefaultInstance()).getType() == FieldType.ID) {
                // this is an ID field, so skip it outright because the key will inject it.
                return null;
            } else if (fieldOpts.orElse(FieldPersistenceOptions.getDefaultInstance()).getType() == FieldType.KEY) {
                // this is a key field, so skip it and instead inject the ID field.
                return Pair.of(
                    fieldPointer.getName(),
                    resolveKeyColumn(idField(descriptor).orElseThrow(), driverSettings)
                );
            } else {
                return Pair.of(
                    fieldPointer.getName(),
                    resolveColumnName(fieldPointer,
                        fieldAnnotation(fieldPointer.getField(), Datamodel.spanner),
                        fieldAnnotation(fieldPointer.getField(), Datamodel.column),
                        driverSettings
                    )
                );
            }
        }).filter(Objects::nonNull);
    }

    /**
     * Calculate a default projection of Spanner columns, as configured on the provided default model instance.
     *
     * @param descriptor Default model schema to generate a default set of Spanner columns from.
     * @param driverSettings Settings for the Spanner driver.
     * @return Default list of Spanner columns.
     */
    public static @Nonnull List<String> calculateDefaultFields(@Nonnull Descriptors.Descriptor descriptor,
                                                               @Nonnull SpannerDriverSettings driverSettings) {
        return calculateDefaultFieldStream(
            descriptor,
            driverSettings
        ).map(Pair::getValue).collect(Collectors.toUnmodifiableList());
    }

    /**
     * Calculate a default projection of Spanner columns, as configured on the provided default model instance. The
     * default set of columns includes any columns considered "eligible" for storage in Spanner, each pre-resolved with
     * a column name and type.
     *
     * <p>This method is designed to operate deterministically, by visiting each eligible model field in a predictable
     * order and expressing that same order in the output collection.</p>
     *
     * @see #onlySpannerEligibleFields(SpannerDriverSettings) For an explanation of predicate behavior with regard to
     *      eligibility for interaction with Spanner.
     * @see #resolveColumnName(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of Spanner
     *      column name resolution and default calculation behavior.
     * @see #resolveColumnType(FieldPointer, Optional, Optional, SpannerDriverSettings) For an explanation of Spanner
     *      column type resolution and default decision behavior.
     * @param descriptor Default model schema to generate a default set of Spanner columns from.
     * @param driverSettings Settings for the Spanner driver.
     * @return Default list of Spanner columns.
     */
    public static @Nonnull Collection<Type.StructField> generateStruct(@Nonnull Descriptors.Descriptor descriptor,
                                                                       @Nonnull SpannerDriverSettings driverSettings) {
        return forEachField(
            descriptor,
            Optional.of(onlySpannerEligibleFields(driverSettings))
        ).filter((fieldPointer) ->
            // don't ever include `KEY` fields in structs
            fieldAnnotation(fieldPointer.getField(), Datamodel.field).orElse(
                FieldPersistenceOptions.getDefaultInstance()
            ).getType() != FieldType.KEY
        ).map((fieldPointer) -> {
            var spannerOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.spanner);
            var columnOpts = fieldAnnotation(fieldPointer.getField(), Datamodel.column);
            var name = resolveColumnName(
                fieldPointer,
                spannerOpts,
                columnOpts,
                driverSettings
            );
            var type = resolveColumnType(
                fieldPointer,
                spannerOpts,
                columnOpts,
                driverSettings
            );
            return Type.StructField.of(name, type);
        }).collect(Collectors.toUnmodifiableList());
    }

    /**
     * Return a {@link Predicate} implementation which determines field eligibility with regard to interaction with
     * Cloud Spanner, optionally considering the provided set of circumstantial higher-order eligible fields (for
     * instance, in the case of a known property projection).
     *
     * <p>Field eligibility is determined by the following criteria:
     * <ul>
     *     <li>Model fields <b>MUST</b> be present on the {@link Message} schema to interact with Spanner.</li>
     *     <li>Model fields <b>MUST NOT</b> be annotated with {@link TableFieldOptions#getIgnore()}.</li>
     *     <li>Model fields <b>MUST NOT</b> be annotated with {@link SpannerFieldOptions#getIgnore()}.</li>
     *     <li>Model fields <b>MUST NOT</b> be annotated with {@link FieldVisibility#INTERNAL}.</li>
     *     <li>If provided and non-empty, model fields <b>MUST</b> be present in the set of <pre>eligibleFields</pre>
     *     provided to this method.</li>
     * </ul></p>
     *
     * @see #onlySpannerEligibleFields(SpannerDriverSettings) For circumstances with no known eligible fields.
     * @param eligibleFields Set of higher-order eligible fields, if applicable. Only considered if non-empty.
     * @param settings Settings for the Spanner driver.
     * @return Predicate which filters {@link FieldPointer} objects according to the provided settings.
     */
    public static @Nonnull Predicate<FieldPointer> onlySpannerEligibleFields(@Nonnull SortedSet<String> eligibleFields,
                                                                             @Nonnull SpannerDriverSettings settings) {
        if (eligibleFields.isEmpty()) {
            return defaultFieldPredicate;
        }
        return defaultFieldPredicate.and((fieldPointer) -> {
            var field = fieldPointer.getField();
            var columnOpts = fieldAnnotation(field, Datamodel.column);
            var spannerOpts = fieldAnnotation(field, Datamodel.spanner);

            return !(
                // field should be omitted if not present in list of row fields, if we have any
                !eligibleFields.isEmpty() && !eligibleFields.contains(resolveColumnName(
                    fieldPointer,
                    spannerOpts,
                    columnOpts,
                    settings
                ))
            );
        });
    }

    /**
     * Return a {@link Predicate} implementation which operates on {@link FieldPointer} objects to determine eligibility
     * for interaction with Spanner.
     *
     * <p>This method variant provides no opportunity to filter by higher-order circumstantial fields. Should invoking
     * code have an opportunity to do so, it may dispatch the more customizable form of this method (see below).
     * Additionally, that method may be referenced for a detailed explanation of field eligibility behavior.</p>
     *
     * @see #onlySpannerEligibleFields(SortedSet, SpannerDriverSettings) For the fully-controllable form of this method,
     *      which can combine an additional {@link Predicate} to filter by a set of fields known at invocation time.
     * @param settings Settings for the Spanner driver.
     * @return Predicate to determine {@link FieldPointer} eligibility for interaction with Spanner.
     */
    public static @Nonnull Predicate<FieldPointer> onlySpannerEligibleFields(@Nonnull SpannerDriverSettings settings) {
        return onlySpannerEligibleFields(Collections.emptySortedSet(), settings);
    }

    /**
     * If a concrete type is marked as repeated, wrap it in an array type. Otherwise, just return the type.
     *
     * @param field Field pointer for the model field.
     * @param inner Inner type for the maybe-repeated field.
     * @return Either the array-wrapped type or the concrete individual type.
     */
    public static @Nonnull Type maybeWrapType(@Nonnull FieldPointer field,
                                              @Nonnull Type inner) {
        if (field.getField().isRepeated()) {
            return Type.array(inner);
        }
        return inner;
    }

    /**
     * Resolve a normalized Spanner type for the provided field `pointer`, with the explicit provided `spannerType`.
     * With an explicit type, our job is just to make sure the model configuration is cohesive.
     *
     * @param pointer Resolved field pointer.
     * @param spannerType Explicit Spanner type.
     * @return Resolved normalized Spanner type.
     */
    public static @Nonnull Type resolveType(@Nonnull SpannerDriverSettings settings,
                                            @Nonnull FieldPointer pointer,
                                            @Nonnull tools.elide.core.SpannerOptions.SpannerType spannerType) {
        switch (spannerType) {
            case STRING: return maybeWrapType(pointer, Type.string());
            case JSON:
                if (settings.experimentalNativeJsonType()) {
                    return maybeWrapType(pointer, Type.json());
                }
                return maybeWrapType(pointer, Type.string());
            case NUMERIC: return maybeWrapType(pointer, Type.numeric());
            case FLOAT64: return maybeWrapType(pointer, Type.float64());
            case INT64: return maybeWrapType(pointer, Type.int64());
            case BYTES: return maybeWrapType(pointer, Type.bytes());
            case BOOL: return maybeWrapType(pointer, Type.bool());
            case DATE: return maybeWrapType(pointer, Type.date());
            case TIMESTAMP: return maybeWrapType(pointer, Type.timestamp());
            default: throw new IllegalArgumentException("Unrecognized Spanner type.");
        }
    }

    /**
     * Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no
     * explicit type annotations are present for a Spanner column's type.
     *
     * @param pointer Field pointer to resolve a Spanner type for.
     * @param settings Settings for the Spanner driver.
     * @return Resolved normalized Spanner type.
     */
    public static @Nonnull Type resolveDefaultType(@Nonnull FieldPointer pointer,
                                                   @Nonnull SpannerDriverSettings settings) {
        return resolveDefaultType(
            pointer,
            pointer.getField().getType(),
            settings
        );
    }

    /**
     * Resolve a default Spanner type for the provided field `pointer`. This selects a sensible default when no
     * explicit type annotations are present for a Spanner column's type.
     *
     * @param pointer Field pointer to resolve a Spanner type for.
     * @param protoType Protocol Buffer type to resolve.
     * @param settings Settings for the Spanner driver.
     * @return Resolved normalized Spanner type.
     */
    public static @Nonnull Type resolveDefaultType(@Nonnull FieldPointer pointer,
                                                   @Nonnull Descriptors.FieldDescriptor.Type protoType,
                                                   @Nonnull SpannerDriverSettings settings) {
        switch (protoType) {
            case DOUBLE:
            case FLOAT:
                return maybeWrapType(pointer, Type.float64());

            case INT32:
            case INT64:
            case UINT32:
            case UINT64:
            case FIXED32:
            case FIXED64:
            case SFIXED32:
            case SFIXED64:
            case SINT32:
            case SINT64:
                return maybeWrapType(pointer, Type.int64());

            case BOOL: return maybeWrapType(pointer, Type.bool());
            case STRING:
                if (fieldAnnotation(pointer.getField(), Datamodel.spanner).orElse(
                    SpannerFieldOptions.getDefaultInstance()
                ).getType().equals(SpannerOptions.SpannerType.JSON)) {
                    // special case: for JSON fields, express them as the JSON native type (when enabled).
                    return maybeWrapType(pointer, settings.experimentalNativeJsonType() ? Type.json() : Type.string());
                }
                return maybeWrapType(pointer, Type.string());

            case BYTES: return maybeWrapType(pointer, Type.bytes());
            case ENUM:
                return settings.enumsAsNumbers() ?
                    maybeWrapType(pointer, Type.int64()) :
                    maybeWrapType(pointer, Type.string());

            case MESSAGE:
                var messageType = pointer.getField().getMessageType();
                if (Timestamp.getDescriptor().getFullName().equals(messageType.getFullName())) {
                    // `TIMESTAMP` records should always be expressed as type `TIMESTAMP`.
                    return maybeWrapType(pointer, Type.timestamp());

                } else if (Date.getDescriptor().getFullName().equals(messageType.getFullName())) {
                    // `DATE` records should always be expressed as type `DATE`.
                    return maybeWrapType(pointer, Type.date());

                } else {
                    // special case: if this is a key field, we should treat it like it's actually the key's ID field.
                    if (fieldAnnotation(pointer.getField(), Datamodel.field).orElse(
                        FieldPersistenceOptions.getDefaultInstance()
                    ).getType().equals(FieldType.KEY)) {
                        // it's a key field, so instead, resolve the model's ID field and add that.
                        return resolveKeyType(idField(pointer.getBase()).orElseThrow());
                    }

                    // any other type should fallback to being wrapped as a `STRUCT`.
                    return maybeWrapType(pointer, Type.struct(generateStruct(
                        pointer.getField().getMessageType(),
                        settings
                    )));
                }

            case GROUP:
            default:
                throw new IllegalArgumentException(String.format(
                    "Unrecognized Protocol Buffer type: %s.",
                    pointer.getField().getType().name()
                ));
        }
    }

    /**
     * Given a pre-resolved {@link FieldPointer}, resolve any present {@link TableFieldOptions}.
     *
     * @see #columnOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more
     *      detailed explanation of {@link TableFieldOptions} with regard to Spanner.
     * @param fieldPointer Field pointer for which we should resolve any present column-generic options.
     * @return Set of specified table field options, or {@link Optional#empty()}.
     */
    public static @Nonnull Optional<TableFieldOptions> columnOpts(@Nonnull FieldPointer fieldPointer) {
        return columnOpts(fieldPointer.getField());
    }

    /**
     * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction
     * with Spanner, resolve any present column-generic options and annotations via {@link TableFieldOptions}.
     *
     * <p>Column-generic options apply to engines which operate in a columnar manner. This includes Spanner, but also
     * includes engines like BigQuery and SQL-based systems. To allow adaptation to those systems without curtailing
     * control of table and field naming, the Spanner driver respects {@link TableFieldOptions} but defers to any
     * present {@link SpannerFieldOptions}.</p>
     *
     * @see #columnOpts(FieldPointer) For a version of this method which operates on {@link FieldPointer}.
     * @see #spannerOpts(Descriptors.FieldDescriptor) For the equivalent version of this method that returns Spanner-
     *      specific field options.
     * @param field Field descriptor for which we should resolve any present {@link TableFieldOptions}.
     * @return Any present column-generic field options or annotations, or {@link Optional#empty()}.
     */
    public static @Nonnull Optional<TableFieldOptions> columnOpts(@Nonnull Descriptors.FieldDescriptor field) {
        // resolve any generic column options...
        var columnOpts = fieldAnnotation(
            field,
            Datamodel.column
        );

        if (columnOpts.isPresent() && logging.isDebugEnabled())
            logging.debug("Found column options for field '{}': \n{}",
                field.getName(),
                columnOpts.toString());
        else if (columnOpts.isEmpty() && logging.isDebugEnabled()) {
            logging.debug("No column opts for field '{}'. Using defaults.",
                field.getName());
        }
        return columnOpts;
    }

    /**
     * Given a pre-resolved {@link FieldPointer}, resolve any present generic {@link FieldPersistenceOptions}.
     *
     * @see #fieldOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more
     *      detailed explanation of {@link FieldPersistenceOptions}.
     * @param fieldPointer Field pointer for which we should resolve any present field-generic options.
     * @return Set of specified general field options, or {@link Optional#empty()}.
     */
    public static @Nonnull Optional<FieldPersistenceOptions> fieldOpts(@Nonnull FieldPointer fieldPointer) {
        return fieldOpts(fieldPointer.getField());
    }

    /**
     * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction
     * with Spanner, resolve any present field-generic options and annotations via {@link FieldPersistenceOptions}.
     *
     * <p>To adapt to other persistence engines, model fields may be annotated with {@link FieldPersistenceOptions}. In
     * all cases, present {@link FieldPersistenceOptions} yield with regard to matchin Spanner-specific fields.</p>
     *
     * @see #spannerOpts(Descriptors.FieldDescriptor) Equivalent method for Spanner options
     * @see #columnOpts(Descriptors.FieldDescriptor) equivalent method for column generic options
     * @param field Field descriptor for which we should resolve any present {@link FieldPersistenceOptions}.
     * @return Any present field-generic field options or annotations, or {@link Optional#empty()}.
     */
    public static @Nonnull Optional<FieldPersistenceOptions> fieldOpts(@Nonnull Descriptors.FieldDescriptor field) {
        // resolve field options
        var fieldOpts = fieldAnnotation(
            field,
            Datamodel.field
        );

        if (fieldOpts.isPresent() && logging.isDebugEnabled())
            logging.debug("Found generic options for field '{}': \n{}",
                field.getName(),
                fieldOpts.toString());
        else if (fieldOpts.isEmpty() && logging.isDebugEnabled()) {
            logging.debug("No generic opts for field '{}'. Using defaults.",
                field.getName());
        }
        return fieldOpts;
    }

    /**
     * Given a pre-resolved {@link FieldPointer}, resolve any present {@link SpannerFieldOptions}.
     *
     * @see #spannerOpts(Descriptors.FieldDescriptor) For the low-level version of this method, which includes a more
     *      detailed explanation of {@link SpannerFieldOptions}.
     * @param fieldPointer Field pointer for which we should resolve any present column-generic options.
     * @return Set of specified table field options, or {@link Optional#empty()}.
     */
    public static @Nonnull Optional<SpannerFieldOptions> spannerOpts(@Nonnull FieldPointer fieldPointer) {
        return spannerOpts(fieldPointer.getField());
    }

    /**
     * Given a resolved Protocol Buffer {@link Descriptors.FieldDescriptor} which is considered eligible for interaction
     * with Spanner, resolve any present Spanner-specific options and annotations via {@link SpannerFieldOptions}.
     *
     * <p>To adapt to other columnar-style engines, model fields may be annotated with {@link TableFieldOptions}. In all
     * cases, present {@link SpannerFieldOptions} override with regard to Spanner Driver behavior.</p>
     *
     * @see #spannerOpts(FieldPointer) For a version of this method which operates on {@link FieldPointer}.
     * @see #columnOpts(Descriptors.FieldDescriptor) For the equivalent version of this method that returns column-
     *      generic field options.
     * @param field Field descriptor for which we should resolve any present {@link SpannerFieldOptions}.
     * @return Any present column-generic field options or annotations, or {@link Optional#empty()}.
     */
    public static @Nonnull Optional<SpannerFieldOptions> spannerOpts(@Nonnull Descriptors.FieldDescriptor field) {
        // resolve spanner options, which override any default table options.
        var spannerOpts = fieldAnnotation(
            field,
            Datamodel.spanner
        );

        if (spannerOpts.isPresent() && logging.isDebugEnabled())
            logging.debug("Found Spanner options for field '{}': \n{}",
                    field.getName(),
                    spannerOpts.toString());
        else if (spannerOpts.isEmpty() && logging.isDebugEnabled()) {
            logging.debug("No Spanner opts for field '{}'. Using defaults.",
                field.getName());
        }
        return spannerOpts;
    }
}