/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.optimizer; import com.foundationdb.sql.StandardException; import com.foundationdb.sql.parser.CreateViewNode; import com.foundationdb.sql.parser.ResultColumn; import com.foundationdb.sql.parser.SQLParser; import com.foundationdb.sql.parser.SQLParserFeature; import com.foundationdb.sql.server.ServerSession; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.TableName; import com.foundationdb.ais.model.View; import com.foundationdb.server.error.InvalidParameterValueException; import com.foundationdb.server.error.ViewHasBadSubqueryException; import com.foundationdb.server.types.service.TypesRegistryServiceImpl; import java.util.*; /** A schema, parser and binder with various client properties. * Also caches view definitions. */ public class AISBinderContext { public static final String CONFIG_PARSER_FEATURES = "parserFeatures"; protected Properties properties; protected AkibanInformationSchema ais; protected SQLParser parser; protected String defaultSchemaName; protected AISBinder binder; protected TypeComputer typeComputer; protected Map<View,AISViewDefinition> viewDefinitions; /** When context is part of a larger object, such as a server session. */ protected AISBinderContext() { } /** Standalone context used for loading views in tests and bootstrapping. */ public AISBinderContext(AkibanInformationSchema ais, String defaultSchemaName) { this.ais = ais; this.defaultSchemaName = defaultSchemaName; properties = new Properties(); properties.put("database", defaultSchemaName); initParser(); setBinderAndTypeComputer(new AISBinder(ais, defaultSchemaName), new FunctionsTypeComputer(TypesRegistryServiceImpl.createRegistryService())); } public Properties getProperties() { return properties; } public String getProperty(String key) { return properties.getProperty(key); } public String getProperty(String key, String defval) { return properties.getProperty(key, defval); } public void setProperty(String key, String value) { if (value == null) properties.remove(key); else properties.setProperty(key, value); } protected void setProperties(Properties properties) { this.properties = properties; } public AkibanInformationSchema getAIS() { return ais; } public SQLParser getParser() { return parser; } protected Set<SQLParserFeature> initParser() { Set<SQLParserFeature> parserFeatures = getParserFeatures(); parser = new SQLParser(); parser.getFeatures().clear(); parser.getFeatures().addAll(parserFeatures); if (defaultSchemaName == null) { defaultSchemaName = getProperty("database"); if (defaultSchemaName == null) defaultSchemaName = getProperty("user"); } // TODO: Any way / need to ask AIS if schema exists and report error? BindingNodeFactory.wrap(parser); return parserFeatures; } protected Set<SQLParserFeature> getParserFeatures() { Set<SQLParserFeature> features = new HashSet<>(); String featuresStr = getProperty(CONFIG_PARSER_FEATURES); if (featuresStr != null) { String[] featureNames = featuresStr.split(","); for(String f : featureNames) { try { features.add(SQLParserFeature.valueOf(f)); } catch(IllegalArgumentException e) { throw new InvalidParameterValueException("'" + f + "' for " + CONFIG_PARSER_FEATURES); } } } if (getBooleanProperty("parserInfixBit", false)) { features.add(SQLParserFeature.INFIX_BIT_OPERATORS); } if (getBooleanProperty("parserInfixLogical", false)) { features.add(SQLParserFeature.INFIX_LOGICAL_OPERATORS); } if (getBooleanProperty("columnAsFunc", false)) { features.add(SQLParserFeature.MYSQL_COLUMN_AS_FUNCS); } String prop = getProperty("parserDoubleQuoted", "identifier"); if (prop.equals("string")) { features.add(SQLParserFeature.DOUBLE_QUOTED_STRING); } else if (!prop.equals("identifier")) { throw new InvalidParameterValueException("'" + prop + "' for parserDoubleQuoted"); } return features; } public boolean getBooleanProperty(String key, boolean defval) { String prop = getProperty(key); if (prop == null) return defval; if (prop.equalsIgnoreCase("true")) return true; else if (prop.equalsIgnoreCase("false")) return false; else throw new InvalidParameterValueException("'" + prop + "' for " + key); } /** Get the non-default properties that were used to parse a view * definition, for example. * @see #initParser */ public Properties getParserProperties(String schemaName) { Properties properties = new Properties(); if (!defaultSchemaName.equals(schemaName)) properties.put("database", defaultSchemaName); String prop = getProperty("parserInfixBit", "false"); if (!"false".equals(prop)) properties.put("parserInfixBit", prop); prop = getProperty("parserInfixLogical", "false"); if (!"false".equals(prop)) properties.put("parserInfixLogical", prop); prop = getProperty("columnAsFunc", "false"); if (!"false".equals(prop)) properties.put("columnAsFunc", prop); prop = getProperty("parserDoubleQuoted", "identifier"); if (!"identifier".equals(prop)) properties.put("parserDoubleQuoted", prop); return properties; } public String getDefaultSchemaName() { return defaultSchemaName; } public void setDefaultSchemaName(String defaultSchemaName) { this.defaultSchemaName = defaultSchemaName; if (binder != null) binder.setDefaultSchemaName(defaultSchemaName); } public AISBinder getBinder() { return binder; } public void setBinderAndTypeComputer(AISBinder binder, TypeComputer typeComputer) { this.binder = binder; binder.setContext(this); this.typeComputer = typeComputer; this.viewDefinitions = new HashMap<>(); } protected void initBinder() { assert (binder == null); setBinderAndTypeComputer(new AISBinder(ais, defaultSchemaName), null); } /** Get view definition given the AIS view. */ public AISViewDefinition getViewDefinition(View view) { AISViewDefinition viewdef = viewDefinitions.get(view); if (viewdef == null) { viewdef = new ViewReloader(view, this).getViewDefinition(view.getDefinition()); viewDefinitions.put(view, viewdef); } return viewdef; } /** When reloading a view from AIS, we need to parse the * definition text again in the same parser environment as it was * originally defined. Also the present binder is in the middle of * something so we need a separate one. */ protected static class ViewReloader extends AISBinderContext { public ViewReloader(View view, AISBinderContext parent) { ais = view.getAIS(); properties = view.getDefinitionProperties(); initParser(); if (defaultSchemaName == null) defaultSchemaName = view.getName().getSchemaName(); setBinderAndTypeComputer(new AISBinder(ais, defaultSchemaName), parent.typeComputer); } } /** Get view definition given the user's DDL. */ public AISViewDefinition getViewDefinition(CreateViewNode ddl, ServerSession server) { try { // Just want the definition for result columns and table references. // If the view uses another view, the inner one is treated // like a table for those purposes. AISViewDefinition view = new AISViewDefinition(ddl, parser); binder.bind(view.getSubquery(), false); view.getTableColumnReferences(); // get the references before expanding views if (typeComputer != null) { typeComputer.compute(view.getSubquery()); } if (!viewHasTypes(view)) { ViewCompiler compiler = new ViewCompiler(server, server.getServiceManager().getStore()); compiler.findAndSetTypes(view); } return view; } catch (StandardException ex) { throw new ViewHasBadSubqueryException(ddl.getObjectName().toString(), ex.getMessage()); } } public boolean viewHasTypes(AISViewDefinition view) { for (ResultColumn col : view.getResultColumns()) { if (col.getType() == null) return false; } return true; } /** Get view definition using stored copy of original DDL. */ public AISViewDefinition getViewDefinition(String ddl) { AISViewDefinition view = null; try { view = new AISViewDefinition(ddl, parser); // Want a subquery that can be spliced in. // If the view uses another view, it gets expanded, too. binder.bind(view.getSubquery(), true); if (typeComputer != null) typeComputer.compute(view.getSubquery()); } catch (StandardException ex) { String name = ddl; if (view != null) name = view.getName().toString(); throw new ViewHasBadSubqueryException(name, ex.getMessage()); } return view; } public boolean isAccessible(TableName object) { return isSchemaAccessible(object.getSchemaName()); } public boolean isSchemaAccessible(String schemaName) { return true; } }