blob: f755c94161701dc358105ce51ade6b5761eb18c0 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.ExpressionClassBuilder
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.derby.impl.sql.compile;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.ClassName;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import org.apache.derby.iapi.services.compiler.ClassBuilder;
import org.apache.derby.iapi.services.compiler.JavaFactory;
import org.apache.derby.iapi.services.compiler.LocalField;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.io.FormatableArrayHolder;
import org.apache.derby.iapi.services.loader.GeneratedClass;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.compile.CompilerContext;
import org.apache.derby.iapi.sql.compile.ExpressionClassBuilderInterface;
import org.apache.derby.iapi.sql.compile.TypeCompiler;
import org.apache.derby.iapi.store.access.ColumnOrdering;
import org.apache.derby.iapi.types.TypeId;
import org.apache.derby.iapi.util.ByteArray;
import org.apache.derby.impl.sql.execute.IndexColumnOrder;
/**
* ExpressionClassBuilder
* provides an interface to satisfy generation's
* common tasks in building classes that involve expressions.
* This is the common superclass of ActivationClassBuilder and
* FilterClassBuilder. See the documentation on ActivationClassBuilder.
*
*/
abstract class ExpressionClassBuilder implements ExpressionClassBuilderInterface
{
///////////////////////////////////////////////////////////////////////
//
// CONSTANTS
//
///////////////////////////////////////////////////////////////////////
static final protected String currentDatetimeFieldName = "cdt";
///////////////////////////////////////////////////////////////////////
//
// STATE
//
///////////////////////////////////////////////////////////////////////
protected ClassBuilder cb;
protected GeneratedClass gc;
protected int nextExprNum;
protected int nextNonFastExpr;
protected int nextFieldNum;
protected MethodBuilder constructor;
CompilerContext myCompCtx;
MethodBuilder executeMethod; // to find it fast
protected LocalField cdtField;
//protected final JavaFactory javaFac;
private String currentRowScanResultSetName;
///////////////////////////////////////////////////////////////////////
//
// CONSTRUCTORS
//
///////////////////////////////////////////////////////////////////////
/**
* By the time this is done, it has constructed the following class:
* <pre>
* final public class #className extends #superClass {
* public #className() { super(); }
* }
* </pre>
*
* @exception StandardException thrown on failure
*/
ExpressionClassBuilder (String superClass, String className, CompilerContext cc )
throws StandardException
{
int modifiers = Modifier.PUBLIC | Modifier.FINAL;
myCompCtx = cc;
JavaFactory javaFac = myCompCtx.getJavaFactory();
if ( className == null ) { className = myCompCtx.getUniqueClassName(); }
// start the class
cb = javaFac.newClassBuilder(myCompCtx.getClassFactory(),
getPackageName(), modifiers,
className, superClass);
beginConstructor();
}
///////////////////////////////////////////////////////////////////////
//
// ABSTRACT METHODS TO BE IMPLEMENTED BY CHILDREN
//
///////////////////////////////////////////////////////////////////////
/**
* Get the name of the package that the generated class will live in.
*
* @return name of package that the generated class will live in.
*/
abstract String getPackageName();
/**
* Get the number of ExecRows that must be allocated
*
* @return number of ExecRows that must be allocated
*
* @exception StandardException thrown on failure
*/
abstract int getRowCount()
throws StandardException;
/**
* Sets the number of subqueries under this expression
*
*
* @exception StandardException thrown on failure
*/
abstract void setNumSubqueries()
throws StandardException;
///////////////////////////////////////////////////////////////////////
//
// ACCESSORS
//
///////////////////////////////////////////////////////////////////////
/**
Return the base class of the activation's hierarchy
(the subclass of Object).
This class is expected to hold methods used by all
compilation code, such as datatype compilation code,
e.g. getDataValueFactory.
*/
abstract String getBaseClassName();
MethodBuilder getConstructor() {
return constructor;
}
ClassBuilder getClassBuilder() {
return cb;
}
/**
* Get a method builder for adding code to the execute() method.
* The method builder does not actually build a method called execute.
* Instead, it creates a method that overrides the reinit() method,
* which is called from execute() on every execution in order to
* reinitialize the data structures.
*/
MethodBuilder getExecuteMethod() {
if (executeMethod == null) {
executeMethod =
cb.newMethodBuilder(Modifier.PROTECTED, "void", "reinit");
executeMethod.addThrownException(ClassName.StandardException);
}
return executeMethod;
}
///////////////////////////////////////////////////////////////////////
//
// CONSTRUCTOR MANAGEMENT
//
///////////////////////////////////////////////////////////////////////
private void beginConstructor()
{
// create a constructor that just calls super.
MethodBuilder realConstructor =
cb.newConstructorBuilder(Modifier.PUBLIC);
realConstructor.callSuper();
realConstructor.methodReturn();
realConstructor.complete();
constructor = cb.newMethodBuilder(Modifier.PUBLIC, "void", "postConstructor");
constructor.addThrownException(ClassName.StandardException);
}
/**
* Finish the constructor by newing the array of Rows and putting a return
* at the end of it.
*
* @exception StandardException thrown on failure
*/
void finishConstructor()
throws StandardException
{
int numResultSets;
/* Set the number of subqueries */
setNumSubqueries();
numResultSets = getRowCount();
/* Generate the new of ExecRow[numResultSets] when there are ResultSets
* which return Rows.
*/
if (numResultSets >= 1)
{
addNewArrayOfRows(numResultSets);
}
/* Generated code is:
* return;
*/
constructor.methodReturn();
constructor.complete();
}
/**
* Generate the assignment for row = new ExecRow[numResultSets]
*
* @param numResultSets The size of the array.
*/
private void addNewArrayOfRows(int numResultSets)
{
/* Generated code is:
* row = new ExecRow[numResultSets];
*/
constructor.pushThis();
constructor.pushNewArray(ClassName.ExecRow, numResultSets);
constructor.putField(ClassName.BaseActivation, "row", ClassName.ExecRow + "[]");
constructor.endStatement();
}
///////////////////////////////////////////////////////////////////////
//
// ADD FIELDS TO GENERATED CLASS
//
///////////////////////////////////////////////////////////////////////
/**
* Add a field declaration to the generated class
*
* @param modifiers The | of the modifier values such as public, static, etc.
* @param type The type of the field in java language.
* @param name The name of the field.
*
* @return None.
*/
LocalField newFieldDeclaration(int modifiers, String type, String name)
{
return cb.addField(type, name, modifiers);
}
/**
* Add an arbitrarily named field to the generated class.
*
* This is used to generate fields where the caller doesn't care what
* the field is named. It is especially useful for generating arbitrary
* numbers of fields, where the caller doesn't know in advance how many
* fields will be used. For example, it is used for generating fields
* to hold intermediate values from expressions.
*
* @param modifiers The | of the modifier values such as public, static, etc.
* @param type The type of the field in java language.
*
* @return The name of the new field
*/
LocalField newFieldDeclaration(int modifiers, String type)
{
return cb.addField(type, newFieldName(), modifiers);
}
///////////////////////////////////////////////////////////////////////
//
// ADD FUNCTIONS TO GENERATED CLASS
//
///////////////////////////////////////////////////////////////////////
/**
* Activations might have need of internal functions
* that are not used by the result sets, but by other
* activation functions. Thus, we make it possible
* for functions to be generated directly as well
* as through the newExprFun interface. newExprFun
* should be used when a static field pointing to the
* expression function is needed.
* <p>
* The generated function will generally have a generated name
* that can be viewed through the MethodBuilder interface.
* This name is generated to ensure uniqueness from other
* function names in the activation class. If you pass in a function
* name, think carefully about whether it will collide with other names.
*
* @param returnType the return type of the function
* @param modifiers the modifiers on the function
*
* @see #newExprFun
*/
MethodBuilder newGeneratedFun(String returnType, int modifiers) {
return newGeneratedFun(returnType, modifiers,
(String[]) null);
}
MethodBuilder newGeneratedFun(String returnType,
int modifiers,
String[] params) {
String exprName = "g".concat(Integer.toString(nextNonFastExpr++));
return newGeneratedFun(exprName, returnType, modifiers,
params);
}
private MethodBuilder newGeneratedFun(String exprName, String returnType,
int modifiers,
String[] params) {
//
// create a new method supplying the given modifiers and return Type
// Java: #modifiers #returnType #exprName { }
//
MethodBuilder exprMethod;
if (params == null)
{
exprMethod = cb.newMethodBuilder(modifiers, returnType, exprName);
}
else
{
exprMethod = cb.newMethodBuilder(modifiers, returnType,
exprName, params);
}
//
// declare it to throw StandardException
// Java: #modifiers #returnType #exprName throws StandardException { }
//
exprMethod.addThrownException(ClassName.StandardException);
return exprMethod;
}
/**
* "ExprFun"s are the "expression functions" that
* are specific to a given JSQL statement. For example,
* an ExprFun is generated to evaluate the where clause
* of a select statement and return a boolean result.
* <p>
*
* All methods return by this are expected to be called
* via the GeneratedMethod interface. Thus the methods
* are public and return java.lang.Object.
* <p>
* Once the exprfun has been created, the
* caller will need to add statements to it,
* minimally a return statement.
* <p>
* ExprFuns return Object types, since they
* are invoked through reflection and thus their
* return type would get wrapped in an object anyway.
* For example: return java.lang.Boolean, not boolean.
*/
MethodBuilder newExprFun()
{
// get next generated function
String exprName = "e".concat(Integer.toString(nextExprNum++));
return newGeneratedFun(exprName, "java.lang.Object", Modifier.PUBLIC, (String[]) null);
}
/**
Push an expression that is a GeneratedMethod reference to the
passed in method. aka. a "function pointer".
*/
void pushMethodReference(MethodBuilder mb, MethodBuilder exprMethod) {
mb.pushThis(); // instance
mb.push(exprMethod.getName()); // arg
mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.GeneratedByteCode,
"getMethod",
ClassName.GeneratedMethod,
1
);
}
/**
* Start a user expression. The difference between a normal expression
* (returned by newExprFun)
* and a user expression is that a user expression catches all exceptions
* (because we don't want random exceptions thrown from user methods to
* propagate to the rest of the system.
*
* @return A new MethodBuilder
*/
MethodBuilder newUserExprFun() {
MethodBuilder mb = newExprFun();
mb.addThrownException("java.lang.Exception");
return mb;
}
///////////////////////////////////////////////////////////////////////
//
// CURRENT DATE/TIME SUPPORT
//
///////////////////////////////////////////////////////////////////////
/**
This utility method returns an expression for CURRENT_DATE.
Get the expression this way, because the activation needs to
generate support information for CURRENT_DATE,
that would otherwise be painful to create manually.
*/
void getCurrentDateExpression(MethodBuilder mb) {
// do any needed setup
LocalField lf = getCurrentSetup();
// generated Java:
// this.cdt.getCurrentDate();
mb.getField(lf);
mb.callMethod(VMOpcode.INVOKEVIRTUAL, (String) null, "getCurrentDate", "java.sql.Date", 0);
}
/**
This utility method returns an expression for CURRENT_TIME.
Get the expression this way, because the activation needs to
generate support information for CURRENT_TIME,
that would otherwise be painful to create manually.
*/
void getCurrentTimeExpression(MethodBuilder mb) {
// do any needed setup
LocalField lf = getCurrentSetup();
// generated Java:
// this.cdt.getCurrentTime();
mb.getField(lf);
mb.callMethod(VMOpcode.INVOKEVIRTUAL, (String) null, "getCurrentTime", "java.sql.Time", 0);
}
/**
This utility method generates an expression for CURRENT_TIMESTAMP.
Get the expression this way, because the activation needs to
generate support information for CURRENT_TIMESTAMP,
that would otherwise be painful to create manually.
*/
void getCurrentTimestampExpression(MethodBuilder mb) {
// do any needed setup
LocalField lf = getCurrentSetup();
// generated Java:
// this.cdt.getCurrentTimestamp();
mb.getField(lf);
mb.callMethod(VMOpcode.INVOKEVIRTUAL, (String) null,
"getCurrentTimestamp", "java.sql.Timestamp", 0);
}
///////////////////////////////////////////////////////////////////////
//
// COLUMN ORDERING
//
///////////////////////////////////////////////////////////////////////
/**
These utility methods buffers compilation from the IndexColumnOrder
class.
They create an ordering based on their parameter, stuff that into
the prepared statement, and then return the entry # for
use in the generated code.
We could write another utility method to generate code to
turn an entry # back into an object, but so far no-one needs it.
WARNING: this is a crafty method that ASSUMES that
you want every column in the list ordered, and that every
column in the list is the entire actual result colunm.
It is only useful for DISTINCT in select.
*/
FormatableArrayHolder getColumnOrdering(ResultColumnList rclist)
{
IndexColumnOrder[] ordering;
int numCols = (rclist == null) ? 0 : rclist.size();
//skip the columns which are not exclusively part of the insert list
//ie columns with default and autoincrement. These columns will not
//be part of ordering.
int numRealCols = 0;
for (int i=0; i<numCols; i++)
{
if (!(rclist.getResultColumn(i+1).isGeneratedForUnmatchedColumnInInsert()))
numRealCols++;
}
ordering = new IndexColumnOrder[numRealCols];
for (int i=0, j=0; i<numCols; i++)
{
if (!(rclist.getResultColumn(i+1).isGeneratedForUnmatchedColumnInInsert()))
{
ordering[j] = new IndexColumnOrder(i);
j++;
}
}
return new FormatableArrayHolder(ordering);
}
/**
* Add a column to the existing Ordering list. Takes
* a column id and only adds it if it isn't in the list.
*
*
* @return the ColumnOrdering array
*/
FormatableArrayHolder addColumnToOrdering(
FormatableArrayHolder orderingHolder,
int columnNum)
{
/*
** We don't expect a lot of order by columns, so
** linear search.
*/
ColumnOrdering[] ordering =
orderingHolder.getArray(ColumnOrdering[].class);
int length = ordering.length;
for (int i = 0; i < length; i++)
{
if (ordering[i].getColumnId() == columnNum)
return orderingHolder;
}
/*
** Didn't find it. Allocate a bigger array
** and add it to the end
*/
IndexColumnOrder[] newOrdering = new IndexColumnOrder[length+1];
System.arraycopy(ordering, 0, newOrdering, 0, length);
newOrdering[length] = new IndexColumnOrder(columnNum);
return new FormatableArrayHolder(newOrdering);
}
FormatableArrayHolder getColumnOrdering(OrderedColumnList<?> oclist) {
int numCols = (oclist == null) ? 0 : oclist.size();
if (numCols == 0)
{
return new FormatableArrayHolder(new IndexColumnOrder[0]);
}
return new FormatableArrayHolder(oclist.getColumnOrdering());
}
int addItem(Object o)
{
if (SanityManager.DEBUG)
{
if ((o != null) && !(o instanceof Serializable))
{
SanityManager.THROWASSERT(
"o (" + o.getClass().getName() +
") expected to be instanceof java.io.Serializable");
}
}
return myCompCtx.addSavedObject(o);
}
///////////////////////////////////////////////////////////////////////
//
// Caching resuable Expressions
//
///////////////////////////////////////////////////////////////////////
/**
* Get/reuse the Expression for getting the DataValueFactory
*/
private Object getDVF;
void pushDataValueFactory(MethodBuilder mb)
{
// generates:
// getDataValueFactory()
//
if (getDVF == null) {
getDVF = mb.describeMethod(VMOpcode.INVOKEVIRTUAL,
getBaseClassName(),
"getDataValueFactory",
ClassName.DataValueFactory);
}
mb.pushThis();
mb.callMethod(getDVF);
}
///////////////////////////////////////////////////////////////////////
//
// RESULT SET SUPPORT
//
///////////////////////////////////////////////////////////////////////
/**
This is a utility method to get a common expression --
"BaseActivation.getResultSetFactory()".
<p>
BaseActivation gets the factory from the context and
caches it for faster retrieval.
*/
private Object getRSF;
void pushGetResultSetFactoryExpression(MethodBuilder mb) {
// generated Java:
// this.getResultSetFactory()
//
if (getRSF == null) {
getRSF = mb.describeMethod(VMOpcode.INVOKEVIRTUAL, getBaseClassName(),
"getResultSetFactory",
ClassName.ResultSetFactory);
}
mb.pushThis();
mb.callMethod(getRSF);
}
/**
This is a utility method to get a common expression --
"BaseActivation.getExecutionFactory()".
REVISIT: could the same expression objects be reused within
the tree and have the correct java generated each time?
<p>
BaseActivation gets the factory from the context and
caches it for faster retrieval.
*/
private Object getEF;
void pushGetExecutionFactoryExpression(MethodBuilder mb) {
if (getEF == null) {
getEF = mb.describeMethod(VMOpcode.INVOKEVIRTUAL, getBaseClassName(),
"getExecutionFactory",
ClassName.ExecutionFactory);
}
// generated Java:
// this.getExecutionFactory()
//
mb.pushThis();
mb.callMethod(getEF);
}
/**
* Generate a reference to the row array that
* all activations use.
*
* @param eb the expression block
*
* @return expression
*/
//private void pushRowArrayReference(MethodBuilder mb)
//{
// PUSHCOMPILE - cache
// mb.pushThis();
// mb.getField(ClassName.BaseActivation, "row", ClassName.ExecRow + "[]");
//}
/**
* Generate a reference to a colunm in a result set.
*
* @param rsNumber the result set number
* @param colId the column number
*/
void pushColumnReference(MethodBuilder mb, int rsNumber, int colId)
{
mb.pushThis();
mb.push(rsNumber);
mb.push(colId);
mb.callMethod(VMOpcode.INVOKEVIRTUAL, ClassName.BaseActivation, "getColumnFromRow",
ClassName.DataValueDescriptor, 2);
//System.out.println("pushColumnReference ");
//pushRowArrayReference(mb);
//mb.getArrayElement(rsNumber); // instance for getColumn
//mb.push(colId); // first arg
//mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.Row, "getColumn", ClassName.DataValueDescriptor, 1);
}
/**
* Generate a reference to the parameter value
* set that all activations use.
*
*/
void pushPVSReference(MethodBuilder mb)
{
// PUSHCOMPILER-WASCACHED
mb.pushThis();
mb.getField(ClassName.BaseActivation, "pvs", ClassName.ParameterValueSet);
}
///////////////////////////////////////////////////////////////////////
//
// CLASS IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////
/*
The first time a current datetime is needed, create the class
level support for it.
*/
protected LocalField getCurrentSetup() {
if (cdtField != null)
return cdtField;
// generated Java:
// 1) the field "cdt" is created:
// private CurrentDatetime cdt;
cdtField = newFieldDeclaration(
Modifier.PRIVATE,
ClassName.CurrentDatetime,
currentDatetimeFieldName);
// 2) the constructor gets a statement to init CurrentDatetime:
// cdt = new CurrentDatetime();
constructor.pushNewStart(ClassName.CurrentDatetime);
constructor.pushNewComplete(0);
constructor.setField(cdtField);
return cdtField;
}
/**
* generated the next field name available.
* these are of the form 'e#', where # is
* incremented each time.
* This shares the name space with the expression methods
* as Java allows names and fields to have the same name.
* This reduces the number of constant pool entries created
* for a generated class file.
*/
private String newFieldName()
{
return "e".concat(Integer.toString(nextFieldNum++));
}
///////////////////////////////////////////////////////////////////////
//
// DEBUG
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// DATATYPES
//
///////////////////////////////////////////////////////////////////////
/**
* Get the TypeCompiler associated with the given TypeId
*
* @param typeId The TypeId to get a TypeCompiler for
*
* @return The corresponding TypeCompiler
*
*/
protected TypeCompiler getTypeCompiler(TypeId typeId)
{
return myCompCtx.getTypeCompilerFactory().getTypeCompiler(typeId);
}
///////////////////////////////////////////////////////////////////////
//
// GENERATE BYTE CODE
//
///////////////////////////////////////////////////////////////////////
/**
* Take the generated class, and turn it into an
* actual class.
* <p> This method assumes, does not check, that
* the class and its parts are all complete.
*
* @param savedBytes place to save generated bytes.
* if null, it is ignored
* @exception StandardException thrown when exception occurs
*/
GeneratedClass getGeneratedClass(ByteArray savedBytes) throws StandardException {
if (gc != null) return gc;
if (savedBytes != null)
{
ByteArray classBytecode = cb.getClassBytecode();
// note: be sure to set the length since
// the class builder allocates the byte array
// in big chunks
savedBytes.setBytes(classBytecode.getArray());
savedBytes.setLength(classBytecode.getLength());
}
gc = cb.getGeneratedClass();
return gc; // !! yippee !! here it is...
}
/**
* Get a "this" expression declared as an Activation.
* This is the commonly used type of the this expression.
*
*/
void pushThisAsActivation(MethodBuilder mb) {
// PUSHCOMPILER - WASCACHED
mb.pushThis();
mb.upCast(ClassName.Activation);
}
/**
Generate a Null data value.
Nothing is required on the stack, a SQL null data value
is pushed.
*/
void generateNull(MethodBuilder mb, TypeCompiler tc, int collationType) {
pushDataValueFactory(mb);
mb.pushNull(tc.interfaceName());
tc.generateNull(mb, collationType);
}
/**
Generate a Null data value.
The express value is required on the stack and will be popped, a SQL null data value
is pushed.
*/
void generateNullWithExpress(MethodBuilder mb, TypeCompiler tc,
int collationType) {
pushDataValueFactory(mb);
mb.swap(); // need the dvf as the instance
mb.cast(tc.interfaceName());
tc.generateNull(mb, collationType);
}
/**
Generate a data value.
The value is to be set in the SQL data value is required
on the stack and will be popped, a SQL data value
is pushed.
*/
void generateDataValue(MethodBuilder mb, TypeCompiler tc,
int collationType, LocalField field) {
pushDataValueFactory(mb);
mb.swap(); // need the dvf as the instance
tc.generateDataValue(mb, collationType, field);
}
/**
*generates a variable name for the rowscanresultset.
*This can not be a fixed name because in cases like
*cascade delete same activation class will be dealing
* more than one RowScanResultSets for dependent tables.
*/
String newRowLocationScanResultSetName()
{
currentRowScanResultSetName = newFieldName();
return currentRowScanResultSetName;
}
// return the Name of ResultSet with the RowLocations to be modified (deleted or updated).
String getRowLocationScanResultSetName()
{
return currentRowScanResultSetName;
}
}