package com.tesora.dve.db; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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/>. * #L% */ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.tesora.dve.common.PEConstants; import com.tesora.dve.common.PEStringUtils; import com.tesora.dve.common.catalog.PersistentSite; import com.tesora.dve.common.catalog.StorageSite; import com.tesora.dve.db.mysql.MysqlEmitter; import com.tesora.dve.exceptions.PECodingException; import com.tesora.dve.server.global.HostService; import com.tesora.dve.server.messaging.SQLCommand; import com.tesora.dve.singleton.Singletons; import com.tesora.dve.sql.node.expression.DelegatingLiteralExpression; import com.tesora.dve.sql.node.expression.ExpressionNode; import com.tesora.dve.sql.node.expression.LateBindingConstantExpression; import com.tesora.dve.sql.schema.ConnectionValues; import com.tesora.dve.sql.schema.Name; import com.tesora.dve.sql.schema.SchemaContext; import com.tesora.dve.sql.schema.TempTable; import com.tesora.dve.sql.schema.cache.IAutoIncrementLiteralExpression; import com.tesora.dve.sql.schema.cache.IConstantExpression; import com.tesora.dve.sql.schema.cache.IDelegatingLiteralExpression; import com.tesora.dve.sql.schema.cache.ILiteralExpression; import com.tesora.dve.sql.schema.cache.IParameter; import com.tesora.dve.sql.statement.StatementType; import com.tesora.dve.variables.KnownVariables; import com.tesora.dve.variables.VariableStoreSource; // a generic sql command is what would be emitted by the emitter upto any literals // instead it uses a 'format' string (non literal parts of the final result) // and a list of offsets for where literals exist. public class GenericSQLCommand { public interface DBNameResolver { String getNameOnSite(String dbName); int getSiteIndex(); StorageSite getWorkerSite(); } private static enum Tokens { SPACE(" "), SINGLE_QUOTE("'"), QUESTION_MARK("?"), NULL("null"); private final String token; private Tokens(final String token) { this.token = token; } public byte[] getBytes(final Charset encoding) { return this.token.getBytes(encoding); // TODO: encode using CharsetEncoder. } @Override public String toString() { return this.token; } } private static final class FragmentTable { private final List<CommandFragment> fragments; private final Map<OffsetEntry, Integer> fragmentIndex; public FragmentTable(final int initialCapacity) { this.fragments = new ArrayList<CommandFragment>(initialCapacity); this.fragmentIndex = new HashMap<OffsetEntry, Integer>(initialCapacity); } public FragmentTable(final FragmentTable other) { this(other.fragments, other.fragmentIndex); } private FragmentTable(final List<CommandFragment> fragments, final Map<OffsetEntry, Integer> fragmentIndex) { this.fragments = new ArrayList<CommandFragment>(fragments); this.fragmentIndex = new HashMap<OffsetEntry, Integer>(fragmentIndex); } /** * May trigger lazy decoding of all fragments. * * @return The length of a String formed by concatenating decoded values * of all fragments. */ public int getDecodedLength() { int totalLength = 0; for (final CommandFragment fragment : this.fragments) { totalLength += fragment.getDecoded().length(); } return totalLength; } /** * May trigger lazy encoding of all fragments. * * @return The size of a buffer needed to accommodate encoded values of * all fragments. */ public int getEncodedLength() { int totalLength = 0; for (final CommandFragment fragment : this.fragments) { totalLength += fragment.getEncoded().remaining(); } return totalLength; } public Set<OffsetEntry> viewIndexEntries() { return Collections.unmodifiableSet(this.fragmentIndex.keySet()); } public List<CommandFragment> viewFragments() { return Collections.unmodifiableList(this.fragments); } public boolean hasIndexEntries() { return !this.fragmentIndex.isEmpty(); } /** * @return Position of an indexable fragment in the list of fragments. */ public int getFragmentPosition(final OffsetEntry key) { return this.fragmentIndex.get(key); } /** * Add a non-indexable fragment that cannot be resolved/replaced. * It is safe to share these fragments between different command * instances as they never change. */ public void add(final CommandFragment fragment) { this.add(null, fragment); } /** * Add an indexable fragment. These fragments can be accessed and * replaced during command resolution. */ public void add(final OffsetEntry key, final CommandFragment fragment) { if (fragment != null) { if (key != null) { if (this.fragmentIndex.containsKey(key)) { throw new PECodingException("Fragment for '" + key.toString() + "' is already occupied."); } this.fragmentIndex.put(key, this.fragments.size()); } this.fragments.add(fragment); } } public void replace(final OffsetEntry key, final CommandFragment newValue) { final Integer position = this.fragmentIndex.get(key); if (position == null) { throw new PECodingException("No fragment to replace for '" + key.toString() + "' found."); } this.fragments.set(position, newValue); } public void addAll(final FragmentTable other) { final int indexOffset = this.fragments.size(); this.fragments.addAll(other.fragments); for (final Map.Entry<OffsetEntry, Integer> otherEntry : other.fragmentIndex.entrySet()) { this.fragmentIndex.put(otherEntry.getKey(), otherEntry.getValue() + indexOffset); } } /** * Produce a display-friendly version of this table. * * The column headers are: * "fragment position" (index into the fragment list)" * "index key entry" (offset entry that binds to the fragment if any)" * "decoded fragment value" * * May trigger lazy decoding of all fragments. * * @return Text formatted version of this table. */ @Override public String toString() { final int numFragments = this.fragments.size(); final StringBuilder table = new StringBuilder(); for (int i = 0; i < numFragments; ++i) { table .append(i).append(" ") .append(String.valueOf(findIndexKeyForValue(i))).append(" \"") .append(this.fragments.get(i)).append("\"") .append(PEConstants.LINE_SEPARATOR); } return table.toString(); } private OffsetEntry findIndexKeyForValue(final int value) { for (final Map.Entry<OffsetEntry, Integer> indexEntry : this.fragmentIndex.entrySet()) { if (indexEntry.getValue() == value) { return indexEntry.getKey(); } } return null; } } public static final class CommandFragment { private final Charset encoding; private String decoded; private ByteBuffer encoded; protected CommandFragment(final Charset encoding, final Tokens sqlToken) { this(encoding, sqlToken.toString()); } protected CommandFragment(final Charset encoding, final Name schemaObjectName) { this(encoding, schemaObjectName.get()); } protected CommandFragment(final Charset encoding, final StringBuilder decoded) { this(encoding, decoded.toString()); } protected CommandFragment(final Charset encoding, final String decoded) { this.encoding = encoding; this.decoded = decoded; } protected CommandFragment(final Charset encoding, final ByteBuffer encoded) { this.encoding = encoding; this.encoded = (ByteBuffer) encoded.duplicate().rewind(); // TODO: is not read-only @see getDecoded() } /** * Decode the encoded value if not already. * * @return Decoded value of this fragment. */ public String getDecoded() { if (this.decoded == null) { this.decoded = new String(this.encoded.array(), this.encoding); // TODO: decode using CharsetDecoder. } return this.decoded; } /** * Encode the decoded value if not already. * * @return Encoded value of this fragment. */ public ByteBuffer getEncoded() { if (this.encoded == null) { this.encoded = ByteBuffer.wrap(this.decoded.getBytes(this.encoding)).asReadOnlyBuffer(); // TODO: encode using CharsetEncoder. } return this.encoded.duplicate(); } /** * May trigger lazy decoding of this fragment. * * @return Decoded value of this fragment. */ @Override public String toString() { return this.getDecoded(); } } // private static final class FormatBuffer { // // private static final float BUFFER_INITIAL_CAPACITY_FACTOR = 2.0f; // // private ByteBuffer buffer; // // private static ByteBuffer ensureCapacity(final ByteBuffer container, final int requiredRemainingCapacity) { // final int needeExtraCapacity = requiredRemainingCapacity - container.remaining(); // if (needeExtraCapacity > 0) { // return expandCapacity(container, needeExtraCapacity); // } // // return container; // } // // private static ByteBuffer expandCapacity(final ByteBuffer container, final int extraSpace) { // final ByteBuffer extended = allocateByteBuffer(container.capacity() + extraSpace); // extended.put(container.array(), 0, container.position()); // return extended; // } // // private static ByteBuffer allocateByteBuffer(final int minInitialCapacity) { // return allocateByteBuffer(minInitialCapacity, BUFFER_INITIAL_CAPACITY_FACTOR); // } // // private static ByteBuffer allocateByteBuffer(final int minInitialCapacity, final float extraCapacityFraction) { // return ByteBuffer.allocate((int) (minInitialCapacity * extraCapacityFraction)); // } // // public static FormatBuffer wrap(final byte[] bytes) { // return wrap(ByteBuffer.wrap(bytes)); // } // // public static FormatBuffer wrap(final ByteBuffer bytes) { // return new FormatBuffer(bytes); // } // // public static FormatBuffer allocate(final int minInitialCapacity) { // return new FormatBuffer(minInitialCapacity); // } // // private FormatBuffer(final int minInitialCapacity) { // this.buffer = allocateByteBuffer(minInitialCapacity); // } // // private FormatBuffer(final ByteBuffer storage) { // this.buffer = storage; // } // // public FormatBuffer append(final byte[] source) { // return append(ByteBuffer.wrap(source)); // } // // public FormatBuffer append(final FormatBuffer source) { // return append(source.viewBytes()); // } // // public FormatBuffer append(final ByteBuffer source) { // return append(source, 0, source.limit()); // } // // public FormatBuffer append(final FormatBuffer source, final int startIndexInclusive, final int endIndexExclusive) { // return append(source.viewBytes(), startIndexInclusive, endIndexExclusive); // } // // public void ensureCapacity(final int requiredRemainingCapacity) { // this.buffer = ensureCapacity(this.buffer, requiredRemainingCapacity); // } // // public void expandCapacity(final int extraSpace) { // this.buffer = expandCapacity(this.buffer, extraSpace); // } // // private FormatBuffer append(final ByteBuffer source, final int startIndexInclusive, final int endIndexExclusive) { // final int numBytesToCopy = endIndexExclusive - startIndexInclusive; // if (numBytesToCopy > 0) { // this.ensureCapacity(numBytesToCopy); // this.buffer.put((ByteBuffer) source.asReadOnlyBuffer().position(startIndexInclusive).limit(endIndexExclusive)); // } // // return this; // } // // public ByteBuffer viewBytes() { // final ByteBuffer readOnlyView = this.buffer.asReadOnlyBuffer(); // if (readOnlyView.position() > 0) { // return (ByteBuffer) readOnlyView.flip(); // } // // return readOnlyView; // } // // public int bytesWritten() { // return this.buffer.position(); // } // } private final Charset encoding; private final FragmentTable commandFragments; private final StatementType type; private Boolean isUpdate = null; private Boolean hasLimit = null; private static Charset getCurrentSessionConnectionCharSet(final SchemaContext sc) { if ((sc.getOptions() != null) && sc.getOptions().isInfoSchemaView()) { // won't have the host service set up, so hardcode in a charset. return KnownVariables.CHARACTER_SET_CLIENT.getDefaultOnMissing().getJavaCharset(); } return getCurrentSessionConnectionCharSet(sc.getConnection().getVariableSource()); } private static Charset getCurrentSessionConnectionCharSet(final VariableStoreSource vs) { return KnownVariables.CHARACTER_SET_CLIENT.getSessionValue(vs).getJavaCharset(); } public GenericSQLCommand(final SchemaContext sc, final String format) { this(getCurrentSessionConnectionCharSet(sc), format); } public GenericSQLCommand(final VariableStoreSource vs, final String format) { this(getCurrentSessionConnectionCharSet(vs), format); } public GenericSQLCommand(final Charset connectionCharset, final String format) { this(connectionCharset, format, Collections.EMPTY_LIST, null, null, null); } public GenericSQLCommand(final Charset encoding, final byte[] format) { this(encoding, new FragmentTable(1), null, null, null); this.commandFragments.add(new CommandFragment(encoding, ByteBuffer.wrap(format))); } private GenericSQLCommand(final SchemaContext sc, final String format, final List<OffsetEntry> entries, StatementType stmtType, Boolean isUpdate, Boolean hasLimit) { this(getCurrentSessionConnectionCharSet(sc), format, entries, stmtType, isUpdate, hasLimit); } private GenericSQLCommand(final Charset connectionCharset, final String format, final List<OffsetEntry> entries, StatementType stmtType, Boolean isUpdate, Boolean hasLimit) { this(connectionCharset, chopToFragments(connectionCharset, format, entries), stmtType, isUpdate, hasLimit); } private GenericSQLCommand(final Charset connectionCharset, final FragmentTable fragments, StatementType stmtType, Boolean isUpdate, Boolean hasLimit) { this.encoding = connectionCharset; this.commandFragments = fragments; this.type = stmtType; this.isUpdate = isUpdate; this.hasLimit = hasLimit; } public Charset getEncoding(){ return encoding; } private static FragmentTable chopToFragments(final Charset encoding, final String value, final List<OffsetEntry> entries) { final FragmentTable fragments = new FragmentTable((2 * entries.size()) + 1); int lastEntryIndex = 0; for (final OffsetEntry entry : entries) { final int nextEntryIndex = entry.getCharacterOffset(); fragments.add(getFragment(encoding, value, lastEntryIndex, nextEntryIndex)); final String entryToken = entry.getToken(); fragments.add(entry, new CommandFragment(encoding, entryToken)); lastEntryIndex = nextEntryIndex + entryToken.length(); } fragments.add(getFragment(encoding, value, lastEntryIndex, value.length())); return fragments; } private static CommandFragment getFragment(final Charset encoding, final String value, final int startIndexInclusive, final int endIndexExclusive) { final String fragmentText = value.substring(startIndexInclusive, endIndexExclusive); if (!fragmentText.isEmpty()) { return new CommandFragment(encoding, fragmentText); } return null; } /** * May trigger lazy decoding of all command fragments. * * @return Decoded command String. */ @Override public String toString() { return this.getDecoded(); } /** * May trigger lazy decoding of all command fragments. * * @return String formed by concatenating decoded values of all command * fragments. * @deprecated This method copies all decoded bytes into a new String. Try * to work with the command fragments directly ( @see public * List<CommandFragment> viewCommandFragments() ) instead. */ @Deprecated public String getDecoded() { final StringBuilder decoded = new StringBuilder(this.commandFragments.getDecodedLength()); for (final CommandFragment cf : this.commandFragments.viewFragments()) { decoded.append(cf.getDecoded()); } return decoded.toString(); } /** * May trigger lazy encoding of all command fragments. * * @return ByteBuffer formed by concatenating encoded values of all command * fragments. * @deprecated This method copies all encoded bytes into a new buffer. Try * to work with the command fragments directly ( @see public * List<CommandFragment> viewCommandFragments() ) instead. */ @Deprecated public ByteBuffer getEncoded() { final ByteBuffer encoded = ByteBuffer.allocate(this.commandFragments.getEncodedLength()); for (final CommandFragment cf : this.commandFragments.viewFragments()) { encoded.put(cf.getEncoded()); } return (ByteBuffer) encoded.rewind(); } /** * @return A read-only view of the fragments forming this command. */ public List<CommandFragment> viewCommandFragments() { return this.commandFragments.viewFragments(); } /** * May trigger lazy decoding of all fragments. * * @return The length of a String formed by concatenating decoded values * of all command fragments. */ public int getDecodedLength() { return this.commandFragments.getDecodedLength(); } /** * May trigger lazy encoding of all fragments. * * @return The size of a buffer needed to accommodate encoded values of * all command fragments. */ public int getEncodedLength() { return this.commandFragments.getEncodedLength(); } public SQLCommand getSQLCommand() { return new SQLCommand(this); } public GenericSQLCommand append(final GenericSQLCommand other) { // TODO: this may be relaxed if (this.encoding != other.encoding) { throw new PECodingException("Appended commands must use same encodings."); } this.commandFragments.addAll(other.commandFragments); return this; } public GenericSQLCommand resolve(ConnectionValues cv, String prettyIndent) { return resolve(cv, false, prettyIndent); } /** * Here we resolve variable entries except late-resolving ones. * * @see public GenericSQLCommand getLateResolvedOnWorker(Worker) */ public GenericSQLCommand resolve(ConnectionValues cv, boolean preserveParamMarkers, String indent) { if (!this.commandFragments.hasIndexEntries()) { return this; } final Emitter emitter = new MysqlEmitter(); final FragmentTable resolvedFragments = new FragmentTable(this.commandFragments); for (final OffsetEntry oe : resolvedFragments.viewIndexEntries()) { if (oe.getKind() == EntryKind.LITERAL) { final LiteralOffsetEntry loe = (LiteralOffsetEntry) oe; final StringBuilder buf = new StringBuilder(); emitter.emitLiteral(cv, loe.getLiteral(), buf); resolvedFragments.replace(oe, new CommandFragment(this.encoding, buf)); } else if (oe.getKind() == EntryKind.LATE_CONSTANT) { final LateBindingConstantOffsetEntry lbcoe = (LateBindingConstantOffsetEntry) oe; final StringBuilder buf = new StringBuilder(); emitter.emitLateBindingConstantExpression(cv, lbcoe.getExpression(), buf); resolvedFragments.replace(oe, new CommandFragment(this.encoding, buf)); } else if (oe.getKind() == EntryKind.PRETTY) { if (indent != null) { final PrettyOffsetEntry poe = (PrettyOffsetEntry) oe; final StringBuilder buf = new StringBuilder(); if (resolvedFragments.getFragmentPosition(oe) > 0) { buf.append(PEConstants.LINE_SEPARATOR); } poe.addIndent(buf, indent); resolvedFragments.replace(oe, new CommandFragment(this.encoding, buf)); } else { if (resolvedFragments.getFragmentPosition(oe) > 0) { resolvedFragments.replace(oe, new CommandFragment(this.encoding, Tokens.SPACE)); } } } else if (oe.getKind() == EntryKind.TEMPTABLE) { final TempTableOffsetEntry ttoe = (TempTableOffsetEntry) oe; resolvedFragments.replace(oe, new CommandFragment(this.encoding, cv.getTempTableName(ttoe.getTempTable().getValuesIndex()))); } else if (oe.getKind() == EntryKind.PARAMETER) { final ParameterOffsetEntry poe = (ParameterOffsetEntry) oe; if (!preserveParamMarkers) { // get the expr from the expr manager and swap it in final Object o = cv.getParameterValue(poe.getParameter().getPosition()); if (o != null) { if (o.getClass().isArray()) { final byte[] quoteBytes = Tokens.SINGLE_QUOTE.getBytes(this.encoding); // TODO: encode using CharsetEncoder. final byte[] parameterValueBytes = (byte[]) o; final ByteBuffer encoded = ByteBuffer.allocate((2 * quoteBytes.length) + parameterValueBytes.length); encoded.put(quoteBytes); encoded.put(parameterValueBytes); encoded.put(quoteBytes); resolvedFragments.replace(oe, new CommandFragment(this.encoding, encoded)); } else { resolvedFragments.replace(oe, new CommandFragment(this.encoding, String.valueOf(o))); } } else { resolvedFragments.replace(oe, new CommandFragment(this.encoding, Tokens.NULL)); } } else { resolvedFragments.replace(oe, new CommandFragment(this.encoding, Tokens.QUESTION_MARK)); } } else if (oe.getKind() == EntryKind.LATEVAR) { final LateResolvingVariableOffsetEntry lrvoe = (LateResolvingVariableOffsetEntry) oe; final Object value = lrvoe.expr.getValue(cv); if (value != null) { resolvedFragments.replace(oe, new CommandFragment(this.encoding, PEStringUtils.singleQuote(value.toString()))); } else { resolvedFragments.replace(oe, new CommandFragment(this.encoding, Tokens.NULL)); } } } return new GenericSQLCommand(this.encoding, resolvedFragments, this.type, this.isUpdate, this.hasLimit); } public GenericSQLCommand resolveLateConstants(ConnectionValues cv) { if (!this.commandFragments.hasIndexEntries()) { return this; } final Emitter emitter = Singletons.require(DBNative.class).getEmitter(); final FragmentTable resolvedFragments = new FragmentTable(this.commandFragments); for (final OffsetEntry oe : resolvedFragments.viewIndexEntries()) { if (oe.getKind() == EntryKind.LATE_CONSTANT) { final LateBindingConstantOffsetEntry lbcoe = (LateBindingConstantOffsetEntry) oe; String value = emitter.emitConstantExprValue(lbcoe.getExpression(), lbcoe.getExpression().getValue(cv)); resolvedFragments.replace(oe, new CommandFragment(this.encoding,value)); } else if (oe.getKind() == EntryKind.LATE_AUTOINC) { final LateAutoincOffsetEntry laoe = (LateAutoincOffsetEntry) oe; String value = emitter.emitConstantExprValue(laoe.getLiteral(), laoe.getLiteral().getValue(cv)); resolvedFragments.replace(oe, new CommandFragment(this.encoding,value)); } } return new GenericSQLCommand(this.encoding, resolvedFragments, this.type, this.isUpdate, this.hasLimit); } /** * Resolve the command as String for display/logging purposes. * * @param lines * Resolved command's lines. */ public void resolveAsTextLines(final ConnectionValues cv, final boolean preserveParamMarkers, final String indent, final List<String> lines) { final GenericSQLCommand resolved = resolve(cv, preserveParamMarkers, indent); final String resolvedAsString = resolved.getDecoded(); lines.addAll(Arrays.asList(resolvedAsString.split(PEConstants.LINE_SEPARATOR))); } /** * Replace delegating literals with raw plan entries. * * @param mapping * Mapping of offset entries to raw plan literal replacements. */ public GenericSQLCommand resolveRawEntries(final Map<Integer, String> mapping, final ConnectionValues cv) { return resolveRawEntries(mapping).resolve(cv, true, null); } private GenericSQLCommand resolveRawEntries(final Map<Integer, String> mapping) { if (!this.commandFragments.hasIndexEntries()) { return this; } final FragmentTable resolvedFragments = new FragmentTable(this.commandFragments); for (final OffsetEntry oe : resolvedFragments.viewIndexEntries()) { if (oe.getKind() == EntryKind.LITERAL) { final LiteralOffsetEntry loe = (LiteralOffsetEntry) oe; final ILiteralExpression ile = loe.getLiteral(); if (ile instanceof IDelegatingLiteralExpression) { final IDelegatingLiteralExpression idle = (IDelegatingLiteralExpression) ile; final String repl = mapping.get(idle.getPosition()); resolvedFragments.replace(oe, new CommandFragment(this.encoding, repl)); } } } return new GenericSQLCommand(this.encoding, resolvedFragments, this.type, this.isUpdate, this.hasLimit); } /** * Here we resolve late entries whose values depend on the worker/site they * execute on. */ public GenericSQLCommand resolveLateEntries(final GenericSQLCommand.DBNameResolver w) { if (!this.commandFragments.hasIndexEntries()) { return this; } final FragmentTable resolvedFragments = new FragmentTable(this.commandFragments); for (final OffsetEntry oe : resolvedFragments.viewIndexEntries()) { if (oe.getKind().isLate()) { final String token = oe.getToken(); if (oe.getKind() == EntryKind.RANDOM_SEED) { if (w.getWorkerSite() instanceof PersistentSite) { /* The IF clause handles the case when seed = 0. */ final String seedSql = String.valueOf(w.getSiteIndex()).concat(" * (").concat(token).concat(" + IF(").concat(token).concat(", 0, 1))"); resolvedFragments.replace(oe, new CommandFragment(this.encoding, seedSql)); } else { resolvedFragments.replace(oe, new CommandFragment(this.encoding, token)); } } else { resolvedFragments.replace(oe, new CommandFragment(this.encoding, w.getNameOnSite(token))); } } } return new GenericSQLCommand(this.encoding, resolvedFragments, this.type, this.isUpdate, this.hasLimit); } public List<Object> getFinalParams(SchemaContext sc) { // does not apply if params are not pushdown if (sc.getValueManager().hasPassDownParams()) { final List<Object> out = new ArrayList<Object>(); for (final OffsetEntry oe : this.commandFragments.viewIndexEntries()) { if (oe.getKind() == EntryKind.PARAMETER) { final ParameterOffsetEntry poe = (ParameterOffsetEntry) oe; out.add(sc.getValueManager().getValue(sc, poe.getParameter())); } } return out; } return null; } public Boolean isSelect() { if (this.type == null) { return null; } return ((this.type == StatementType.SELECT) || (this.type == StatementType.UNION)); } public Boolean isForUpdate() { return this.isUpdate; } public Boolean isLimit() { return this.hasLimit; } public StatementType getStatementType() { return this.type; } public enum EntryKind { LITERAL(false), DBNAME(true), TEMPTABLE(false), PARAMETER(false), LATEVAR(false), PRETTY(false), RANDOM_SEED(true), LATE_CONSTANT(false), LATE_AUTOINC(false); private final boolean late; private EntryKind(final boolean isLate) { this.late = isLate; } public boolean isLate() { return this.late; } } public static abstract class OffsetEntry { protected int offset; protected String token; public OffsetEntry(int off, String tok) { this.offset = off; this.token = tok; } public int getCharacterOffset() { return this.offset; } public String getToken() { return this.token; } public abstract EntryKind getKind(); @Override public String toString() { return this.getKind().toString().concat(" (").concat(this.getToken()).concat(")"); } } public static class LiteralOffsetEntry extends OffsetEntry { protected final ILiteralExpression literal; public LiteralOffsetEntry(int off, String tok, ILiteralExpression dle) { super(off, tok); this.literal = dle; } @Override public EntryKind getKind() { return EntryKind.LITERAL; } public ILiteralExpression getLiteral() { return this.literal; } } public static class LateAutoincOffsetEntry extends OffsetEntry { protected final IAutoIncrementLiteralExpression literal; public LateAutoincOffsetEntry(int off, String tok, IAutoIncrementLiteralExpression expr) { super(off,tok); this.literal = expr; } @Override public EntryKind getKind() { return EntryKind.LATE_AUTOINC; } public IAutoIncrementLiteralExpression getLiteral() { return this.literal; } } public static class PrettyOffsetEntry extends OffsetEntry { private final short indent; public PrettyOffsetEntry(int off, short indent) { super(off, ""); this.indent = indent; } public void addIndent(StringBuilder buf, String multiple) { for (int i = 0; i < this.indent; i++) { buf.append(multiple); } } @Override public EntryKind getKind() { return EntryKind.PRETTY; } } // two different kinds of parameters - those that we can just sub in // and those that we have to pass down - but this is entirely controlled by // the expr manager public static class ParameterOffsetEntry extends OffsetEntry { // this is the original position private final IParameter parameter; public ParameterOffsetEntry(int off, String tok, IParameter param) { super(off, tok); this.parameter = param; } @Override public EntryKind getKind() { return EntryKind.PARAMETER; } public IParameter getParameter() { return this.parameter; } } public static class LateResolveEntry extends OffsetEntry { public LateResolveEntry(int off, String tok) { super(off, tok); } @Override public EntryKind getKind() { return EntryKind.DBNAME; } } public static class TempTableOffsetEntry extends OffsetEntry { protected TempTable temp; public TempTableOffsetEntry(int off, String tok, TempTable tt) { super(off, tok); this.temp = tt; } @Override public EntryKind getKind() { return EntryKind.TEMPTABLE; } public TempTable getTempTable() { return this.temp; } } public static class LateResolvingVariableOffsetEntry extends OffsetEntry { private final IConstantExpression expr; public LateResolvingVariableOffsetEntry(int off, String tok, IConstantExpression expr) { super(off, tok); this.expr = expr; } @Override public EntryKind getKind() { return EntryKind.LATEVAR; } } public static class RandomSeedOffsetEntry extends LateResolveEntry { private final ExpressionNode expr; public RandomSeedOffsetEntry(final int off, final String tok, final ExpressionNode expr) { super(off, tok); this.expr = expr; } public ExpressionNode getSeedExpression() { return this.expr; } @Override public EntryKind getKind() { return EntryKind.RANDOM_SEED; } } public static class LateBindingConstantOffsetEntry extends LateResolveEntry { private final LateBindingConstantExpression expr; public LateBindingConstantOffsetEntry(int offset, String token, LateBindingConstantExpression expr) { super(offset,token); this.expr = expr; } public LateBindingConstantExpression getExpression() { return this.expr; } @Override public EntryKind getKind() { return EntryKind.LATE_CONSTANT; } } // helper class public static class Builder { private final List<OffsetEntry> entries; private boolean isLimit = false; private boolean isForUpdate = false; private StatementType type; public Builder() { this.entries = new ArrayList<OffsetEntry>(); this.type = null; } public Builder withLiteral(int offset, String tok, DelegatingLiteralExpression dle) { this.entries.add(new LiteralOffsetEntry(offset, tok, dle.getCacheExpression())); return this; } public Builder withLateAutoinc(int offset, String tok, IAutoIncrementLiteralExpression ile) { this.entries.add(new LateAutoincOffsetEntry(offset, tok, (IAutoIncrementLiteralExpression) ile.getCacheExpression())); return this; } public Builder withParameter(int offset, String tok, IParameter p) { this.entries.add(new ParameterOffsetEntry(offset, tok, (IParameter) p.getCacheExpression())); return this; } public Builder withDBName(int offset, String tok) { this.entries.add(new LateResolveEntry(offset, tok)); return this; } public Builder withTempTable(int offset, String tok, TempTable tt) { this.entries.add(new TempTableOffsetEntry(offset, tok, tt)); return this; } public Builder withLateVariable(int offset, String tok, IConstantExpression ice) { this.entries.add(new LateResolvingVariableOffsetEntry(offset, tok, ice)); return this; } public Builder withPretty(int offset, int indent) { this.entries.add(new PrettyOffsetEntry(offset, (short) indent)); return this; } public Builder withLimit() { this.isLimit = true; return this; } public Builder withForUpdate() { this.isForUpdate = true; return this; } public Builder withType(StatementType st) { this.type = st; return this; } public Builder withRandomSeed(int offset, String tok, ExpressionNode expr) { this.entries.add(new RandomSeedOffsetEntry(offset, tok, expr)); return this; } public Builder withLateConstant(int offset, String tok, LateBindingConstantExpression expr) { entries.add(new LateBindingConstantOffsetEntry(offset,tok,expr)); return this; } public GenericSQLCommand build(final SchemaContext sc, String format) { return new GenericSQLCommand(sc, format, this.entries, this.type, this.isForUpdate, this.isLimit); } } }