@Test public void testNestedTransaction() throws Exception { TransactionManager transactionManager = createTestTransactionManager(); Session session = sessionBuilder() .setTransactionId(TransactionId.create()) .setClientTransactionSupport() .build(); QueryStateMachine stateMachine = QueryStateMachine.begin( new QueryId("query"), "START TRANSACTION", session, URI.create("fake://uri"), true, transactionManager, executor); try { try { new StartTransactionTask() .execute( new StartTransaction(ImmutableList.of()), transactionManager, metadata, new AllowAllAccessControl(), stateMachine) .join(); Assert.fail(); } catch (CompletionException e) { throw Throwables.propagate(e.getCause()); } } catch (PrestoException e) { Assert.assertEquals(e.getErrorCode(), NOT_SUPPORTED.toErrorCode()); } Assert.assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); Assert.assertFalse(stateMachine.getQueryInfoWithoutDetails().isClearTransactionId()); Assert.assertFalse( stateMachine.getQueryInfoWithoutDetails().getStartedTransactionId().isPresent()); }
@Test public void testStartTransactionIdleExpiration() throws Exception { Session session = sessionBuilder().setClientTransactionSupport().build(); TransactionManager transactionManager = TransactionManager.create( new TransactionManagerConfig() .setIdleTimeout(new Duration(1, TimeUnit.MICROSECONDS)) // Fast idle timeout .setIdleCheckInterval(new Duration(10, TimeUnit.MILLISECONDS)), scheduledExecutor, executor); QueryStateMachine stateMachine = QueryStateMachine.begin( new QueryId("query"), "START TRANSACTION", session, URI.create("fake://uri"), true, transactionManager, executor); Assert.assertFalse(stateMachine.getSession().getTransactionId().isPresent()); new StartTransactionTask() .execute( new StartTransaction(ImmutableList.of()), transactionManager, metadata, new AllowAllAccessControl(), stateMachine) .join(); Assert.assertFalse(stateMachine.getQueryInfoWithoutDetails().isClearTransactionId()); Assert.assertTrue( stateMachine.getQueryInfoWithoutDetails().getStartedTransactionId().isPresent()); long start = System.nanoTime(); while (!transactionManager.getAllTransactionInfos().isEmpty()) { if (Duration.nanosSince(start).toMillis() > 10_000) { Assert.fail("Transaction did not expire in the allotted time"); } TimeUnit.MILLISECONDS.sleep(10); } }
@Test public void testStartTransactionTooManyIsolationLevels() throws Exception { Session session = sessionBuilder().setClientTransactionSupport().build(); TransactionManager transactionManager = createTestTransactionManager(); QueryStateMachine stateMachine = QueryStateMachine.begin( new QueryId("query"), "START TRANSACTION", session, URI.create("fake://uri"), true, transactionManager, executor); Assert.assertFalse(stateMachine.getSession().getTransactionId().isPresent()); try { try { new StartTransactionTask() .execute( new StartTransaction( ImmutableList.of( new Isolation(Isolation.Level.READ_COMMITTED), new Isolation(Isolation.Level.READ_COMMITTED))), transactionManager, metadata, new AllowAllAccessControl(), stateMachine) .join(); Assert.fail(); } catch (CompletionException e) { throw Throwables.propagate(e.getCause()); } } catch (SemanticException e) { Assert.assertEquals(e.getCode(), INVALID_TRANSACTION_MODE); } Assert.assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); Assert.assertFalse(stateMachine.getQueryInfoWithoutDetails().isClearTransactionId()); Assert.assertFalse( stateMachine.getQueryInfoWithoutDetails().getStartedTransactionId().isPresent()); }
@Test public void testStartTransaction() throws Exception { Session session = sessionBuilder().setClientTransactionSupport().build(); TransactionManager transactionManager = createTestTransactionManager(); QueryStateMachine stateMachine = QueryStateMachine.begin( new QueryId("query"), "START TRANSACTION", session, URI.create("fake://uri"), true, transactionManager, executor); Assert.assertFalse(stateMachine.getSession().getTransactionId().isPresent()); new StartTransactionTask() .execute( new StartTransaction(ImmutableList.of()), transactionManager, metadata, new AllowAllAccessControl(), stateMachine) .join(); Assert.assertFalse(stateMachine.getQueryInfoWithoutDetails().isClearTransactionId()); Assert.assertTrue( stateMachine.getQueryInfoWithoutDetails().getStartedTransactionId().isPresent()); Assert.assertEquals(transactionManager.getAllTransactionInfos().size(), 1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo( stateMachine.getQueryInfoWithoutDetails().getStartedTransactionId().get()); Assert.assertFalse(transactionInfo.isAutoCommitContext()); }
@Override public CompletableFuture<?> execute( Call call, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, QueryStateMachine stateMachine) { if (!stateMachine.isAutoCommit()) { throw new PrestoException( NOT_SUPPORTED, "Procedures cannot be called within a transaction (use autocommit mode)"); } Session session = stateMachine.getSession(); QualifiedObjectName procedureName = createQualifiedObjectName(session, call, call.getName()); Procedure procedure = metadata.getProcedureRegistry().resolve(procedureName); // map declared argument names to positions Map<String, Integer> positions = new HashMap<>(); for (int i = 0; i < procedure.getArguments().size(); i++) { positions.put(procedure.getArguments().get(i).getName(), i); } // per specification, do not allow mixing argument types Predicate<CallArgument> hasName = argument -> argument.getName().isPresent(); boolean anyNamed = call.getArguments().stream().anyMatch(hasName); boolean allNamed = call.getArguments().stream().allMatch(hasName); if (anyNamed && !allNamed) { throw new SemanticException( INVALID_PROCEDURE_ARGUMENTS, call, "Named and positional arguments cannot be mixed"); } // get the argument names in call order Map<String, CallArgument> names = new LinkedHashMap<>(); for (int i = 0; i < call.getArguments().size(); i++) { CallArgument argument = call.getArguments().get(i); if (argument.getName().isPresent()) { String name = argument.getName().get(); if (names.put(name, argument) != null) { throw new SemanticException( INVALID_PROCEDURE_ARGUMENTS, argument, "Duplicate procedure argument: %s", name); } if (!positions.containsKey(name)) { throw new SemanticException( INVALID_PROCEDURE_ARGUMENTS, argument, "Unknown argument name: %s", name); } } else if (i < procedure.getArguments().size()) { names.put(procedure.getArguments().get(i).getName(), argument); } else { throw new SemanticException( INVALID_PROCEDURE_ARGUMENTS, call, "Too many arguments for procedure"); } } // verify argument count if (names.size() < positions.size()) { throw new SemanticException( INVALID_PROCEDURE_ARGUMENTS, call, "Too few arguments for procedure"); } // get argument values Object[] values = new Object[procedure.getArguments().size()]; for (Entry<String, CallArgument> entry : names.entrySet()) { CallArgument callArgument = entry.getValue(); int index = positions.get(entry.getKey()); Argument argument = procedure.getArguments().get(index); Expression expression = callArgument.getValue(); Type type = argument.getType(); Object value = evaluateConstantExpression(expression, type, metadata, session); values[index] = toTypeObjectValue(session, type, value); } // validate arguments MethodType methodType = procedure.getMethodHandle().type(); for (int i = 0; i < procedure.getArguments().size(); i++) { if ((values[i] == null) && methodType.parameterType(i).isPrimitive()) { String name = procedure.getArguments().get(i).getName(); throw new PrestoException( INVALID_PROCEDURE_ARGUMENT, "Procedure argument cannot be null: " + name); } } // insert session argument List<Object> arguments = new ArrayList<>(); Iterator<Object> valuesIterator = asList(values).iterator(); for (Class<?> type : methodType.parameterList()) { if (ConnectorSession.class.isAssignableFrom(type)) { arguments.add(session.toConnectorSession(procedureName.getCatalogName())); } else { arguments.add(valuesIterator.next()); } } try { procedure.getMethodHandle().invokeWithArguments(arguments); } catch (Throwable t) { if (t instanceof InterruptedException) { Thread.currentThread().interrupt(); } propagateIfInstanceOf(t, PrestoException.class); throw new PrestoException(PROCEDURE_CALL_FAILED, t); } return completedFuture(null); }