/* * 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.cassandra.cql3; import java.nio.ByteBuffer; import java.util.*; import org.apache.cassandra.db.marshal.CompositeType; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.utils.FBUtilities; /** * Static helper methods and classes for user types. */ public abstract class UserTypes { private UserTypes() {} public static ColumnSpecification fieldSpecOf(ColumnSpecification column, int field) { return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier(column.name + "." + field, true), ((UserType)column.type).types.get(field)); } public static class Literal implements Term.Raw { public final Map<ColumnIdentifier, Term.Raw> entries; public Literal(Map<ColumnIdentifier, Term.Raw> entries) { this.entries = entries; } public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { validateAssignableTo(keyspace, receiver); UserType ut = (UserType)receiver.type; boolean allTerminal = true; List<Term> values = new ArrayList<>(entries.size()); for (int i = 0; i < ut.types.size(); i++) { ColumnIdentifier field = new ColumnIdentifier(ut.columnNames.get(i), UTF8Type.instance); Term value = entries.get(field).prepare(keyspace, fieldSpecOf(receiver, i)); if (value instanceof Term.NonTerminal) allTerminal = false; values.add(value); } DelayedValue value = new DelayedValue(((UserType)receiver.type), values); return allTerminal ? value.bind(Collections.<ByteBuffer>emptyList()) : value; } private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { if (!(receiver.type instanceof UserType)) throw new InvalidRequestException(String.format("Invalid user type literal for %s of type %s", receiver, receiver.type.asCQL3Type())); UserType ut = (UserType)receiver.type; for (int i = 0; i < ut.types.size(); i++) { ColumnIdentifier field = new ColumnIdentifier(ut.columnNames.get(i), UTF8Type.instance); Term.Raw value = entries.get(field); if (value == null) throw new InvalidRequestException(String.format("Invalid user type literal for %s: missing field %s", receiver, field)); ColumnSpecification fieldSpec = fieldSpecOf(receiver, i); if (!value.isAssignableTo(keyspace, fieldSpec)) throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s", receiver, field, fieldSpec.type.asCQL3Type())); } } public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) { try { validateAssignableTo(keyspace, receiver); return true; } catch (InvalidRequestException e) { return false; } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{"); Iterator<Map.Entry<ColumnIdentifier, Term.Raw>> iter = entries.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<ColumnIdentifier, Term.Raw> entry = iter.next(); sb.append(entry.getKey()).append(":").append(entry.getValue()); if (iter.hasNext()) sb.append(", "); } sb.append("}"); return sb.toString(); } } // Same purpose than Lists.DelayedValue, except we do handle bind marker in that case public static class DelayedValue extends Term.NonTerminal { private final UserType type; private final List<Term> values; public DelayedValue(UserType type, List<Term> values) { this.type = type; this.values = values; } public boolean containsBindMarker() { for (Term t : values) if (t.containsBindMarker()) return true; return false; } public void collectMarkerSpecification(VariableSpecifications boundNames) { for (int i = 0; i < type.types.size(); i++) values.get(i).collectMarkerSpecification(boundNames); } private ByteBuffer[] bindInternal(List<ByteBuffer> variables) throws InvalidRequestException { ByteBuffer[] buffers = new ByteBuffer[values.size()]; for (int i = 0; i < type.types.size(); i++) { ByteBuffer buffer = values.get(i).bindAndGet(variables); if (buffer == null) throw new InvalidRequestException("null is not supported inside user type literals"); if (buffer.remaining() > FBUtilities.MAX_UNSIGNED_SHORT) throw new InvalidRequestException(String.format("Value for field %s is too long. User type fields are limited to %d bytes but %d bytes provided", UTF8Type.instance.getString(type.columnNames.get(i)), FBUtilities.MAX_UNSIGNED_SHORT, buffer.remaining())); buffers[i] = buffer; } return buffers; } public Constants.Value bind(List<ByteBuffer> variables) throws InvalidRequestException { return new Constants.Value(bindAndGet(variables)); } @Override public ByteBuffer bindAndGet(List<ByteBuffer> variables) throws InvalidRequestException { return CompositeType.build(bindInternal(variables)); } } }