
jdk.incubator.sql2.OperationGroup Maven / Gradle / Ivy
Show all versions of pgadba Show documentation
/*
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.incubator.sql2;
import java.time.Duration;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collector;
/**
*
* A set of {@link Operation}s that share certain properties, are managed as a
* unit, and are executed as a unit. The {@link Operation}s created by an
* {@code OperationGroup} and submitted are the member {@link Operation}s of
* that {@code OperationGroup}. An {@code OperationGroup} is not a transaction
* and is not related to a transaction in any way.
*
*
* An {@link OperationGroup} provides conditional execution, control of error
* response, and control of execution order.
*
*
* Execution of one or more {@link Operation}s may depend on the result of a
* previous {@link Operation}. Depending on the result of that previous
* {@link Operation} some other {@link Operation}s perhaps should not be
* executed. For example consider an account withdrawal. If the amount to
* withdraw exceeds the account balance the withdrawal {@link Operation}s should
* not be executed and an overdraft {@link Operation} should be executed
* instead. It would be possible for the user thread to wait for the balance
* check {@link Operation} but that would block. Better would be to use the
* {@link java.util.concurrent.CompletionStage} of the balance check
* {@link Operation} and submit the appropriate {@link Operation} in a
* subsequent stage. But this is a common pattern and it is better still to
* encapsulate that pattern, which conditional {@link OperationGroup} does.
*
*
* Not all {@link Operation}s need to be executed in the order submitted. The
* most common example is a mass insert. The order in which the records are
* inserted doesn’t matter. A parallel {@link OperationGroup} gives the
* implementation the freedom to execute the {@link Operation}s in any order. If
* some of the {@link Operation}s have
* {@link java.util.concurrent.CompletionStage} parameters this can be
* especially valuable.
*
*
* {@link OperationGroup} also allows control of error response. By default if
* one {@link Operation} fails all subsequent {@link Operation}s are skipped.
* That’s not always right. Consider the mass insert case. Just because one
* insert fails doesn’t mean they should all fail. An independent
* {@link OperationGroup} does this; the failure of one {@link Operation} has no
* impact on the execution of the rest.
*
*
* As an {@link OperationGroup} is an {@link Operation} it must be submitted
* before its member {@link Operation}s are executed. Submitting an {@link OperationGroup}
* allows its member {@link Operation}s to be executed but does not prohibit more
* member {@link Operation}s from being submitted. Member {@link Operation}s may be
* submitted before and after the containing {@link OperationGroup} is submitted.
*
*
* The result of an {@link OperationGroup} depends on the results of its member
* {@link Operation}s. Therefore an {@link OperationGroup} must know when all
* member {@link Operation}s have been submitted. It cannot generate a
* result until all member {@link Operation}s are completed. Since member {@link Operation}s can be
* submitted after the {@link OperationGroup} has been submitted (see previous
* paragraph) submitting the containing {@link OperationGroup} is not sufficient to mark that all member
* {@link Operation}s have been submitted. Calling {@link OperationGroup#close} signals
* that all member Operations have been submitted. After close is called, no
* more member Operations may be submitted and the OperationGroup will complete
* when all member Operations are complete.
*
*
* An {@code OperationGroup} conceptually has a collection of member
* {@link Operation}s. When an {@code OperationGroup} is submitted it is placed
* in the collection of the {@code OperationGroup} of which it is a member. The
* member {@code OperationGroup} is executed according to the attributes of the
* {@code OperationGroup} of which it is a member. The member {@link Operation}s
* of an {@code OperationGroup} are executed according to the attributes of that
* {@code OperationGroup}.
*
*
* How an {@code OperationGroup} is executed depends on its attributes.
*
*
* If an {@code OperationGroup} has a condition and the value of that condition
* is {@link Boolean#TRUE} then execute the member {@link Operation}s as below.
* If it is {@link Boolean#FALSE} then the {@code OperationGroup} is completed
* with the value null. If the condition completed exceptionally then the
* {@code OperationGroup} is completed exceptionally with a
* {@link SqlSkippedException} that has that exception as its cause.
*
*
* If the {@code OperationGroup} is sequential the member {@link Operation}s are
* executed in the order they were submitted. If it is parallel, they may be
* executed in any order including simultaneously.
*
*
* If an {@code OperationGroup} is dependent and a member {@link Operation}
* completes exceptionally the remaining member {@link Operation}s in the
* collection are completed exceptionally with a {@link SqlSkippedException}
* that has the initial {@link Exception} as its cause and the
* {@code OperationGroup} is completed exceptionally with the initial
* {@link Exception}. A member {@link Operation} in-flight may either complete
* normally or be completed exceptionally but must complete one way or the
* other. [NOTE: Too strong?]
*
*
* The result of this {@code OperationGroup} is the result of collecting the
* results of its member {@link Operation}s. If the {@code OperationGroup} is
* dependent and one of its member {@link Operation}s completes exceptionally,
* the {@code OperationGroup} is completed exceptionally.
*
*
* An implementation of this class must be thread safe as result and error
* handlers running asynchronously may be accessing an {@code OperationGroup} in
* parallel with each other and with a user thread.
*
*
* ISSUE: Currently no way to create a nested {@code OperationGroup}. That is an
* intentional limitation but may be a simplification we can live with. Or
* not.
*
* @param The type of the result of the member {@link Operation}s
* @param The type of the collected results the member {@link Operation}s
*/
public interface OperationGroup extends Operation, AutoCloseable {
/**
* Mark this {@code OperationGroup} as parallel. If this method is not called
* the {@code OperationGroup} is sequential. If an {@code OperationGroup} is
* parallel, member {@link Operation}s may be executed in any order including
* in parallel. If an {@code OperationGroup} is sequential, the default,
* member {@link Operation}s are executed strictly in the order they are
* submitted.
*
* Note: There is no covariant override of this method in {@link Session} as
* there is only a small likelihood of needing it.
*
* @return this {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} has been
* submitted, any member {@link Operation}s have been created, or this method
* has been called previously
*/
public OperationGroup parallel();
/**
* Mark this {@code OperationGroup} as independent. If this method is not
* called the {@code OperationGroup} is dependent, the default. If an
* {@code OperationGroup} is independent then failure of one member
* {@link Operation} does not affect the execution of other member
* {@link Operation}s. If an {@code OperationGroup} is dependent then failure
* of one member {@link Operation} will cause all member {@link Operation}s
* remaining in the queue to be completed exceptionally with a
* {@link SqlSkippedException} with the cause set to the original exception.
*
* The result of this {@code OperationGroup}'s execution is the result of
* collecting the results of the member {@link Operation}s that complete
* normally.
*
* Note: There is no covariant override of this method in {@link Session} as
* there is only a small likelihood of needing it.
*
* @return this {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} has been
* submitted, any member {@link Operation}s have been created, or this method
* has been called previously
*/
public OperationGroup independent();
/**
* Define a condition that determines whether the member {@link Operation}s of
* this {@code OperationGroup} are executed or not. If and when this
* {@code OperationGroup} is executed then if the condition argument is
* completed with {@link Boolean#TRUE} the member {@link Operation}s are
* executed. If {@link Boolean#FALSE} or if it is completed exceptionally the
* member {@link Operation}s are not executed but are removed from the queue.
* After all member {@link Operation}s have been removed from the queue this
* {@code OperationGroup} is completed with {@code null}.
*
* Note: There is no covariant override of this method in Session as there is
* only a small likelihood of needing it.
*
* ISSUE: Should the member Operations be skipped or otherwise completed
* exceptionally?
*
* @param condition a {@link CompletionStage} the value of which determines
* whether this {@code OperationGroup} is executed or not
* @return this OperationGroup
* @throws IllegalStateException if this {@code OperationGroup} has been
* submitted, any member {@link Operation}s have been created, or this method
* has been called previously
*/
public OperationGroup conditional(CompletionStage condition);
/**
* Provides a {@link Collector} to reduce the results of the member
* {@link Operation}s. The result of this {@code OperationGroup} is the result
* of calling finisher on the final accumulated result.If the
* {@link Collector} is {@link Collector.Characteristics#UNORDERED} the member
* {@link Operation} results may be accumulated out of order.If the
* {@link Collector} is {@link Collector.Characteristics#CONCURRENT} then the
* member {@link Operation} results may be split into subsets that are reduced
* separately and then combined. If this {@code OperationGroup} is sequential,
* the characteristics of the {@link Collector} only affect how the results of
* the member {@link Operation}s are collected; the member {@link Operation}s
* are executed sequentially regardless. If this {@code OperationGroup} is
* parallel the characteristics of the {@link Collector} may influence the
* execution order of the member {@link Operation}s.
*
* The default value is
* {@code Collector.of(()->null, (a,t)->{}, (l,r)->null, a->null)}.
*
* @param c the Collector. Not null.
* @return This OperationGroup
* @throws IllegalStateException if called more than once or if this
* {@code OperationGroup} has been submitted
*/
public OperationGroup collect(Collector c);
/**
* Return a new member {@link PrimitiveOperation} that is never skipped.
* Skipping of member {@link Operation}s stops with a catchOperation and the
* subsequent {@link Operation} is executed normally. The value of a
* catchOperation is always null. Since a catchOperation is never completed
* exceptionally, it has no error handler or timeout.
*
* @return an {@link PrimitiveOperation} that is never skipped;
* @throws IllegalStateException if this {@code OperationGroup} is closed
* or if this {@code OperationGroup} is parallel or independent.
*/
public PrimitiveOperation catchOperation();
/**
* Creates and submits a catch Operation. Convenience method.
*
* @return this OperationGroup
* @throws IllegalStateException if this {@code OperationGroup} is closed
* or if this {@code OperationGroup} is parallel or independent.
*/
public default OperationGroup catchErrors() {
catchOperation().submit();
return this;
}
/**
* Return a new {@link ArrayRowCountOperation}.
*
* Usage Note: Frequently use of this method will require a type witness to
* enable correct type inferencing.
*
* session.<List<Integer>>arrayCountOperation(sql)
* .set ...
* .collect ...
* .submit ...
*
*
* @param the result type of the returned {@link ArrayRowCountOperation}
* @param sql SQL to be executed. Must return an update count.
* @return a new {@link ArrayRowCountOperation} that is a member of this
* {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public ArrayRowCountOperation arrayRowCountOperation(String sql);
/**
* Return a new {@link ParameterizedRowCountOperation}.
*
* @param the result type of the returned {@link RowCountOperation}
* @param sql SQL to be executed. Must return an update count.
* @return an new {@link ParameterizedRowCountOperation} that is a member of
* this {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public ParameterizedRowCountOperation rowCountOperation(String sql);
/**
* Return a new {@link Operation} for a SQL that doesn't return any result,
* for example DDL. The result of this Operation is always null.
*
* The result of the returned Operation must be Void but specifying that here
* causes problems.
*
* @param sql SQL for the {@link Operation}.
* @return a new {@link Operation} that is a member of this
* {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public Operation operation(String sql);
/**
* Return a new {@link OutOperation} that is a member {@link Operation} of
* this {@code OperationGroup}. The SQL must return a set of zero or more out
* parameters or function results.
*
* @param the result type of the returned {@link OutOperation}
* @param sql SQL for the {@link Operation}. Must return zero or more out
* parameters or function results.
* @return a new {@link OutOperation} that is a member of this
* {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public OutOperation outOperation(String sql);
/**
* Return a new {@link ParameterizedRowOperation} that is a member
* {@link Operation} of this {@code OperationGroup}.
*
* @param the type of the result of the returned
* {@link ParameterizedRowOperation}
* @param sql SQL for the {@link Operation}. Must return a row sequence.
* @return a new {@link ParameterizedRowOperation} that is a member of this
* {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public ParameterizedRowOperation rowOperation(String sql);
/**
* Return a new {@link ParameterizedRowPublisherOperation} that is a member
* {@link Operation} of this {@code OperationGroup}.
*
* @param the type of the result of the returned
* {@link ParameterizedRowPublisherOperation}
* @param sql SQL for the {@link Operation}. Must return a row sequence.
* @return a new {@link ParameterizedRowPublisherOperation} that is a member
* of this {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public ParameterizedRowPublisherOperation rowPublisherOperation(String sql);
/**
* Return a new {@link MultiOperation} that is a member {@link Operation} of
* this {@code OperationGroup}.
*
* @param the type of the result of the returned {@link MultiOperation}
* @param sql SQL for the {@link Operation}
* @return a new {@link MultiOperation} that is a member of this
* {@code OperationGroup}
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public MultiOperation multiOperation(String sql);
/**
* Return a new {@link Operation} that ends the database transaction. This
* {@link Operation} is a member of the {@code OperationGroup}. The
* transaction is ended with a commit unless the {@link TransactionCompletion}
* has been {@link TransactionCompletion#setRollbackOnly} in which case the
* transaction is ended with a rollback.
*
*
* An endTransaction Operation may be skipped. To insure that it will not be
* skipped it should immediately follow a catch Operation. All end transaction
* convenience methods do so.
*
* The type argument {@link S} of the containing {@code OperationGroup} must
* be a supertype of {@link TransactionOutcome}.
*
* @param trans the TransactionCompletion that determines whether the
* Operation does a database commit or a database rollback.
* @return an {@link Operation} that will end the database transaction.
* @throws IllegalStateException if this {@code OperationGroup} is closed
* or is parallel.
*/
public Operation endTransactionOperation(TransactionCompletion trans);
/**
* Convenience method that creates and submits a endTransaction
* {@link Operation} that commits by default but can be set to rollback by
* calling {@link TransactionCompletion#setRollbackOnly}. The endTransaction
* Operation is never skipped.
*
* @param trans the TransactionCompletion that determines whether the
* {@link Operation} is a database commit or a database rollback.
* @return a {@link CompletionStage} that is completed with the outcome of the
* transaction
* @throws IllegalStateException if this {@code OperationGroup} is closed
* or is parallel.
*/
public default CompletionStage commitMaybeRollback(TransactionCompletion trans) {
catchErrors();
return this.endTransactionOperation(trans).submit().getCompletionStage();
}
/**
* Return a new {@link LocalOperation} that is a member {@link Operation} of
* this {@code OperationGroup}.
*
* @param value type of the returned local {@link Operation}
* @return a LocalOperation
* @throws IllegalStateException if this {@code OperationGroup} is closed.
*/
public LocalOperation localOperation();
/**
* Supply a {@link Logger} for the implementation of this
* {@code OperationGroup} to use to log significant events. Exactly what
* events are logged, at what Level the events are logged and with what
* parameters is implementation dependent. All member {@link Operation}s of
* this {@code OperationGroup} will use the same {@link Logger} except a
* member {@code OperationGroup} that is supplied with a different
* {@link Logger} uses that {@link Logger}.
*
* Supplying a {@link Logger} configured with a
* {@link java.util.logging.MemoryHandler} with the
* {@link java.util.logging.MemoryHandler#pushLevel} set to
* {@link java.util.logging.Level#WARNING} will result in no log output in
* normal operation. In the event of an error the actions leading up to the
* error will be logged.
*
* Implementation Note: Implementations are encouraged to log the creation of
* this {@code OperationGroup} set to {@link java.util.logging.Level#INFO},
* the creation of member {@link Operation}s at the
* {@link java.util.logging.Level#CONFIG} level, and execution of member
* {@link Operation}s at the {@link java.util.logging.Level#FINE} level.
* Detailed information about the execution of member {@link Operation}s may
* be logged at the {@link java.util.logging.Level#FINER} and
* {@link java.util.logging.Level#FINEST} levels. Errors in the execution of
* user code should be logged at the {@link java.util.logging.Level#WARNING}
* Level. Errors in the implementation code should be logged at the
* {@link java.util.logging.Level#SEVERE} Level.
*
* @param logger used by the implementation to log significant events
* @return this {@code OperationGroup}
*/
public OperationGroup logger(Logger logger);
/**
* Returns a {@code String} enclosed in single quotes. Any occurrence of a
* single quote within the string will be replaced by two single quotes.
*
*
*
* Examples of the conversion:
*
* Value Result
*
*
* Hello 'Hello'
* G'Day 'G''Day'
* 'G''Day'
* '''G''''Day'''
* I'''M 'I''''''M'
*
*
*
*
*
*
* @implNote ADBA driver implementations may need to provide their own
* implementation of this method in order to meet the requirements of the
* underlying datasource.
* @param val a character string. Not null
* @return A string enclosed by single quotes with every single quote
* converted to two single quotes. Not null
* @throws NullPointerException if {@code val} is {@code null}
* @throws IllegalArgumentException if {@code val} cannot be enquoted
*/
default String enquoteLiteral(String val) {
return "'" + val.replace("'", "''") + "'";
}
/**
* Returns a SQL identifier. If {@code identifier} is a simple SQL identifier:
*
* - Return the original value if {@code alwaysQuote} is {@code false}
* - Return a delimited identifier if {@code alwaysQuote} is
* {@code true}
*
*
* If {@code identifier} is not a simple SQL identifier, {@code identifier}
* will be enclosed in double quotes if not already present. If the datasource
* does not support double quotes for delimited identifiers, the identifier
* should be enquoted by whatever mechanism the data source supports. If the
* datasource does not support delimited identifiers, an
* {@code IllegalArgumentException} should be thrown.
*
* A {@code IllegalArgumentException} will be thrown if {@code identifier}
* contains any characters invalid in a delimited identifier or the identifier
* length is invalid for the datasource.
*
* @implSpec The default implementation uses the following criteria to
* determine a valid simple SQL identifier:
*
* - The string is not enclosed in double quotes
* - The first character is an alphabetic character from a through z, or
* from A through Z
* - The name only contains alphanumeric characters or the character
* "_"
*
*
* The default implementation will throw a {@code SQLException} if:
*
* - {@code identifier} contains a {@code null} character or double quote
* and is not a simple SQL identifier.
* - The length of {@code identifier} is less than 1 or greater than 128
* characters
*
*
*
* Examples of the conversion:
*
*
* identifier
* alwaysQuote
* Result
*
*
*
* Hello
* false
* Hello
*
*
* Hello
* true
* "Hello"
*
*
* G'Day
* false
* "G'Day"
*
*
* "Bruce Wayne"
* false
* "Bruce Wayne"
*
*
* "Bruce Wayne"
* true
* "Bruce Wayne"
*
*
* GoodDay$
* false
* "GoodDay$"
*
*
* Hello"World
* false
* IllegalArgumentException
*
*
* "Hello"World"
* false
* IllegalArgumentException
*
*
*
*
* @implNote ADBA driver implementations may need to provide their own
* implementation of this method in order to meet the requirements of the
* underlying datasource.
* @param identifier a SQL identifier. Not null
* @param alwaysQuote indicates if a simple SQL identifier should be returned
* as a quoted identifier
* @return A simple SQL identifier or a delimited identifier. Not null
* @throws NullPointerException if identifier is {@code null}
* @throws IllegalArgumentException if {@code identifier} can not be converted
* to a valid identifier
*/
default String enquoteIdentifier(String identifier, boolean alwaysQuote) {
int len = identifier.length();
if (len < 1 || len > 128) {
throw new IllegalArgumentException("Invalid name");
}
if (Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]*").matcher(identifier).matches()) {
return alwaysQuote ? "\"" + identifier + "\"" : identifier;
}
if (identifier.matches("^\".+\"$")) {
identifier = identifier.substring(1, len - 1);
}
if (Pattern.compile("[^\u0000\"]+").matcher(identifier).matches()) {
return "\"" + identifier + "\"";
}
else {
throw new IllegalArgumentException("Invalid name");
}
}
/**
* Retrieves whether {@code identifier} is a simple SQL identifier.
*
* @implSpec The default implementation uses the following criteria to
* determine a valid simple SQL identifier:
*
* - The string is not enclosed in double quotes
* - The first character is an alphabetic character from a through z, or
* from A through Z
* - The string only contains alphanumeric characters or the character
* "_"
* - The string is between 1 and 128 characters in length inclusive
*
*
*
*
* Examples of the conversion:
*
*
* identifier
* Simple Identifier
*
*
*
*
* Hello
* true
*
*
* G'Day
* false
*
*
* "Bruce Wayne"
* false
*
*
* GoodDay$
* false
*
*
* Hello"World
* false
*
*
* "Hello"World"
* false
*
*
*
*
* @implNote ADBA driver implementations may need to provide their own
* implementation of this method in order to meet the requirements of the
* underlying datasource.
* @param identifier a SQL identifier. Not null
* @return true if a simple SQL identifier, false otherwise
* @throws NullPointerException if identifier is {@code null}
*/
default boolean isSimpleIdentifier(String identifier) {
int len = identifier.length();
return len >= 1 && len <= 128
&& Pattern.compile("[\\p{Alpha}][\\p{Alnum}_]*").matcher(identifier).matches();
}
/**
* Returns a {@code String} representing a National Character Set Literal
* enclosed in single quotes and prefixed with a upper case letter N. Any
* occurrence of a single quote within the string will be replaced by two
* single quotes.
*
*
*
* Examples of the conversion:
*
*
* Value
* Result
*
*
*
* Hello N'Hello'
* G'Day N'G''Day'
* 'G''Day'
* N'''G''''Day'''
* I'''M N'I''''''M'
* N'Hello' N'N''Hello'''
*
*
*
*
*
* @implNote ADBA driver implementations may need to provide their own
* implementation of this method in order to meet the requirements of the
* underlying datasource. An implementation of enquoteNCharLiteral may accept
* a different set of characters than that accepted by the same drivers
* implementation of enquoteLiteral.
* @param val a character string. Not null
* @return the result of replacing every single quote character in the
* argument by two single quote characters where this entire result is then
* prefixed with 'N'. Not null.
* @throws NullPointerException if {@code val} is {@code null}
* @throws IllegalArgumentException if {@code val} cannot be enquoted
*/
default String enquoteNCharLiteral(String val) {
return "N'" + val.replace("'", "''") + "'";
}
/**
* Allow this {@code OperationGroup} to be completed and removed from the
* queue once all of its member {@link Operation}s have been completed. After
* this method is called no additional member {@link Operation}s can be
* submitted. Once all member {@link Operation}s have been removed from the
* queue this {@code OperationGroup} will be completed and removed from the
* queue.
*
* {@inheritDoc}
*/
@Override
public void close();
// Covariant overrides
/**
* {@inheritDoc}
*
* @return this {@code OperationGroup}
*/
@Override
public OperationGroup timeout(Duration minTime);
/**
* {@inheritDoc}
*
* @return this {@code OperationGroup}
*/
@Override
public OperationGroup onError(Consumer handler);
}