diff --git a/java/AoJ/README.md b/java/AoJ/README.md new file mode 100644 index 00000000..64b3089b --- /dev/null +++ b/java/AoJ/README.md @@ -0,0 +1,55 @@ +# AoJ: ADBA over JDBC + +ADBA is Asynchronous Database Access, a non-blocking database access api that Oracle is proposing as a Java standard. ADBA was announced at [JavaOne 2016](https://static.rainfocus.com/oracle/oow16/sess/1461693351182001EmRq/ppt/CONF1578%2020160916.pdf) and presented again at [JavaOne 2017](http://www.oracle.com/technetwork/database/application-development/jdbc/con1491-3961036.pdf). The ADBA source is available for download from the [OpenJDK sandbox]( +http://hg.openjdk.java.net/jdk/sandbox/file/9d3b0eb749a9/src/jdk.incubator.adba) as part of the OpenJDK project. You can get involved in the ADBA specification effort by following the [JDBC Expert Group mailing list](http://mail.openjdk.java.net/pipermail/jdbc-spec-discuss/). + +Reading a bunch of JavaDoc and interfaces can be interesting, but it is not nearly as engaging as having actual running code to play with. To that end, we have uploaded the beginnings of an implementation of ADBA running over standard JDBC, AoJ. AoJ is available for download from [GitHub](https://github.com/oracle/oracle-db-examples/tree/master/java/AoJ) under the Apache license. It should run with any reasonably standard compliant JDBC driver. + +AoJ implements only a small part of ADBA, but it is enough to write interesting code. It provides partial implementations of DataSourceFactory, DataSource, Connection, OperationGroup, RowOperation, CountOperation, Transaction and others. These implementations are not complete but there is enough there to write interesting database programs. The code that is there is untested, but it does work to some extent. The saving grace is that you can download the source and improve it: add new features, fix bugs, try out alternate implementations. + +Oracle is not proposing AoJ as an open source project. However, because AoJ is released under the Apache license, the Java community can fork the code and create a true open source project with this upload as a base. Oracle developers may contribute when we have time, but this would have to be a Java community effort. + +We could have held this code back and worked on it longer. Instead we thought it better to get it to the community as soon as we could. We hope that you agree. + +## Sample Code + +The following test case should give you some idea of what AoJ can do. It uses the scott/tiger [schema](https://github.com/oracle/dotnet-db-samples/blob/master/schemas/scott.sql). It should run with any JDBC driver connecting to a database with the scott schema. + +`````` public void transactionSample() { + DataSourceFactory factory = DataSourceFactory.forName("com.oracle.adbaoverjdbc.DataSourceFactory"); + try (DataSource ds = factory.builder() + .url(URL) + .username(“scott") + .password(“tiger") + .build(); + Connection conn = ds.getConnection(t -> System.out.println("ERROR: " + t.getMessage()))) { + Transaction trans = conn.transaction(); + CompletionStage idF = conn.rowOperation("select empno, ename from emp where ename = ? for update") + .set("1", "CLARK", AdbaType.VARCHAR) + .collect(Collector.of( + () -> new int[1], + (a, r) -> {a[0] = r.get("empno", Integer.class); }, + (l, r) -> null, + a -> a[0]) + ) + .submit() + .getCompletionStage(); + conn.countOperation("update emp set deptno = ? where empno = ?") + .set("1", 50, AdbaType.INTEGER) + .set("2", idF, AdbaType.INTEGER) + .apply(c -> { + if (c.getCount() != 1L) { + trans.setRollbackOnly(); + throw new SqlException("updated wrong number of rows", null, null, -1, null, -1); + } + return c.getCount(); + }) + .onError(t -> t.printStackTrace()) + .submit(); + conn.catchErrors(); + conn.commitMaybeRollback(trans); + } + ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.MINUTES); + } + + diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Connection.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Connection.java new file mode 100644 index 00000000..6e803b73 --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Connection.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.AdbaConnectionProperty; +import jdk.incubator.sql2.Connection.Lifecycle; +import jdk.incubator.sql2.ConnectionProperty; +import jdk.incubator.sql2.Operation; +import jdk.incubator.sql2.ShardingKey; +import jdk.incubator.sql2.SqlException; +import jdk.incubator.sql2.TransactionOutcome; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; + +/** + * Connection is a subclass of OperationGroup. The member Operation stuff is mostly + * inherited from OperationGroup. There are a couple of differences. First the + * predecessor for all Connections is an already completed CompletableFuture, + * ROOT. Since ROOT is completed a Connection will begin executing as soon as it + * is submitted. Second, a Connection is not really a member of an OperationGroup + * so the code that handles submitting the Connection is a little different from + * OperationGroup. + * + * A Connection is also contains a java.sql.Connection and has methods to execute + * some JDBC actions. It might be a good idea to move the java.sql.Connection and + * associated actions to a separate class. + */ +class Connection extends OperationGroup implements jdk.incubator.sql2.Connection { + + // STATIC + protected static final CompletionStage ROOT = CompletableFuture.completedFuture(null); + + static jdk.incubator.sql2.Connection newConnection(DataSource ds, + Map properties) { + return new Connection(ds, properties); + } + + // FIELDS + private Lifecycle connectionLifecycle = Lifecycle.NEW; + private final Set lifecycleListeners; + private final DataSource dataSource; + private final Map properties; + + private java.sql.Connection jdbcConnection; + + private final Executor executor; + private CompletableFuture connectionCF; + + // CONSTRUCTORS + private Connection(DataSource ds, + Map properties) { + super(null, null); // hack as _this_ not allowed. See SimpleOperation constructor + this.lifecycleListeners = new HashSet<>(); + dataSource = ds; + this.properties = properties; + ConnectionProperty execProp = AdbaConnectionProperty.EXECUTOR; + executor = (Executor) properties.getOrDefault(execProp, execProp.defaultValue()); + } + + // PUBLIC + @Override + public Operation connectOperation() { + if (! isHeld()) { + throw new IllegalStateException("TODO"); + } + return com.oracle.adbaoverjdbc.SimpleOperation.newOperation(this, this, this::jdbcConnect); + } + + @Override + public Operation validationOperation(Validation depth) { + if (! isHeld()) { + throw new IllegalStateException("TODO"); + } + return com.oracle.adbaoverjdbc.SimpleOperation.newOperation(this, this, op -> jdbcValidate(op, depth)); + } + + @Override + public Operation closeOperation() { + if (! isHeld()) { + throw new IllegalStateException("TODO"); + } + return com.oracle.adbaoverjdbc.UnskippableOperation.newOperation(this, this, this::jdbcClose); //TODO cannot be skipped + } + + @Override + public jdk.incubator.sql2.OperationGroup operationGroup() { + if (!isHeld()) { + throw new IllegalStateException("TODO"); + } + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public Transaction transaction() { + if (! isHeld()) { + throw new IllegalStateException("TODO"); + } + return Transaction.createTransaction(this); + } + + @Override + public Connection registerLifecycleListener(ConnectionLifecycleListener listener) { + if (!connectionLifecycle.isActive()) { + throw new IllegalStateException("TODO"); + } + lifecycleListeners.add(listener); + return this; + } + + @Override + public Connection deregisterLifecycleListener(ConnectionLifecycleListener listener) { + if (!connectionLifecycle.isActive()) { + throw new IllegalStateException("TODO"); + } + lifecycleListeners.remove(listener); + return this; + } + + @Override + public Lifecycle getConnectionLifecycle() { + return connectionLifecycle; + } + + @Override + public jdk.incubator.sql2.Connection abort() { + setLifecycle(connectionLifecycle.abort()); + this.closeImmediate(); + return this; + } + + @Override + public Map getProperties() { + Map map = new HashMap<>(properties.size()); + properties.forEach((k, v) -> { + if (!k.isSensitive()) { + map.put(k, v); + } + }); + return map; + } + + @Override + public ShardingKey.Builder shardingKeyBuilder() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public jdk.incubator.sql2.Connection activate() { + setLifecycle(connectionLifecycle.activate()); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public jdk.incubator.sql2.Connection deactivate() { + setLifecycle(connectionLifecycle.deactivate()); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + + + + // INTERNAL + protected Connection setLifecycle(Lifecycle next) { + Lifecycle previous = connectionLifecycle; + connectionLifecycle = next; + if (previous != next) { + lifecycleListeners.stream().forEach(l -> l.lifecycleEvent(this, previous, next)); + } + return this; + } + + Connection closeImmediate() { + try { + if (jdbcConnection != null && !jdbcConnection.isClosed()) { + setLifecycle(connectionLifecycle.abort()); + jdbcConnection.abort(executor); // Connection.abort is not supposed to hang + //TODO should call connectionLifecycle.close() when abort completes. + } + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1); + } + finally { + dataSource.deregisterConnection(this); + } + return this; + } + + @Override + protected Executor getExecutor() { + return executor; + } + + @Override + jdk.incubator.sql2.Submission submit(com.oracle.adbaoverjdbc.Operation op) { + if (op == this) { + // submitting the Connection OperationGroup + connectionCF = (CompletableFuture)attachErrorHandler(op.follows(ROOT, getExecutor())); + return com.oracle.adbaoverjdbc.Submission.submit(this::cancel, connectionCF); + } + else { + return super.submit(op); + } + } + + + + + // JDBC operations. These are all blocking + + private Void jdbcConnect(com.oracle.adbaoverjdbc.Operation op) { + try { + Properties info = (Properties) ((Properties) properties.get(JdbcConnectionProperties.JDBC_CONNECTION_PROPERTIES)).clone(); + info.setProperty("user", (String) properties.get(AdbaConnectionProperty.USER)); + info.setProperty("password", (String) properties.get(AdbaConnectionProperty.PASSWORD)); + String url = (String) properties.get(AdbaConnectionProperty.URL); + System.out.println("DriverManager.getConnection(\"" + url + "\", " + info +")"); //DEBUG + jdbcConnection = DriverManager.getConnection(url, info); + jdbcConnection.setAutoCommit(false); + setLifecycle(Connection.Lifecycle.OPEN); + return null; + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1); + } + } + + private Void jdbcValidate(com.oracle.adbaoverjdbc.Operation op, + Validation depth) { + try { + switch (depth) { + case COMPLETE: + case SERVER: + int timeoutSeconds = (int) (op.getTimeoutMillis() / 1000L); + System.out.println("Connection.isValid(" + timeoutSeconds + ")"); //DEBUG + if (!jdbcConnection.isValid(timeoutSeconds)) { + throw new SqlException("validation failure", null, null, -1, null, -1); + } + break; + case NETWORK: + case SOCKET: + case LOCAL: + case NONE: + System.out.println("Connection.isClosed"); //DEBUG + if (jdbcConnection.isClosed()) { + throw new SqlException("validation failure", null, null, -1, null, -1); + } + } + return null; + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1); + } + } + + + protected T jdbcExecute(com.oracle.adbaoverjdbc.Operation op, String sql) { + try (java.sql.Statement stmt = jdbcConnection.createStatement()) { + int timeoutSeconds = (int) (op.getTimeoutMillis() / 1000L); + if (timeoutSeconds < 0) stmt.setQueryTimeout(timeoutSeconds); + System.out.println("Statement.execute(\"" + sql + "\")"); //DEBUG + stmt.execute(sql); + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sql, -1); + } + return null; + } + + private Void jdbcClose(com.oracle.adbaoverjdbc.Operation op) { + try { + setLifecycle(connectionLifecycle.close()); + if (jdbcConnection != null) { + System.out.println("Connection.close"); //DEBUG + jdbcConnection.close(); + } + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1); + } + finally { + closeImmediate(); + setLifecycle(connectionLifecycle.closed()); + } + return null; + } + + PreparedStatement prepareStatement(String sqlString) throws SQLException { + System.out.println("Connection.prepareStatement(\"" + sqlString + "\")"); //DEBUG + return jdbcConnection.prepareStatement(sqlString); + } + + TransactionOutcome jdbcEndTransaction(SimpleOperation op, Transaction trans) { + try { + if (trans.endWithCommit(this)) { + System.out.println("commit"); //DEBUG + jdbcConnection.commit(); + return TransactionOutcome.COMMIT; + } + else { + System.out.println("rollback"); //DEBUG + jdbcConnection.rollback(); + return TransactionOutcome.ROLLBACK; + } + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1); + } + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/ConnectionBuilder.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/ConnectionBuilder.java new file mode 100644 index 00000000..639fe58c --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/ConnectionBuilder.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.ConnectionProperty; +import java.util.HashMap; +import java.util.Map; + +/** + * A builder to create an AoJ connection. The AoJ connection creates a JDBC + * connection by calling java.sql.DriverManager.getConnection with the following + * user provided ConnectionProperty values: + * + *
+ *
URL
+ *
passed as the url argument to getConnection
+ *
USER
+ *
added to the JDBC_CONNECTION_PROPERTIES as the "user" property.
+ *
PASSWORD
+ *
added to the JDBC_CONNECTION_PROPERTIES as the "password" property
+ *
JDBC_CONNECTION_PROPERTIES
+ *
a java.util.Properties passed as the info argument to getConnection
+ *
+ */ +class ConnectionBuilder implements jdk.incubator.sql2.Connection.Builder { + + /** + * + * @param ds + * @param defaultProperties. Captured + * @param requiredProperties. Captured + * @return + */ + static ConnectionBuilder newConnectionBuilder(DataSource ds, + Map defaultProperties, + Map requiredProperties) { + return new ConnectionBuilder(ds, defaultProperties, requiredProperties); + } + + private boolean isBuilt = false; + private final DataSource dataSource; + private final Map defaultProperties; + private final Map requiredProperties; + + /** + * + * @param ds + * @param defaultConnectionProperties + * @param specifiedConnectionProperties + */ + private ConnectionBuilder(DataSource ds, + Map defaultConnectionProperties, + Map specifiedConnectionProperties) { + super(); + dataSource = ds; + defaultProperties = new HashMap(defaultConnectionProperties); + requiredProperties = new HashMap(specifiedConnectionProperties); + } + + @Override + public jdk.incubator.sql2.Connection.Builder property(ConnectionProperty property, Object value) { + if (isBuilt) { + throw new IllegalStateException("TODO"); + } + if (requiredProperties.containsKey(property)) { + throw new IllegalArgumentException("cannot override required properties"); + } + if (!property.validate(value)) { + throw new IllegalArgumentException("TODO"); + } + requiredProperties.put(property, value); + return this; + } + + @Override + public jdk.incubator.sql2.Connection build() { + if (isBuilt) { + throw new IllegalStateException("TODO"); + } + isBuilt = true; + // replace default values with specified values where provided + // otherwise use defaults + defaultProperties.putAll(requiredProperties); + return Connection.newConnection(dataSource, defaultProperties); + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/CountOperation.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/CountOperation.java new file mode 100644 index 00000000..19e1e6ea --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/CountOperation.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.ParameterizedCountOperation; +import jdk.incubator.sql2.Result; +import jdk.incubator.sql2.RowOperation; +import jdk.incubator.sql2.SqlException; +import jdk.incubator.sql2.SqlType; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.time.Duration; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * + * @param + */ +class CountOperation extends ParameterizedOperation + implements ParameterizedCountOperation { + + static private final Function DEFAULT_PROCESSOR = c -> null; + + /** + * Factory method to create CountOperations. + * + * @param the type of the value of the CountOperation + * @param conn the Connection the CountOperation belongs to + * @param grp the GroupOperation the CountOperation is a member of + * @param sql the SQL string to execute. Must return a count. + * @return a new CountOperation that will execute sql. + */ + static CountOperation newCountOperation(Connection conn, OperationGroup grp, String sql) { + return new CountOperation<>(conn, grp, sql); + } + + // attributes + private final String sqlString; + private Function countProcessor; + + PreparedStatement jdbcStatement; + + CountOperation(Connection conn, OperationGroup operationGroup, String sql) { + super(conn, operationGroup); + countProcessor = DEFAULT_PROCESSOR; + sqlString = sql; + } + + @Override + public RowOperation returning(String... keys) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public CountOperation apply(Function processor) { + if (isImmutable() || countProcessor != DEFAULT_PROCESSOR) throw new IllegalStateException("TODO"); + if (processor == null) throw new IllegalArgumentException("TODO"); + countProcessor = processor; + return this; + } + + @Override + CompletionStage follows(CompletionStage predecessor, Executor executor) { + predecessor = attachFutureParameters(predecessor); + return predecessor + .thenApplyAsync(this::executeQuery, executor); + } + + /** + * Execute the SQL query, process the returned count, and return the result of + * processing the returned count. + * + * @param ignore not used + * @return the result of processing the count + */ + private T executeQuery(Object ignore) { + checkCanceled(); + try { + jdbcStatement = connection.prepareStatement(sqlString); + setParameters.forEach((String k, ParameterValue v) -> { + v.set(jdbcStatement, k); + }); + System.out.println("executeUpdate(\"" + sqlString + "\")"); + long c = jdbcStatement.executeLargeUpdate(); + return countProcessor.apply(new Count(c)); + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sqlString, -1); + } + } + + // Covariant overrides + + @Override + public CountOperation set(String id, Object value) { + return (CountOperation)super.set(id, value); + } + + @Override + public CountOperation set(String id, Object value, SqlType type) { + return (CountOperation)super.set(id, value, type); + } + + @Override + public CountOperation set(String id, CompletionStage source) { + return (CountOperation)super.set(id, source); + } + + @Override + public CountOperation set(String id, CompletionStage source, SqlType type) { + return (CountOperation)super.set(id, source, type); + } + + @Override + public CountOperation timeout(Duration minTime) { + return (CountOperation)super.timeout(minTime); + } + + @Override + public CountOperation onError(Consumer handler) { + return (CountOperation)super.onError(handler); + } + + /** + * Represents the result of a SQL execution that is an update count. + * + * ISSUE: It's not obvious this type is more valuable than just using + * java.lang.Long. Result.Count exists to clearly express that the input arg + * to the processor Function is a count. Could rely on documentation but this + * seems like it might be important enough to capture in the type system. There + * also may be non-numeric return values that Result.Count could express, eg + * success but number unknown. + */ + static class Count implements Result.Count { + + private long count = -1; + + private Count(long c) { + count = c; + } + + @Override + public long getCount() { + return count; + } + + } +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSource.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSource.java new file mode 100644 index 00000000..0543d9ae --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSource.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.ConnectionProperty; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Bare bones DataSource. No support for Connection caching. + * + */ +class DataSource implements jdk.incubator.sql2.DataSource { + + static DataSource newDataSource(Map defaultConnectionProperties, + Map requiredConnectionProperties) { + return new DataSource(defaultConnectionProperties, requiredConnectionProperties); + } + + protected final Map defaultConnectionProperties; + protected final Map requiredConnectionProperties; + + protected final Set openConnections = new HashSet<>(); + + protected DataSource(Map defaultProps, + Map requiredProps) { + super(); + defaultConnectionProperties = defaultProps; + requiredConnectionProperties = requiredProps; + } + + @Override + public Connection.Builder builder() { + return ConnectionBuilder.newConnectionBuilder(this, defaultConnectionProperties, requiredConnectionProperties); + } + + @Override + public void close() { + openConnections.stream().forEach( c -> c.close() ); + } + + + + DataSource registerConnection(Connection c) { + openConnections.add(c); + return this; + } + + DataSource deregisterConnection(Connection c) { + openConnections.remove(c); + return this; + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSourceBuilder.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSourceBuilder.java new file mode 100644 index 00000000..12e8f1e4 --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSourceBuilder.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.ConnectionProperty; +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +class DataSourceBuilder implements jdk.incubator.sql2.DataSource.Builder { + + static DataSourceBuilder newDataSourceBuilder() { + return new DataSourceBuilder(); + } + + protected boolean isBuilt = false; + + /** + * defaultConnectionProperties can be overridden by a ConnectionBuilder + */ + Map defaultConnectionProperties = new HashMap<>(); + + /** + * it is an error if a ConnectionBuilder tries to override requiredConnectionProperties + */ + Map requiredConnectionProperties = new HashMap<>(); + + @Override + public jdk.incubator.sql2.DataSource.Builder defaultConnectionProperty(ConnectionProperty property, Object value) { + if (isBuilt) { + throw new IllegalStateException("TODO"); + } + if (defaultConnectionProperties.containsKey(property)) { + throw new IllegalArgumentException("cannot set a default multiple times"); + } + if (requiredConnectionProperties.containsKey(property)) { + throw new IllegalArgumentException("cannot set a default that is already required"); + } + if (!property.validate(value)) { + throw new IllegalArgumentException("TODO"); + } + defaultConnectionProperties.put(property, value); + return this; + } + + @Override + public jdk.incubator.sql2.DataSource.Builder connectionProperty(ConnectionProperty property, Object value) { + if (isBuilt) { + throw new IllegalStateException("TODO"); + } + if (defaultConnectionProperties.containsKey(property)) { + throw new IllegalArgumentException("cannot set a required prop that has a default"); + } + if (requiredConnectionProperties.containsKey(property)) { + throw new IllegalArgumentException("cannot set a required prop multiple times"); + } + if (!property.validate(value)) { + throw new IllegalArgumentException("TODO"); + } + requiredConnectionProperties.put(property, value); + return this; + } + + @Override + public jdk.incubator.sql2.DataSource build() { + if (isBuilt) { + throw new IllegalStateException("cannot build more than once. All objects are use-once"); + } + isBuilt = true; + return DataSource.newDataSource(defaultConnectionProperties, requiredConnectionProperties); + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSourceFactory.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSourceFactory.java new file mode 100644 index 00000000..044a9b1b --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/DataSourceFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.DataSource; + +/** + * + */ +public class DataSourceFactory implements jdk.incubator.sql2.DataSourceFactory { + + @Override + public DataSource.Builder builder() { + return DataSourceBuilder.newDataSourceBuilder(); + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/JdbcConnectionProperties.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/JdbcConnectionProperties.java new file mode 100644 index 00000000..d285e279 --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/JdbcConnectionProperties.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import java.util.Properties; + +/** + * An ADBA ConnectionProperty that specifies a set of JDBC connection properties. + * Its value is a java.util.Properties. This value is passed as the info argument + * when creating a java.sql.Connection. + * + */ +public class JdbcConnectionProperties implements jdk.incubator.sql2.ConnectionProperty { + + public static final JdbcConnectionProperties JDBC_CONNECTION_PROPERTIES + = new JdbcConnectionProperties(); + + private JdbcConnectionProperties() { + } + + @Override + public String name() { + return "JDBC_CONNECTION_PROPERTIES"; + } + + @Override + public Class range() { + return Properties.class; + } + + @Override + public Object defaultValue() { + return new Properties(); + } + + @Override + public boolean isSensitive() { + return false; + } + + @Override + public String toString() { + return name(); + } +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Operation.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Operation.java new file mode 100644 index 00000000..6064854e --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Operation.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.AdbaType; +import jdk.incubator.sql2.SqlSkippedException; +import jdk.incubator.sql2.SqlType; +import jdk.incubator.sql2.Submission; +import java.math.BigInteger; +import java.sql.JDBCType; +import java.sql.SQLType; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * An Operation collects the various properties of the request for work, then + * constructs one or more CompletionStages that will do the work of the + * Operation. Finally it connects the CompletionStage(s) to result + * CompletionStage of the preceeding Operation. + * + */ +abstract class Operation implements jdk.incubator.sql2.Operation { + + private static final Map CLASS_TO_JDBCTYPE = new HashMap<>(20); + static { + try { + CLASS_TO_JDBCTYPE.put(Boolean.class, JDBCType.BOOLEAN); + CLASS_TO_JDBCTYPE.put(BigInteger.class, JDBCType.BIGINT); + CLASS_TO_JDBCTYPE.put(Class.forName("[B"), JDBCType.BINARY); + CLASS_TO_JDBCTYPE.put(Boolean.class, JDBCType.BIT); + CLASS_TO_JDBCTYPE.put(Boolean.class, JDBCType.BOOLEAN); + CLASS_TO_JDBCTYPE.put(Character.class, JDBCType.CHAR); + CLASS_TO_JDBCTYPE.put(LocalDate.class, JDBCType.DATE); + CLASS_TO_JDBCTYPE.put(Double.class, JDBCType.DOUBLE); + CLASS_TO_JDBCTYPE.put(Float.class, JDBCType.FLOAT); + CLASS_TO_JDBCTYPE.put(Integer.class, JDBCType.INTEGER); + CLASS_TO_JDBCTYPE.put(Float.class, JDBCType.REAL); + CLASS_TO_JDBCTYPE.put(Short.class, JDBCType.SMALLINT); + CLASS_TO_JDBCTYPE.put(LocalTime.class, JDBCType.TIME); + CLASS_TO_JDBCTYPE.put(LocalDateTime.class, JDBCType.TIMESTAMP); + CLASS_TO_JDBCTYPE.put(OffsetTime.class, JDBCType.TIME_WITH_TIMEZONE); + CLASS_TO_JDBCTYPE.put(OffsetDateTime.class, JDBCType.TIMESTAMP_WITH_TIMEZONE); + CLASS_TO_JDBCTYPE.put(Byte.class, JDBCType.TINYINT); + CLASS_TO_JDBCTYPE.put(Class.forName("[byte"), JDBCType.VARBINARY); + CLASS_TO_JDBCTYPE.put(String.class, JDBCType.VARCHAR); + } + catch (ClassNotFoundException ex) { /* should never happen */ } + } + + private static final Map ADBATYPE_TO_JDBCTYPE = new HashMap<>(40); + static { + ADBATYPE_TO_JDBCTYPE.put(AdbaType.ARRAY, JDBCType.ARRAY); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.BIGINT, JDBCType.BIGINT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.BINARY, JDBCType.BINARY); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.BIT, JDBCType.BIT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.BOOLEAN, JDBCType.BOOLEAN); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.CHAR, JDBCType.CHAR); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.CLOB, JDBCType.CLOB); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.DATALINK, JDBCType.DATALINK); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.DATE, JDBCType.DATE); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.DECIMAL, JDBCType.DECIMAL); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.DISTINCT, JDBCType.DISTINCT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.DOUBLE, JDBCType.DOUBLE); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.FLOAT, JDBCType.FLOAT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.INTEGER, JDBCType.INTEGER); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.JAVA_OBJECT, JDBCType.JAVA_OBJECT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.LONGNVARCHAR, JDBCType.LONGNVARCHAR); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.LONGVARBINARY, JDBCType.LONGVARBINARY); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.LONGVARCHAR, JDBCType.LONGVARBINARY); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.NCHAR, JDBCType.NCHAR); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.NCLOB, JDBCType.NCLOB); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.NULL, JDBCType.NULL); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.NUMERIC, JDBCType.NUMERIC); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.NVARCHAR, JDBCType.NVARCHAR); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.OTHER, JDBCType.OTHER); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.REAL, JDBCType.REAL); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.REF, JDBCType.REF); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.REF_CURSOR, JDBCType.REF_CURSOR); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.ROWID, JDBCType.ROWID); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.SMALLINT, JDBCType.SMALLINT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.SQLXML, JDBCType.SQLXML); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.STRUCT, JDBCType.STRUCT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.TIME, JDBCType.TIME); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.TIMESTAMP, JDBCType.TIMESTAMP); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.TIME_WITH_TIME_ZONE, JDBCType.TIME_WITH_TIMEZONE); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.TIMESTAMP_WITH_TIME_ZONE, JDBCType.TIMESTAMP_WITH_TIMEZONE); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.TINYINT, JDBCType.TINYINT); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.VARBINARY, JDBCType.VARBINARY); + ADBATYPE_TO_JDBCTYPE.put(AdbaType.VARCHAR, JDBCType.VARCHAR); + } + + /** + * Find the default SQLType to represent a Java type. + * + * @param c a Java type + * @return the default SQLType to represent the Java type + */ + static SQLType toSQLType(Class c) { + SQLType s = CLASS_TO_JDBCTYPE.get(c); + if (s == null) { + throw new UnsupportedOperationException("Not supported yet."); + } + return s; + } + + /** + * Return the java.sql.SQLType corresponding to the jdk.incubator.sql2.SqlType. + * + * @param t an ADBA type + * @return a JDBC type + */ + static SQLType toSQLType(SqlType t) { + SQLType s = ADBATYPE_TO_JDBCTYPE.get(t); + if (s == null) { + throw new UnsupportedOperationException("Not supported yet."); + } + return s; + } + + static Throwable unwrapException(Throwable ex) { + return ex instanceof CompletionException ? ex.getCause() : ex; + } + + // attributes + protected Duration timeout = null; + protected Consumer errorHandler = null; + + // internal state + protected final Connection connection; + protected final OperationGroup group; + protected OperationLifecycle operationLifecycle = OperationLifecycle.MUTABLE; + + Operation(Connection conn, OperationGroup operationGroup) { + // passing null for connection and operationGroup is a hack. It is not + // possible to pass _this_ to a super constructor so we define null to mean + // _this_. Yuck. Only used by Connection. + connection = conn == null ? (Connection) this : conn; + group = operationGroup == null ? (OperationGroup) this : operationGroup; + } + + @Override + public Operation onError(Consumer handler) { + if (isImmutable() || errorHandler != null) { + throw new IllegalStateException("TODO"); + } + if (handler == null) { + throw new IllegalArgumentException("TODO"); + } + errorHandler = handler; + return this; + } + + @Override + public Operation timeout(Duration minTime) { + if (isImmutable() || timeout != null) { + throw new IllegalStateException("TODO"); + } + if (minTime == null || minTime.isNegative() || minTime.isZero()) { + throw new IllegalArgumentException("TODO"); + } + timeout = minTime; + return this; + } + + @Override + public Submission submit() { + if (isImmutable()) { + throw new IllegalStateException("TODO"); + } + immutable(); + return group.submit(this); + } + + /** + * Returns true if this Operation is immutable. An Operation is immutable if + * it has been submitted. Held OperationGroups are an exception. + * + * @return return true if immutable + */ + boolean isImmutable() { + return operationLifecycle.isImmutable(); + } + + protected Operation immutable() { + operationLifecycle = OperationLifecycle.RELEASED; + return this; + } + + long getTimeoutMillis() { + if (timeout == null) { + return 0L; + } + else { + return timeout.get(ChronoUnit.MILLIS); + } + } + + protected Executor getExecutor() { + return connection.getExecutor(); + } + + /** + * Attaches the CompletableFuture that starts this Operation to the tail and + * return a CompletableFuture that represents completion of this Operation. + * The returned CompletableFuture may not be directly attached to the tail, + * but completion of the tail should result in completion of the returned + * CompletableFuture. (Note: Not quite true for OperationGroups submitted by + * calling submitHoldingForMoreMembers. While the returned CompletableFuture + * does depend on the tail, it also depends on user code calling + * releaseProhibitingMoreMembers.) + * + * @param tail the predecessor of this operation. Completion of tail starts + * execution of this Operation + * @param executor used for asynchronous execution + * @return completion of this CompletableFuture means this Operation is + * complete. The value of the Operation is the value of the CompletableFuture. + */ + abstract CompletionStage follows(CompletionStage tail, Executor executor); + + boolean cancel() { + if (operationLifecycle.isFinished()) { + return false; + } + else { + operationLifecycle = OperationLifecycle.CANCELED; + return true; + } + } + + boolean isCanceled() { + return operationLifecycle.isCanceled(); + } + + Operation checkCanceled() { + if (isCanceled()) { + throw new SqlSkippedException("TODO", null, null, -1, null, -1); + } + return this; + } + + /** + * If an errorHandler is specified, attach a CompletableFuture to the argument + * that will call the errorHandler in event the argument completes + * exceptionally and return that CompletableFuture. If there is no errorHandle + * specified, return the argument. + * + * @param result A CompletionStage that may complete exceptionally + * @return a CompletableFuture that will call the errorHandle if any. + */ + protected CompletionStage attachErrorHandler(CompletionStage result) { + if (errorHandler != null) { + return result.exceptionally(t -> { + Throwable ex = unwrapException(t); + errorHandler.accept(ex); + if (ex instanceof SqlSkippedException) throw (SqlSkippedException)ex; + else throw new SqlSkippedException("TODO", ex, null, -1, null, -1); + }); + } + else { + return result; + } + } + + static enum OperationLifecycle { + MUTABLE, + HELD, + RELEASED, + COMPLETED, + CANCELED; + + /** + * @return true iff op has been submitted which means no more configuration + */ + boolean isSubmitted() { + return this != MUTABLE; + } + + /** + * @return return true if no new members may be added. Implies isSubmitted + */ + boolean isImmutable() { //TODO better name? + return this == RELEASED || this == COMPLETED || this == CANCELED; + } + + boolean isFinished() { + return this == COMPLETED || this == CANCELED; + } + + boolean isCanceled() { + return this == CANCELED; + } + + } + + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/OperationGroup.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/OperationGroup.java new file mode 100644 index 00000000..6f554c92 --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/OperationGroup.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.ArrayCountOperation; +import jdk.incubator.sql2.DynamicMultiOperation; +import jdk.incubator.sql2.LocalOperation; +import jdk.incubator.sql2.OutOperation; +import jdk.incubator.sql2.ParameterizedCountOperation; +import jdk.incubator.sql2.ParameterizedRowOperation; +import jdk.incubator.sql2.RowProcessorOperation; +import jdk.incubator.sql2.StaticMultiOperation; +import jdk.incubator.sql2.Submission; +import jdk.incubator.sql2.Transaction; +import jdk.incubator.sql2.TransactionOutcome; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.concurrent.Flow; +import java.util.function.Consumer; +import java.util.logging.Logger; +import java.util.stream.Collector; + +/** + * Only sequential, dependent, unconditional supported. + * + * Each member Operation creates a CompletableFuture that depends on the previous + * member's CompletableFuture. The first member Operation depends on a distinguished + * CompletableFuture called the head. When the head is completed + * the chain of member Operations is executed asynchronously. + * + * When the OperationGroup itself is submitted, the head is completed with + * the predecessor CompletableFuture. So, when the preceding Operation is completed + * the head is completed and the member Operations begin execution. + * + * The CompletableFuture for the OperationGroup depends on a CompletableFuture + * called held. When held is complete no more member Operations can be added. The + * value of the OperationGroup's CompletableFuture is computed by creating another + * CompletableFuture that depends on the value of the last member Operation. Since + * this is created only after held is completed we know the last member Operation. + * + * When the last member Operation is completed the result of the OperationGroup is + * computed by applying collector.finisher to the accumulator. + * + * For parallel groups each member Operation should depend directly on the + * head and the OperationGroup's result should depend on all the member + * Operations. + * + * For independent groups follows needs to insure the returned CompletableFuture + * hides any exceptions. + * + * For conditional groups the head should depend on both the predecessor + * completing and the condition completing with true. + * + * @param value type of member Operations + * @param value type of OperationGroup + */ +class OperationGroup extends com.oracle.adbaoverjdbc.Operation + implements jdk.incubator.sql2.OperationGroup { + + static final Collector DEFAULT_COLLECTOR = Collector.of( + () -> null, + (a, v) -> {}, + (a, b) -> null, + a -> null); + + static OperationGroup newOperationGroup(Connection conn) { + return new OperationGroup(conn, conn); + } + + private boolean isParallel = false; + private boolean isIndependent = false; + private CompletionStage condition = null; + + private Object accumulator; + private Collector collector; + +/** + * completed when this OperationGroup is no longer held. Completion of this + * OperationGroup depends on held. + * + * @see submit, releaseProhibitingMoreOperations, submitHoldingForMoreOperations + */ + private final CompletableFuture held; + + /** + * predecessor of all member Operations and the OperationGroup itself + */ + private final CompletableFuture head; + + /** + * The last CompletionStage of any submitted member Operation. Mutable until + * not isHeld(). + */ + private CompletionStage memberTail; + + protected OperationGroup(Connection conn, OperationGroup group) { + super(conn, group); + held = new CompletableFuture(); + head = new CompletableFuture(); + memberTail = head; + collector = DEFAULT_COLLECTOR; + } + + @Override + public jdk.incubator.sql2.OperationGroup parallel() { + if ( isImmutable() || isParallel) throw new IllegalStateException("TODO"); + isParallel = true; + return this; + } + + @Override + public jdk.incubator.sql2.OperationGroup independent() { + if ( isImmutable() || isIndependent) throw new IllegalStateException("TODO"); + isIndependent = true; + return this; + } + + @Override + public jdk.incubator.sql2.OperationGroup conditional(CompletionStage condition) { + if ( isImmutable() || condition != null) throw new IllegalStateException("TODO"); + this.condition = condition; + return this; + } + + @Override + public jdk.incubator.sql2.Submission submitHoldingForMoreMembers() { + if ( isImmutable() || ! isHeld() ) throw new IllegalStateException("TODO"); //TODO prevent multiple calls + accumulator = collector.supplier().get(); + return super.submit(); + } + + @Override + public jdk.incubator.sql2.OperationGroup releaseProhibitingMoreMembers() { + if ( ! isImmutable() || ! isHeld() ) throw new IllegalStateException("TODO"); + held.complete(null); + immutable(); // having set isHeld to false this call will make this OpGrp immutable + return this; + } + + @Override + public OperationGroup collect(Collector c) { + if ( isImmutable() || collector != DEFAULT_COLLECTOR) throw new IllegalStateException("TODO"); + if (c == null) throw new IllegalArgumentException("TODO"); + collector = c; + return this; + } + + @Override + public Operation catchOperation() { + if (! isHeld() ) throw new IllegalStateException("TODO"); + return UnskippableOperation.newOperation(connection, this, op -> null); + } + + @Override + public ArrayCountOperation arrayCountOperation(String sql) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public ParameterizedCountOperation countOperation(String sql) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + if (sql == null) throw new IllegalArgumentException("TODO"); + return CountOperation.newCountOperation(connection, this, sql); + } + + @Override + public SqlOperation operation(String sql) { + if ( !isHeld() ) throw new IllegalStateException("TODO"); + return SqlOperation.newOperation(connection, this, sql); + } + + @Override + public OutOperation outOperation(String sql) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public ParameterizedRowOperation rowOperation(String sql) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + if (sql == null) throw new IllegalArgumentException("TODO"); + return RowOperation.newRowOperation(connection, this, sql); + } + + @Override + public RowProcessorOperation rowProcessorOperation(String sql) { + if ( !isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public StaticMultiOperation staticMultiOperation(String sql) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public DynamicMultiOperation dynamicMultiOperation(String sql) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public SimpleOperation endTransactionOperation(Transaction trans) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + return com.oracle.adbaoverjdbc.SimpleOperation.newOperation( + connection, + (OperationGroup)this, + op -> connection.jdbcEndTransaction(op, (com.oracle.adbaoverjdbc.Transaction)trans)); + } + + @Override + public LocalOperation localOperation() { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public Flow.Processor, Submission> operationProcessor() { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public jdk.incubator.sql2.OperationGroup logger(Logger logger) { + if ( ! isHeld() ) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public OperationGroup timeout(Duration minTime) { + super.timeout(minTime); + return this; + } + + @Override + public OperationGroup onError(Consumer handler) { + super.onError(handler); + return this; + } + + @Override + public Submission submit() { + if ( isImmutable() ) throw new IllegalStateException("TODO"); + accumulator = collector.supplier().get(); + held.complete(null); + return super.submit(); + } + + // Internal methods + + Submission submit(Operation op) { + memberTail = op.attachErrorHandler(op.follows(memberTail, getExecutor())); + return com.oracle.adbaoverjdbc.Submission.submit(this::cancel, memberTail); + } + + @Override + CompletionStage follows(CompletionStage predecessor, Executor executor) { + head.complete(predecessor); // completing head allows members to execute + return held.thenCompose( h -> // when held completes memberTail holds the last member + memberTail.thenApplyAsync( t -> (T)collector.finisher().apply(accumulator), executor)); + } + + protected boolean isHeld() { + return !held.isDone(); + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/ParameterizedOperation.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/ParameterizedOperation.java new file mode 100644 index 00000000..11b256b3 --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/ParameterizedOperation.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.AdbaType; +import jdk.incubator.sql2.SqlException; +import jdk.incubator.sql2.SqlType; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import static com.oracle.adbaoverjdbc.Operation.toSQLType; +import static com.oracle.adbaoverjdbc.Operation.toSQLType; + +/** + * + */ +public abstract class ParameterizedOperation extends Operation + implements jdk.incubator.sql2.ParameterizedOperation { + + protected final Map setParameters; + protected CompletionStage futureParameters; + + ParameterizedOperation(Connection conn, OperationGroup operationGroup) { + super(conn, operationGroup); + setParameters = new HashMap<>(); + } + + CompletionStage attachFutureParameters(CompletionStage predecessor) { + if (futureParameters == null) return predecessor; + else return predecessor.runAfterBoth(futureParameters, () -> {}); + } + + @Override + public ParameterizedOperation set(String id, Object value, SqlType type) { + if (isImmutable() || setParameters.containsKey(id)) { + throw new IllegalStateException("TODO"); + } + if (id == null || (type != null && !(type instanceof AdbaType))) { + throw new IllegalArgumentException("TODO"); + } + if (value instanceof CompletionStage) { + if (futureParameters == null) { + futureParameters = ((CompletionStage)value) + .thenAccept( v -> { setParameters.put(id, new ParameterValue(v, type)); }); + } + else { + futureParameters = ((CompletionStage)value) + .thenAcceptBoth(futureParameters, + (v, f) -> { setParameters.put(id, new ParameterValue(v, type)); }); + } + } + else { + setParameters.put(id, new ParameterValue(value, type)); + } + return this; + } + + @Override + public ParameterizedOperation set(String id, CompletionStage source, SqlType type) { + return set(id, (Object) source, type); + } + + @Override + public ParameterizedOperation set(String id, CompletionStage source) { + return set(id, (Object) source, null); + } + + @Override + public ParameterizedOperation set(String id, Object value) { + return set(id, value, null); + } + + static final class ParameterValue { + + final Object value; + final SqlType type; + + ParameterValue(Object val, SqlType typ) { + value = val; + type = typ; + } + + void set(PreparedStatement stmt, String id) { + try { + try { + setByPosition(stmt, Integer.parseInt(id)); + } + catch (NumberFormatException ex) { + setByName(stmt, id); + } + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), null, -1); + } + } + + void setByPosition(PreparedStatement stmt, int index) throws SQLException { + if (type == null) { + stmt.setObject(index, value, toSQLType(value.getClass())); + } + else if (type instanceof AdbaType) { + stmt.setObject(index, value, toSQLType((AdbaType)type)); + } + else { + throw new IllegalArgumentException("TODO"); + } + } + + void setByName(PreparedStatement stmt, String id) throws SQLException { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/RowOperation.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/RowOperation.java new file mode 100644 index 00000000..fa94f11d --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/RowOperation.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import jdk.incubator.sql2.ParameterizedRowOperation; +import jdk.incubator.sql2.Result; +import jdk.incubator.sql2.SqlException; +import jdk.incubator.sql2.SqlType; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.stream.Collector; + +/** + * Creates separate CompletionStages to execute the query, to fetch and process + * each block of fetchSize rows and to compute the final result. Yes, these are + * all synchronous actions so there is no theoretical requirement to do them in + * separate CompletionStages. This class does so to break up this large synchronous + * action into smaller tasks so as to avoid hogging a thread. + */ +class RowOperation extends ParameterizedOperation + implements jdk.incubator.sql2.ParameterizedRowOperation { + + + private static final int NOT_SET = -1; + static final Collector DEFAULT_COLLECTOR = Collector.of( + () -> null, + (a, v) -> {}, + (a, b) -> null, + a -> null); + static RowOperation newRowOperation(Connection conn, OperationGroup grp, String sql) { + return new RowOperation<>(conn, grp, sql); + } + + // attributes + private final String sqlString; + private int fetchSize; + private Collector collector; + + // internal state + private PreparedStatement jdbcStatement; + private ResultSet resultSet; + private Object accumulator; + private boolean rowsRemain; + private long rowCount; + private String[] identifiers; + + protected RowOperation(Connection conn, OperationGroup grp, String sql) { + super(conn, grp); + fetchSize = NOT_SET; + collector = DEFAULT_COLLECTOR; + sqlString = sql; + } + + @Override + CompletionStage follows(CompletionStage predecessor, Executor executor) { + predecessor = attachFutureParameters(predecessor); + return predecessor + .thenRunAsync(this::executeQuery, executor) + .thenCompose(this::moreRows); + } + + @Override + boolean cancel() { + try { + if (jdbcStatement != null) { + jdbcStatement.cancel(); + } + super.cancel(); + return rowsRemain; // if all rows processed then + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sqlString, -1); + } + } + + /** + * Return a CompletionStage that fetches the next block of rows. If there are + * no more rows to fetch return a CompletionStage that completes the query. + * + * @param x ignored + * @return the next Completion stage in the processing of the query. + */ + private CompletionStage moreRows(Object x) { + checkCanceled(); + if (rowsRemain) { + return CompletableFuture.runAsync(this::handleFetchRows, getExecutor()) + .thenComposeAsync(this::moreRows, getExecutor()); + } + else { + return CompletableFuture.supplyAsync(this::completeQuery, getExecutor()); + } + } + + private void initFetchSize() throws SQLException { + if (fetchSize == NOT_SET) { + fetchSize = jdbcStatement.getFetchSize(); + } + else { + jdbcStatement.setFetchSize(fetchSize); + } + } + + private void executeQuery() { + checkCanceled(); + try { + jdbcStatement = connection.prepareStatement(sqlString); + initFetchSize(); + setParameters.forEach((String k, ParameterValue v) -> { + v.set(jdbcStatement, k); + }); + System.out.println("executeQuery(\"" + sqlString + "\")"); + resultSet = jdbcStatement.executeQuery(); + accumulator = collector.supplier().get(); + rowsRemain = true; + rowCount = 0; + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sqlString, -1); + } + } + + /** + * Process fetchSize rows. If the fetches are in sync then all the rows will + * be in memory after the first is fetched up through the last row processed. + * The subsequent row, the one after the last row processed should not be in + * memory and will require a database roundtrip to fetch. This is all assuming + * the rows are fetched fetchSize rows per roundtrip which may not be the case. + * + * @return true if more rows remain + * @throws SQLException + */ + private Object handleFetchRows() { + try { + for (int i = 0; i < fetchSize && (rowsRemain = resultSet.next()); i++) { + handleRow(); + rowCount++; + } + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sqlString, -1); + } + return null; + } + + private void handleRow() throws SQLException { + checkCanceled(); + try (Row row = new Row(this)) { + collector.accumulator().accept(accumulator, row); + } + } + + private T completeQuery() { + try { + resultSet.close(); + jdbcStatement.close(); + checkCanceled(); + return (T) collector.finisher().apply(accumulator); + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sqlString, -1); + } + } + + private String[] getIdentifiers() { + if (identifiers == null) { + try { + if (resultSet == null) { + throw new IllegalStateException("TODO"); + } + System.out.println("ResultSet.getMetaData()"); //DEBUG + ResultSetMetaData md = resultSet.getMetaData(); + int count = md.getColumnCount(); + identifiers = new String[count]; + for (int i = 0; i < count; i++) { + identifiers[i] = md.getColumnName(i); + } + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), sqlString, -1); + } + } + return identifiers; + } + + @Override + public ParameterizedRowOperation fetchSize(long rows) throws IllegalArgumentException { + if (isImmutable() || fetchSize != NOT_SET) throw new IllegalStateException("TODO"); + if (rows < 1) throw new IllegalArgumentException("TODO"); + fetchSize = (int)rows; + return this; + } + + @Override + public ParameterizedRowOperation collect(Collector c) { + if (isImmutable() || collector != DEFAULT_COLLECTOR) throw new IllegalStateException("TODO"); + if (c == null) throw new IllegalArgumentException("TODO"); + collector = c; + return this; + } + + + @Override + public RowOperation onError(Consumer handler) { + return (RowOperation)super.onError(handler); + } + + @Override + public RowOperation timeout(Duration minTime) { + return (RowOperation)super.timeout(minTime); + } + + @Override + public RowOperation set(String id, Object value, SqlType type) { + return (RowOperation)super.set(id, value, type); + } + + @Override + public RowOperation set(String id, CompletionStage source, SqlType type) { + return (RowOperation)super.set(id, source, type); + } + + @Override + public RowOperation set(String id, CompletionStage source) { + return (RowOperation)super.set(id, source); + } + + @Override + public RowOperation set(String id, Object value) { + return (RowOperation)super.set(id, value); + } + + static final class Row implements jdk.incubator.sql2.Result.Row, AutoCloseable { + + private RowOperation op; + + Row(RowOperation op) { + this.op = op; + } + + @Override + public void close() { + op = null; + } + + @Override + public long rowNumber() { + if (op == null) throw new IllegalStateException("TODO"); + return op.rowCount; // keep an independent count because ResultSet.row is limited to int + } + + @Override + public void cancel() { + if (op == null) throw new IllegalStateException("TODO"); + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public T get(String id, Class type) { + if (op == null) { + throw new IllegalStateException("TODO"); + } + try { + int index; + try { + index = Integer.parseInt(id); + } + catch (NumberFormatException ex) { + return op.resultSet.getObject(id, type); + } + return op.resultSet.getObject(index, type); + } + catch (SQLException ex) { + throw new SqlException(ex.getMessage(), ex, ex.getSQLState(), ex.getErrorCode(), op.sqlString, -1); + } + } + + @Override + public String[] getIdentifiers() { + if (op == null) throw new IllegalStateException("TODO"); + return op.getIdentifiers(); + } + + } +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/SimpleOperation.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/SimpleOperation.java new file mode 100644 index 00000000..64df935d --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/SimpleOperation.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Function; +import java.util.function.Supplier; +import jdk.incubator.sql2.SqlSkippedException; + +/** + * + * @param + */ +class SimpleOperation extends Operation implements Supplier { + + static SimpleOperation newOperation(Connection conn, + OperationGroup group, + Function, S> act) { + return new SimpleOperation<>(conn, group, act); + } + + private final Function, T> action; + + protected SimpleOperation(Connection conn, + OperationGroup operationGroup, + Function, T> act) { + super(conn, operationGroup); + action = act; + } + + @Override + CompletionStage follows(CompletionStage tail, Executor executor) { + return tail.thenApplyAsync(x -> get(), executor); + } + + /** + * Computes the value of this Operation by calling the action. If this + * Operation has been canceled throws SqlSkippedException. If the action + * throws a checked exception, wrap that checked exception in a SqlException. + * SqlException is unchecked as required by Supplier, and can be handled by + * CompletionStage. + */ + @Override + public T get() { + checkCanceled(); + try { + return action.apply(this); + } + finally { + operationLifecycle = OperationLifecycle.COMPLETED; + } + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/SqlOperation.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/SqlOperation.java new file mode 100644 index 00000000..a606fbea --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/SqlOperation.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +/** + * + */ +class SqlOperation extends SimpleOperation { + + static SqlOperation newOperation(Connection conn, OperationGroup group, String sql) { + return new SqlOperation<>(conn, group, sql); + } + + protected SqlOperation(Connection conn, OperationGroup group, String sql) { + super(conn, group, op -> (T)conn.jdbcExecute(op, sql)); + } +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Submission.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Submission.java new file mode 100644 index 00000000..4f8e065d --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Submission.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +/** + * + */ +class Submission implements jdk.incubator.sql2.Submission { + + final private Supplier cancel; + final private CompletionStage stage; + private CompletionStage publicStage; + + static Submission submit(Supplier cancel, CompletionStage s) { + return new Submission<>(cancel, s); + } + + protected Submission(Supplier can, CompletionStage stg) { + cancel = can; + stage = stg; + } + + @Override + public CompletionStage cancel() { + return new CompletableFuture().completeAsync(cancel); + } + + @Override + public CompletionStage getCompletionStage() { + if (publicStage == null) publicStage = ((CompletableFuture)stage).minimalCompletionStage(); + return publicStage; + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Transaction.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Transaction.java new file mode 100644 index 00000000..98b14bce --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/Transaction.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +/** + * + */ +class Transaction implements jdk.incubator.sql2.Transaction { + + private boolean isRollbackOnly = false; + private boolean isInFlight = true; + private final Connection connection; + + static Transaction createTransaction(Connection conn) { + return new Transaction(conn); + } + + private Transaction(Connection conn) { + connection = conn; + } + + /** + * + * @param conn + * @return true iff transaction should be committed. false otherwise + */ + synchronized boolean endWithCommit(Connection conn) { + if (conn != connection) throw new IllegalArgumentException("TODO"); + if (!isInFlight) throw new IllegalStateException("TODO"); + isInFlight = false; + return !isRollbackOnly; + } + + @Override + public synchronized boolean setRollbackOnly() { + if (!connection.getConnectionLifecycle().isActive()) throw new IllegalStateException("TODO"); + if (isInFlight) { + isRollbackOnly = true; + return true; + } + else { + return false; + } + } + + @Override + public boolean isRollbackOnly() { + return isRollbackOnly; + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/UnskippableOperation.java b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/UnskippableOperation.java new file mode 100644 index 00000000..c56765be --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/com/oracle/adbaoverjdbc/UnskippableOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 com.oracle.adbaoverjdbc; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Function; + +/** + * + */ +class UnskippableOperation extends SimpleOperation { + + static UnskippableOperation newOperation(Connection conn, + OperationGroup group, + Function, S> action) { + return new UnskippableOperation<>(conn, group, action); + } + + protected UnskippableOperation(Connection conn, + OperationGroup operationGroup, + Function, T> action) { + super(conn, operationGroup, (Function, T>)action); + } + + @Override + CompletionStage follows(CompletionStage tail, Executor executor) { + return tail.handleAsync( + (Object v, Throwable t) -> { + try { + return get(); + } + catch (Throwable ex) { + if (errorHandler != null) errorHandler.accept(ex); + throw ex; + } + }, + executor); + } + +} diff --git a/java/AoJ/src/com/oracle/adbaoverjdbc/module-info.java b/java/AoJ/src/com/oracle/adbaoverjdbc/module-info.java new file mode 100644 index 00000000..c9b0ce26 --- /dev/null +++ b/java/AoJ/src/com/oracle/adbaoverjdbc/module-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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. + */ + +module com.oracle.adbaoverjdbc { + requires jdk.incubator.adba; + requires java.sql; + exports com.oracle.adbaoverjdbc; + provides jdk.incubator.sql2.DataSourceFactory with com.oracle.adbaoverjdbc.DataSourceFactory; +} diff --git a/java/README.md b/java/README.md index e0df90cc..e056adeb 100644 --- a/java/README.md +++ b/java/README.md @@ -1,6 +1,8 @@ # Java based examples This is a repository of sample code that will demonstrate various concepts to assist Java developers in designing applications for accessing and processing data from the Oracle Database, leveraging Java Database Connectivity (JDBC), Universal Connection Pool (UCP); or running Java code directly in the database leveraging the embedded JVM (a.k.a. OJVM). +We have just added the AoJ sub-repository for the Asynchronous Java Database Access (ADBA) over JDBC. + # What's in Oracle database 12c Release 2 for Java Developers? * **Java 8**: Java 8 in JDBC/UCP and OJVM; JDBC 4.2 * **Performance**: JIT (OJVM), Network Compression over WAN (JDBC), Configurable connection health check frequency (UCP), PL/SQL Callbace interface (JDBC) diff --git a/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json b/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json index 5718fa9f..c2f35b4f 100644 --- a/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json +++ b/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json @@ -274,9 +274,9 @@ "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" }, "oracledb": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-2.1.2.tgz", - "integrity": "sha1-ZedlVopZUtLW5gb1QSNKtCKLaFE=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-2.2.0.tgz", + "integrity": "sha512-ywwalyryeJYb5dr1JScyPcNxCeN0zExrKLtorSdptBZqhfS5Dp9KLgGOExc+XMMfEejXGtC/RfiDxKaGn6+VJA==" }, "parseurl": { "version": "1.3.2", diff --git a/javascript/rest-api/part-2-database-basics/hr_app/package.json b/javascript/rest-api/part-2-database-basics/hr_app/package.json index 8be89262..44dc8bc0 100644 --- a/javascript/rest-api/part-2-database-basics/hr_app/package.json +++ b/javascript/rest-api/part-2-database-basics/hr_app/package.json @@ -11,6 +11,6 @@ "dependencies": { "express": "^4.16.3", "morgan": "^1.9.0", - "oracledb": "^2.1.2" + "oracledb": "^2.2.0" } } diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/config/database.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/config/database.js new file mode 100644 index 00000000..40831f85 --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/config/database.js @@ -0,0 +1,10 @@ +module.exports = { + hrPool: { + user: process.env.HR_USER, + password: process.env.HR_PASSWORD, + connectString: process.env.HR_CONNECTIONSTRING, + poolMin: 10, + poolMax: 10, + poolIncrement: 0 + } +}; diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/config/web-server.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/config/web-server.js new file mode 100644 index 00000000..ff74b593 --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/config/web-server.js @@ -0,0 +1,3 @@ +module.exports = { + port: process.env.HTTP_PORT || 3000 +}; diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/controllers/employees.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/controllers/employees.js new file mode 100644 index 00000000..a5257c6c --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/controllers/employees.js @@ -0,0 +1,25 @@ +const employees = require('../db_apis/employees.js'); + +async function get(req, res, next) { + try { + const context = {}; + + context.id = Number(req.params.id); + + const rows = await employees.find(context); + + if (req.params.id) { + if (rows.length === 1) { + res.status(200).json(rows[0]); + } else { + res.status(404).end(); + } + } else { + res.status(200).json(rows); + } + } catch (err) { + next(err); + } +} + +module.exports.get = get; diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/db_apis/employees.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/db_apis/employees.js new file mode 100644 index 00000000..df3da53b --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/db_apis/employees.js @@ -0,0 +1,32 @@ +const database = require('../services/database.js'); + +const baseQuery = + `select employee_id "id", + first_name "first_name", + last_name "last_name", + email "email", + phone_number "phone_number", + hire_date "hire_date", + job_id "job_id", + salary "salary", + commission_pct "commission_pct", + manager_id "manager_id", + department_id "department_id" + from employees`; + +async function find(context) { + let query = baseQuery; + const binds = {}; + + if (context.id) { + binds.employee_id = context.id; + + query += `\nwhere employee_id = :employee_id`; + } + + const result = await database.simpleExecute(query, binds); + + return result.rows; +} + +module.exports.find = find; diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/index.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/index.js new file mode 100644 index 00000000..0eba3fad --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/index.js @@ -0,0 +1,86 @@ +const webServer = require('./services/web-server.js'); +const database = require('./services/database.js'); +const dbConfig = require('./config/database.js'); +const defaultThreadPoolSize = 4; + +// Increase thread pool size by poolMax +process.env.UV_THREADPOOL_SIZE = dbConfig.hrPool.poolMax + defaultThreadPoolSize; + +async function startup() { + console.log('Starting application'); + + try { + console.log('Initializing database module'); + + await database.initialize(); + } catch (err) { + console.error(err); + + process.exit(1); // Non-zero failure code + } + + try { + console.log('Initializing web server module'); + + await webServer.initialize(); + } catch (err) { + console.error(err); + + process.exit(1); // Non-zero failure code + } +} + +startup(); + +async function shutdown(e) { + let err = e; + + console.log('Shutting down application'); + + try { + console.log('Closing web server module'); + + await webServer.close(); + } catch (e) { + console.error(e); + + err = err || e; + } + + try { + console.log('Closing database module'); + + await database.close(); + } catch (e) { + console.error(e); + + err = err || e; + } + + console.log('Exiting process'); + + if (err) { + process.exit(1); // Non-zero failure code + } else { + process.exit(0); + } +} + +process.on('SIGTERM', () => { + console.log('Received SIGTERM'); + + shutdown(); +}); + +process.on('SIGINT', () => { + console.log('Received SIGINT'); + + shutdown(); +}); + +process.on('uncaughtException', err => { + console.log('Uncaught exception'); + console.error(err); + + shutdown(err); +}); diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/package-lock.json b/javascript/rest-api/part-3-handling-get-requests/hr_app/package-lock.json new file mode 100644 index 00000000..c2f35b4f --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/package-lock.json @@ -0,0 +1,392 @@ +{ + "name": "hr_app", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.6.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "basic-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.16" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "requires": { + "accepts": "1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.3", + "qs": "6.5.1", + "range-parser": "1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "1.4.0", + "type-is": "1.6.16", + "utils-merge": "1.0.1", + "vary": "1.1.2" + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.4.0", + "unpipe": "1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.4.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + }, + "morgan": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", + "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "requires": { + "basic-auth": "2.0.0", + "debug": "2.6.9", + "depd": "1.1.2", + "on-finished": "2.3.0", + "on-headers": "1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "oracledb": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-2.2.0.tgz", + "integrity": "sha512-ywwalyryeJYb5dr1JScyPcNxCeN0zExrKLtorSdptBZqhfS5Dp9KLgGOExc+XMMfEejXGtC/RfiDxKaGn6+VJA==" + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", + "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.6.0" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.4.0" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.18" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/package.json b/javascript/rest-api/part-3-handling-get-requests/hr_app/package.json new file mode 100644 index 00000000..44dc8bc0 --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/package.json @@ -0,0 +1,16 @@ +{ + "name": "hr_app", + "version": "0.1.0", + "description": "Creating a REST API with Node.js and Oracle Database", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "license": "Apache-2.0", + "dependencies": { + "express": "^4.16.3", + "morgan": "^1.9.0", + "oracledb": "^2.2.0" + } +} diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/services/database.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/services/database.js new file mode 100644 index 00000000..d7cb8e0a --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/services/database.js @@ -0,0 +1,43 @@ +const oracledb = require('oracledb'); +const dbConfig = require('../config/database.js'); + +async function initialize() { + await oracledb.createPool(dbConfig.hrPool); +} + +module.exports.initialize = initialize; + +async function close() { + await oracledb.getPool().close(); +} + +module.exports.close = close; + +function simpleExecute(statement, binds = [], opts = {}) { + return new Promise(async (resolve, reject) => { + let conn; + + opts.outFormat = oracledb.OBJECT; + opts.autoCommit = true; + + try { + conn = await oracledb.getConnection(); + + const result = await conn.execute(statement, binds, opts); + + resolve(result); + } catch (err) { + reject(err); + } finally { + if (conn) { // conn assignment worked, need to close + try { + await conn.close(); + } catch (err) { + console.log(err); + } + } + } + }); +} + +module.exports.simpleExecute = simpleExecute; diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/services/router.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/services/router.js new file mode 100644 index 00000000..5a1e980e --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/services/router.js @@ -0,0 +1,8 @@ +const express = require('express'); +const router = new express.Router(); +const employees = require('../controllers/employees.js'); + +router.route('/employees/:id?') + .get(employees.get); + +module.exports = router; diff --git a/javascript/rest-api/part-3-handling-get-requests/hr_app/services/web-server.js b/javascript/rest-api/part-3-handling-get-requests/hr_app/services/web-server.js new file mode 100644 index 00000000..1bbece06 --- /dev/null +++ b/javascript/rest-api/part-3-handling-get-requests/hr_app/services/web-server.js @@ -0,0 +1,48 @@ +const http = require('http'); +const express = require('express'); +const morgan = require('morgan'); +const webServerConfig = require('../config/web-server.js'); +const router = require('./router.js'); + +let httpServer; + +function initialize() { + return new Promise((resolve, reject) => { + const app = express(); + httpServer = http.createServer(app); + + // Combines logging info from request and response + app.use(morgan('combined')); + + // Mount the router at /api so all its routes start with /api + app.use('/api', router); + + httpServer.listen(webServerConfig.port, err => { + if (err) { + reject(err); + return; + } + + console.log(`Web server listening on localhost:${webServerConfig.port}`); + + resolve(); + }); + }); +} + +module.exports.initialize = initialize; + +function close() { + return new Promise((resolve, reject) => { + httpServer.close((err) => { + if (err) { + reject(err); + return; + } + + resolve(); + }); + }); +} + +module.exports.close = close;