All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.datastax.oss.driver.internal.core.metadata.schema.parsing.DataTypeClassNameParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright DataStax, Inc.
 *
 * 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.datastax.oss.driver.internal.core.metadata.schema.parsing;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.DefaultProtocolVersion;
import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
import com.datastax.oss.driver.api.core.type.DataType;
import com.datastax.oss.driver.api.core.type.DataTypes;
import com.datastax.oss.driver.api.core.type.UserDefinedType;
import com.datastax.oss.driver.api.core.type.codec.TypeCodecs;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.internal.core.type.DefaultTupleType;
import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder;
import com.datastax.oss.driver.internal.core.type.codec.ParseUtils;
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
import com.datastax.oss.protocol.internal.util.Bytes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Parses data types from schema tables, for Cassandra 2.2 and below.
 *
 * 

In these versions, data types appear as class names, like * "org.apache.cassandra.db.marshal.AsciiType" or * "org.apache.cassandra.db.marshal.TupleType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.Int32Type)". * *

This is modified (and simplified) from Cassandra's {@code TypeParser} class to suit our needs. * In particular it's not very efficient, but it doesn't really matter since it's rarely used and * never in a critical path. */ @ThreadSafe public class DataTypeClassNameParser implements DataTypeParser { private static final Logger LOG = LoggerFactory.getLogger(DataTypeClassNameParser.class); @Override public DataType parse( CqlIdentifier keyspaceId, String toParse, Map userTypes, InternalDriverContext context) { // We take keyspaceId as a parameter because of the parent interface, but it's actually unused // by this implementation. return parse(toParse, userTypes, context, context.getSessionName()); } /** * Simplified parse method for external use. * *

This is intended for use in Cassandra's UDF implementation (the current version uses the * similar method from driver 3). */ public DataType parse(String toParse, AttachmentPoint attachmentPoint) { return parse( toParse, null, // No caching of user types: nested types will always be fully re-parsed attachmentPoint, "parser"); } private DataType parse( String toParse, Map userTypes, AttachmentPoint attachmentPoint, String logPrefix) { boolean frozen = false; if (isReversed(toParse)) { // Just skip the ReversedType part, we don't care toParse = getNestedClassName(toParse); } else if (toParse.startsWith("org.apache.cassandra.db.marshal.FrozenType")) { frozen = true; toParse = getNestedClassName(toParse); } Parser parser = new Parser(toParse, 0); String next = parser.parseNextName(); if (next.startsWith("org.apache.cassandra.db.marshal.ListType")) { DataType elementType = parse(parser.getTypeParameters().get(0), userTypes, attachmentPoint, logPrefix); return DataTypes.listOf(elementType, frozen); } if (next.startsWith("org.apache.cassandra.db.marshal.SetType")) { DataType elementType = parse(parser.getTypeParameters().get(0), userTypes, attachmentPoint, logPrefix); return DataTypes.setOf(elementType, frozen); } if (next.startsWith("org.apache.cassandra.db.marshal.MapType")) { List parameters = parser.getTypeParameters(); DataType keyType = parse(parameters.get(0), userTypes, attachmentPoint, logPrefix); DataType valueType = parse(parameters.get(1), userTypes, attachmentPoint, logPrefix); return DataTypes.mapOf(keyType, valueType, frozen); } if (frozen) LOG.warn( "[{}] Got o.a.c.db.marshal.FrozenType for something else than a collection, " + "this driver version might be too old for your version of Cassandra", logPrefix); if (next.startsWith("org.apache.cassandra.db.marshal.UserType")) { ++parser.idx; // skipping '(' CqlIdentifier keyspace = CqlIdentifier.fromInternal(parser.readOne()); parser.skipBlankAndComma(); String typeName = TypeCodecs.TEXT.decode( Bytes.fromHexString("0x" + parser.readOne()), attachmentPoint.getProtocolVersion()); if (typeName == null) { throw new AssertionError("Type name cannot be null, this is a server bug"); } CqlIdentifier typeId = CqlIdentifier.fromInternal(typeName); Map nameAndTypeParameters = parser.getNameAndTypeParameters(); // Avoid re-parsing if we already have the definition if (userTypes != null && userTypes.containsKey(typeId)) { // copy as frozen since C* 2.x UDTs are always frozen. return userTypes.get(typeId).copy(true); } else { UserDefinedTypeBuilder builder = new UserDefinedTypeBuilder(keyspace, typeId); parser.skipBlankAndComma(); for (Map.Entry entry : nameAndTypeParameters.entrySet()) { CqlIdentifier fieldName = CqlIdentifier.fromInternal(entry.getKey()); DataType fieldType = parse(entry.getValue(), userTypes, attachmentPoint, logPrefix); builder.withField(fieldName, fieldType); } // Create a frozen UserType since C* 2.x UDTs are always frozen. return builder.frozen().withAttachmentPoint(attachmentPoint).build(); } } if (next.startsWith("org.apache.cassandra.db.marshal.TupleType")) { List rawTypes = parser.getTypeParameters(); ImmutableList.Builder componentTypesBuilder = ImmutableList.builder(); for (String rawType : rawTypes) { componentTypesBuilder.add(parse(rawType, userTypes, attachmentPoint, logPrefix)); } return new DefaultTupleType(componentTypesBuilder.build(), attachmentPoint); } DataType type = NATIVE_TYPES_BY_CLASS_NAME.get(next); return type == null ? DataTypes.custom(toParse) : type; } static boolean isReversed(String toParse) { return toParse.startsWith("org.apache.cassandra.db.marshal.ReversedType"); } private static String getNestedClassName(String className) { Parser p = new Parser(className, 0); p.parseNextName(); List l = p.getTypeParameters(); if (l.size() != 1) { throw new IllegalStateException(); } className = l.get(0); return className; } static class Parser { private final String str; private int idx; Parser(String str, int idx) { this.str = str; this.idx = idx; } String parseNextName() { skipBlank(); return readNextIdentifier(); } private String readOne() { String name = parseNextName(); String args = readRawArguments(); return name + args; } // Assumes we have just read a class name and read it's potential arguments // blindly. I.e. it assume that either parsing is done or that we're on a '(' // and this reads everything up until the corresponding closing ')'. It // returns everything read, including the enclosing parenthesis. private String readRawArguments() { skipBlank(); if (isEOS() || str.charAt(idx) == ')' || str.charAt(idx) == ',') { return ""; } if (str.charAt(idx) != '(') { throw new IllegalStateException( String.format( "Expecting char %d of %s to be '(' but '%c' found", idx, str, str.charAt(idx))); } int i = idx; int open = 1; while (open > 0) { ++idx; if (isEOS()) { throw new IllegalStateException("Non closed parenthesis"); } if (str.charAt(idx) == '(') { open++; } else if (str.charAt(idx) == ')') { open--; } } // we've stopped at the last closing ')' so move past that ++idx; return str.substring(i, idx); } List getTypeParameters() { List list = new ArrayList<>(); if (isEOS()) { return list; } if (str.charAt(idx) != '(') { throw new IllegalStateException(); } ++idx; // skipping '(' while (skipBlankAndComma()) { if (str.charAt(idx) == ')') { ++idx; return list; } list.add(readOne()); } throw new IllegalArgumentException( String.format( "Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); } Map getCollectionsParameters() { if (isEOS()) { return Collections.emptyMap(); } if (str.charAt(idx) != '(') { throw new IllegalStateException(); } ++idx; // skipping '(' return getNameAndTypeParameters(); } // Must be at the start of the first parameter to read private Map getNameAndTypeParameters() { // The order of the hashmap matters for UDT Map map = new LinkedHashMap<>(); while (skipBlankAndComma()) { if (str.charAt(idx) == ')') { ++idx; return map; } String bbHex = readNextIdentifier(); String name = null; try { name = TypeCodecs.TEXT.decode( Bytes.fromHexString("0x" + bbHex), DefaultProtocolVersion.DEFAULT); } catch (NumberFormatException e) { throwSyntaxError(e.getMessage()); } skipBlank(); if (str.charAt(idx) != ':') { throwSyntaxError("expecting ':' token"); } ++idx; skipBlank(); map.put(name, readOne()); } throw new IllegalArgumentException( String.format( "Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); } private void throwSyntaxError(String msg) { throw new IllegalArgumentException( String.format("Syntax error parsing '%s' at char %d: %s", str, idx, msg)); } private boolean isEOS() { return isEOS(str, idx); } private static boolean isEOS(String str, int i) { return i >= str.length(); } private void skipBlank() { idx = skipBlank(str, idx); } private static int skipBlank(String str, int i) { while (!isEOS(str, i) && ParseUtils.isBlank(str.charAt(i))) { ++i; } return i; } // skip all blank and at best one comma, return true if there not EOS private boolean skipBlankAndComma() { boolean commaFound = false; while (!isEOS()) { int c = str.charAt(idx); if (c == ',') { if (commaFound) { return true; } else { commaFound = true; } } else if (!ParseUtils.isBlank(c)) { return true; } ++idx; } return false; } // left idx positioned on the character stopping the read private String readNextIdentifier() { int i = idx; while (!isEOS() && ParseUtils.isCqlIdentifierChar(str.charAt(idx))) { ++idx; } return str.substring(i, idx); } @Override public String toString() { return str.substring(0, idx) + "[" + (idx == str.length() ? "" : str.charAt(idx)) + "]" + str.substring(idx + 1); } } @VisibleForTesting static ImmutableMap NATIVE_TYPES_BY_CLASS_NAME = new ImmutableMap.Builder() .put("org.apache.cassandra.db.marshal.AsciiType", DataTypes.ASCII) .put("org.apache.cassandra.db.marshal.LongType", DataTypes.BIGINT) .put("org.apache.cassandra.db.marshal.BytesType", DataTypes.BLOB) .put("org.apache.cassandra.db.marshal.BooleanType", DataTypes.BOOLEAN) .put("org.apache.cassandra.db.marshal.CounterColumnType", DataTypes.COUNTER) .put("org.apache.cassandra.db.marshal.DecimalType", DataTypes.DECIMAL) .put("org.apache.cassandra.db.marshal.DoubleType", DataTypes.DOUBLE) .put("org.apache.cassandra.db.marshal.FloatType", DataTypes.FLOAT) .put("org.apache.cassandra.db.marshal.InetAddressType", DataTypes.INET) .put("org.apache.cassandra.db.marshal.Int32Type", DataTypes.INT) .put("org.apache.cassandra.db.marshal.UTF8Type", DataTypes.TEXT) .put("org.apache.cassandra.db.marshal.TimestampType", DataTypes.TIMESTAMP) .put("org.apache.cassandra.db.marshal.SimpleDateType", DataTypes.DATE) .put("org.apache.cassandra.db.marshal.TimeType", DataTypes.TIME) .put("org.apache.cassandra.db.marshal.UUIDType", DataTypes.UUID) .put("org.apache.cassandra.db.marshal.IntegerType", DataTypes.VARINT) .put("org.apache.cassandra.db.marshal.TimeUUIDType", DataTypes.TIMEUUID) .put("org.apache.cassandra.db.marshal.ByteType", DataTypes.TINYINT) .put("org.apache.cassandra.db.marshal.ShortType", DataTypes.SMALLINT) .put("org.apache.cassandra.db.marshal.DurationType", DataTypes.DURATION) .build(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy