/* * 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.pig.newplan.logical.relational; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.pig.data.DataType; import org.apache.pig.impl.logicalLayer.FrontendException; import org.apache.pig.impl.logicalLayer.schema.Schema.FieldSchema; import org.apache.pig.impl.util.Pair; import org.apache.pig.newplan.logical.expression.LogicalExpression; /** * Schema, from a logical perspective. */ public class LogicalSchema { public static class LogicalFieldSchema { public String alias; public byte type; public long uid; public LogicalSchema schema; public LogicalFieldSchema(String alias, LogicalSchema schema, byte type) { this(alias, schema, type, -1); } public LogicalFieldSchema(LogicalFieldSchema fs) { this(fs.alias, fs.schema, fs.type, fs.uid); } public LogicalFieldSchema(String alias, LogicalSchema schema, byte type, long uid) { this.alias = alias; this.type = type; this.schema = schema; this.uid = uid; } /** * Equality is defined as having the same type and either the same schema * or both null schema. Alias and uid are not checked. */ public boolean isEqual(Object other) { if (other instanceof LogicalFieldSchema) { LogicalFieldSchema ofs = (LogicalFieldSchema)other; if (type != ofs.type) return false; if (schema == null && ofs.schema == null) return true; if (schema == null) return false; else return schema.isEqual(ofs.schema); } else { return false; } } public String toString(boolean verbose) { String uidString = ""; if (verbose) uidString="#" + uid; if( type == DataType.BAG ) { if( schema == null ) { return ( alias + uidString + ":bag{}" ); } return ( alias + uidString + ":bag{" + schema.toString() + "}" ); } else if( type == DataType.TUPLE ) { if( schema == null ) { return ( alias + uidString + ":tuple{}" ); } return ( alias + uidString + ":tuple(" + schema.toString() + ")" ); } return ( alias + uidString + ":" + DataType.findTypeName(type) ); } public String toString() { return toString(true); } public void stampFieldSchema() { if (uid==-1) uid = LogicalExpression.getNextUid(); if (schema!=null) { for (LogicalFieldSchema fs : schema.getFields()) { fs.stampFieldSchema(); } } } private boolean compatible(LogicalFieldSchema uidOnlyFieldSchema) { if (uidOnlyFieldSchema==null) return false; if (this.schema==null && uidOnlyFieldSchema.schema!=null || this.schema!=null && uidOnlyFieldSchema==null) return false; if (this.schema!=null) { if (this.schema.size()!=uidOnlyFieldSchema.schema.size()) return false; for (int i=0;i<this.schema.size();i++) { boolean comp = schema.getField(i).compatible(uidOnlyFieldSchema.schema.getField(i)); if (!comp) return false; } } return true; } public LogicalSchema.LogicalFieldSchema mergeUid(LogicalFieldSchema uidOnlyFieldSchema) throws FrontendException { if (uidOnlyFieldSchema!=null && compatible(uidOnlyFieldSchema)) { this.uid = uidOnlyFieldSchema.uid; if (this.schema!=null) { for (int i=0;i<this.schema.size();i++) { schema.getField(i).mergeUid(uidOnlyFieldSchema.schema.getField(i)); } } return uidOnlyFieldSchema; } else { if (uidOnlyFieldSchema==null) { stampFieldSchema(); } else { this.uid = uidOnlyFieldSchema.uid; if (this.schema!=null) { for (int i=0;i<this.schema.size();i++) { schema.getField(i).stampFieldSchema(); } } } LogicalFieldSchema clonedUidOnlyCopy = cloneUid(); return clonedUidOnlyCopy; } } public LogicalFieldSchema cloneUid() { LogicalFieldSchema resultFs = null; if (schema==null) { resultFs = new LogicalFieldSchema(null, null, type, uid); } else { LogicalSchema newSchema = new LogicalSchema(); resultFs = new LogicalFieldSchema(null, newSchema, type, uid); for (int i=0;i<schema.size();i++) { LogicalFieldSchema fs = schema.getField(i).cloneUid(); newSchema.addField(fs); } } return resultFs; } public LogicalFieldSchema deepCopy() { LogicalFieldSchema newFs = new LogicalFieldSchema(alias!=null?alias:null, schema!=null?schema.deepCopy():null, type, uid); return newFs; } } private List<LogicalFieldSchema> fields; private Map<String, Pair<Integer, Boolean>> aliases; private boolean twoLevelAccessRequired = false; public LogicalSchema() { fields = new ArrayList<LogicalFieldSchema>(); aliases = new HashMap<String, Pair<Integer, Boolean>>(); } /** * Add a field to this schema. * @param field to be added to the schema */ public void addField(LogicalFieldSchema field) { fields.add(field); if (field.alias != null && !field.alias.equals("")) { // put the full name of this field into aliases map // boolean in the pair indicates if this alias is full name aliases.put(field.alias, new Pair<Integer, Boolean>(fields.size()-1, true)); int index = 0; // check and put short names into alias map if there is no conflict while(index != -1) { index = field.alias.indexOf("::", index); if (index != -1) { String a = field.alias.substring(index+2); if (aliases.containsKey(a)) { // remove conflict if the conflict is not full name // we can never remove full name if (!aliases.get(a).second) { aliases.remove(a); } }else{ // put alias into map and indicate it is a short name aliases.put(a, new Pair<Integer, Boolean>(fields.size()-1, false)); } index = index +2; } } } } /** * Fetch a field by alias * @param alias * @return field associated with alias, or null if no such field */ public LogicalFieldSchema getField(String alias) { Pair<Integer, Boolean> p = aliases.get(alias); if (p == null) { return null; } return fields.get(p.first); } /** * Fetch a field by field number * @param fieldNum field number to fetch * @return field */ public LogicalFieldSchema getField(int fieldNum) { return fields.get(fieldNum); } /** * Get all fields * @return list of all fields */ public List<LogicalFieldSchema> getFields() { return fields; } /** * Get the size of the schema. * @return size */ public int size() { return fields.size(); } /** * Two schemas are equal if they are of equal size and their fields * schemas considered in order are equal. */ public boolean isEqual(Object other) { if (other != null && other instanceof LogicalSchema) { LogicalSchema os = (LogicalSchema)other; if (size() != os.size()) return false; for (int i = 0; i < size(); i++) { if (!getField(i).isEqual(os.getField(i))) return false; } return true; } else { return false; } } /** * Look for the index of the field that contains the specified uid * @param uid the uid to look for * @return the index of the field, -1 if not found */ public int findField(long uid) { for(int i=0; i< size(); i++) { LogicalFieldSchema f = getField(i); // if this field has the same uid, then return this field if (f.uid == uid) { return i; } // if this field has a schema, check its schema if (f.schema != null) { if (f.schema.findField(uid) != -1) { return i; } } } return -1; } /** * Merge two schemas. * @param s1 * @param s2 * @return a merged schema, or null if the merge fails */ public static LogicalSchema merge(LogicalSchema s1, LogicalSchema s2) throws FrontendException { // If any of the schema is null, take the other party if (s1==null || s2==null) { if (s1!=null) return s1.deepCopy(); else if (s2!=null) return s2.deepCopy(); else return null; } if (s1.size()!=s2.size()) return null; LogicalSchema mergedSchema = new LogicalSchema(); for (int i=0;i<s1.size();i++) { String mergedAlias; byte mergedType; LogicalSchema mergedSubSchema = null; LogicalFieldSchema fs1 = s1.getField(i); LogicalFieldSchema fs2 = s2.getField(i); if (fs1.alias==null) mergedAlias = fs2.alias; else { mergedAlias = fs1.alias; // If both schema have alias, the first one win } if (fs1.type==DataType.NULL) mergedType = fs2.type; else mergedType = fs1.type; if (DataType.isSchemaType(mergedType)) { mergedSubSchema = merge(fs1.schema, fs2.schema); if (mergedSubSchema==null) { throw new FrontendException("Error merging schema " + fs1 + " and " + fs2, 2246); } } LogicalFieldSchema mergedFS = new LogicalFieldSchema(mergedAlias, mergedSubSchema, mergedType); mergedSchema.addField(mergedFS); if (s1.isTwoLevelAccessRequired() && s2.isTwoLevelAccessRequired()) { mergedSchema.setTwoLevelAccessRequired(true); } } return mergedSchema; } public String toString(boolean verbose) { StringBuilder str = new StringBuilder(); for( LogicalFieldSchema field : fields ) { str.append( field.toString(verbose) + "," ); } if( fields.size() != 0 ) { str.deleteCharAt( str.length() -1 ); } return str.toString(); } public String toString() { return toString(true); } public void setTwoLevelAccessRequired(boolean flag) { twoLevelAccessRequired = flag; } public boolean isTwoLevelAccessRequired() { return twoLevelAccessRequired; } public LogicalSchema mergeUid(LogicalSchema uidOnlySchema) throws FrontendException { if (uidOnlySchema!=null) { if (size()!=uidOnlySchema.size()) { throw new FrontendException("Structure of schema change. Original: " + uidOnlySchema + " Now: " + this, 2239); } for (int i=0;i<size();i++) { getField(i).mergeUid(uidOnlySchema.getField(i)); } return uidOnlySchema; } else { LogicalSchema clonedUidOnlyCopy = new LogicalSchema(); for (int i=0;i<size();i++) { getField(i).stampFieldSchema(); clonedUidOnlyCopy.addField(getField(i).cloneUid()); } return clonedUidOnlyCopy; } } public LogicalSchema deepCopy() { LogicalSchema newSchema = new LogicalSchema(); newSchema.setTwoLevelAccessRequired(isTwoLevelAccessRequired()); for (int i=0;i<size();i++) newSchema.addField(getField(i).deepCopy()); return newSchema; } }