| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.phoenix.end2end; |
| |
| import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.sql.Connection; |
| import java.sql.DriverManager; |
| import java.sql.ParameterMetaData; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| |
| import org.apache.phoenix.exception.SQLExceptionCode; |
| import org.apache.phoenix.jdbc.PhoenixStatement; |
| import org.apache.phoenix.query.QueryServices; |
| import org.apache.phoenix.schema.SchemaNotFoundException; |
| import org.apache.phoenix.schema.SequenceAlreadyExistsException; |
| import org.apache.phoenix.schema.SequenceNotFoundException; |
| import org.apache.phoenix.util.PhoenixRuntime; |
| import org.apache.phoenix.util.PropertiesUtil; |
| import org.apache.phoenix.util.QueryUtil; |
| import org.apache.phoenix.util.ReadOnlyProps; |
| import org.apache.phoenix.util.SequenceUtil; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| |
| import com.google.common.collect.Lists; |
| |
| |
| public class SequenceIT extends BaseClientManagedTimeIT { |
| private static final String NEXT_VAL_SQL = "SELECT NEXT VALUE FOR foo.bar FROM SYSTEM.\"SEQUENCE\""; |
| private static final String SELECT_NEXT_VALUE_SQL = "SELECT NEXT VALUE FOR %s FROM SYSTEM.\"SEQUENCE\""; |
| private static final long BATCH_SIZE = 3; |
| |
| private Connection conn; |
| |
| @BeforeClass |
| @Shadower(classBeingShadowed = BaseClientManagedTimeIT.class) |
| public static void doSetup() throws Exception { |
| Map<String,String> props = getDefaultProps(); |
| // Must update config before starting server |
| props.put(QueryServices.SEQUENCE_CACHE_SIZE_ATTRIB, Long.toString(BATCH_SIZE)); |
| setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator())); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| // close any open connection between tests, so that connections are not leaked |
| if (conn != null) { |
| conn.close(); |
| } |
| } |
| |
| @Test |
| public void testSystemTable() throws Exception { |
| nextConnection(); |
| String query = "SELECT sequence_schema, sequence_name, current_value, increment_by FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testDuplicateSequences() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.beta START WITH 2 INCREMENT BY 4\n"); |
| |
| try { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.beta START WITH 2 INCREMENT BY 4\n"); |
| Assert.fail("Duplicate sequences"); |
| } catch (SequenceAlreadyExistsException e){ |
| |
| } |
| } |
| |
| @Test |
| public void testSequenceNotFound() throws Exception { |
| nextConnection(); |
| String query = "SELECT NEXT value FOR qwert.asdf FROM SYSTEM.\"SEQUENCE\""; |
| try { |
| conn.prepareStatement(query).executeQuery(); |
| fail("Sequence not found"); |
| }catch(SequenceNotFoundException e){ |
| |
| } |
| } |
| |
| @Test |
| public void testCreateSequenceWhenNamespaceEnabled() throws Exception { |
| Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); |
| props.setProperty(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, Boolean.toString(true)); |
| String sequenceSchemaName = "ALPHA"; |
| String sequenceName = sequenceSchemaName + ".M_OMEGA"; |
| |
| nextConnection(props); |
| try { |
| conn.createStatement().execute("CREATE SEQUENCE " + sequenceName + " START WITH 2 INCREMENT BY 4"); |
| fail(); |
| } catch (SchemaNotFoundException e) { |
| // expected |
| } |
| |
| conn.createStatement().execute("CREATE SCHEMA " + sequenceSchemaName); |
| nextConnection(props); |
| conn.createStatement().execute("CREATE SEQUENCE " + sequenceName + " START WITH 2 INCREMENT BY 4"); |
| sequenceSchemaName = "TEST_SEQ_SCHEMA"; |
| sequenceName = "M_SEQ"; |
| conn.createStatement().execute("CREATE SCHEMA " + sequenceSchemaName); |
| nextConnection(props); |
| conn.createStatement().execute("USE " + sequenceSchemaName); |
| conn.createStatement().execute("CREATE SEQUENCE " + sequenceName + " START WITH 2 INCREMENT BY 4"); |
| nextConnection(props); |
| String query = "SELECT sequence_schema, sequence_name, current_value, increment_by FROM SYSTEM.\"SEQUENCE\" WHERE sequence_name='" |
| + sequenceName + "'"; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(sequenceSchemaName, rs.getString("sequence_schema")); |
| assertEquals(sequenceName, rs.getString("sequence_name")); |
| assertEquals(2, rs.getInt("current_value")); |
| assertEquals(4, rs.getInt("increment_by")); |
| assertFalse(rs.next()); |
| try { |
| conn.createStatement().execute( |
| "CREATE SEQUENCE " + sequenceSchemaName + "." + sequenceName + " START WITH 2 INCREMENT BY 4"); |
| fail(); |
| } catch (SequenceAlreadyExistsException e) { |
| |
| } |
| } |
| |
| @Test |
| public void testCreateSequence() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.omega START WITH 2 INCREMENT BY 4"); |
| nextConnection(); |
| String query = "SELECT sequence_schema, sequence_name, current_value, increment_by FROM SYSTEM.\"SEQUENCE\" WHERE sequence_name='OMEGA'"; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals("ALPHA", rs.getString("sequence_schema")); |
| assertEquals("OMEGA", rs.getString("sequence_name")); |
| assertEquals(2, rs.getInt("current_value")); |
| assertEquals(4, rs.getInt("increment_by")); |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testCurrentValueFor() throws Exception { |
| ResultSet rs; |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE used.nowhere START WITH 2 INCREMENT BY 4"); |
| nextConnection(); |
| try { |
| rs = conn.createStatement().executeQuery("SELECT CURRENT VALUE FOR used.nowhere FROM SYSTEM.\"SEQUENCE\""); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.CANNOT_CALL_CURRENT_BEFORE_NEXT_VALUE.getErrorCode(), e.getErrorCode()); |
| assertTrue(e.getNextException()==null); |
| } |
| |
| rs = conn.createStatement().executeQuery("SELECT NEXT VALUE FOR used.nowhere FROM SYSTEM.\"SEQUENCE\""); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| rs = conn.createStatement().executeQuery("SELECT CURRENT VALUE FOR used.nowhere FROM SYSTEM.\"SEQUENCE\""); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| } |
| |
| @Test |
| public void testDropSequence() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.omega START WITH 2 INCREMENT BY 4"); |
| nextConnection(); |
| String query = "SELECT sequence_schema, sequence_name, current_value, increment_by FROM SYSTEM.\"SEQUENCE\" WHERE sequence_name='OMEGA'"; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals("ALPHA", rs.getString("sequence_schema")); |
| assertEquals("OMEGA", rs.getString("sequence_name")); |
| assertEquals(2, rs.getInt("current_value")); |
| assertEquals(4, rs.getInt("increment_by")); |
| assertFalse(rs.next()); |
| |
| conn.createStatement().execute("DROP SEQUENCE alpha.omega"); |
| nextConnection(); |
| query = "SELECT sequence_schema, sequence_name, current_value, increment_by FROM SYSTEM.\"SEQUENCE\" WHERE sequence_name='OMEGA'"; |
| rs = conn.prepareStatement(query).executeQuery(); |
| assertFalse(rs.next()); |
| |
| try { |
| conn.createStatement().execute("DROP SEQUENCE alpha.omega"); |
| fail(); |
| } catch (SequenceNotFoundException ignore) { |
| } |
| } |
| |
| @Test |
| public void testSelectNextValueFor() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY 2"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(3, 5, 7); |
| } |
| |
| @Test |
| public void testInsertNextValueFor() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.tau START WITH 2 INCREMENT BY 1"); |
| conn.createStatement().execute("CREATE TABLE test.sequence_number ( id INTEGER NOT NULL PRIMARY KEY)"); |
| nextConnection(); |
| conn.createStatement().execute("UPSERT INTO test.sequence_number (id) VALUES (NEXT VALUE FOR alpha.tau)"); |
| conn.createStatement().execute("UPSERT INTO test.sequence_number (id) VALUES (NEXT VALUE FOR alpha.tau)"); |
| conn.commit(); |
| nextConnection(); |
| String query = "SELECT id FROM test.sequence_number"; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(3, rs.getInt(1)); |
| } |
| |
| @Test |
| public void testSequenceCreation() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE alpha.gamma START WITH 2 INCREMENT BY 3 MINVALUE 0 MAXVALUE 10 CYCLE CACHE 5"); |
| nextConnection(); |
| ResultSet rs = |
| conn.createStatement() |
| .executeQuery( |
| "SELECT start_with, current_value, increment_by, cache_size, min_value, max_value, cycle_flag, sequence_schema, sequence_name FROM SYSTEM.\"SEQUENCE\""); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getLong("start_with")); |
| assertEquals(2, rs.getInt("current_value")); |
| assertEquals(3, rs.getLong("increment_by")); |
| assertEquals(5, rs.getLong("cache_size")); |
| assertEquals(0, rs.getLong("min_value")); |
| assertEquals(10, rs.getLong("max_value")); |
| assertEquals(true, rs.getBoolean("cycle_flag")); |
| assertEquals("ALPHA", rs.getString("sequence_schema")); |
| assertEquals("GAMMA", rs.getString("sequence_name")); |
| assertFalse(rs.next()); |
| rs = |
| conn.createStatement() |
| .executeQuery( |
| "SELECT NEXT VALUE FOR alpha.gamma, CURRENT VALUE FOR alpha.gamma FROM SYSTEM.\"SEQUENCE\""); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getLong(1)); |
| assertEquals(2, rs.getLong(2)); |
| assertFalse(rs.next()); |
| rs = |
| conn.createStatement() |
| .executeQuery( |
| "SELECT CURRENT VALUE FOR alpha.gamma, NEXT VALUE FOR alpha.gamma FROM SYSTEM.\"SEQUENCE\""); |
| assertTrue(rs.next()); |
| assertEquals(5, rs.getLong(1)); |
| assertEquals(5, rs.getLong(2)); |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testSameMultipleSequenceValues() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.zeta START WITH 4 INCREMENT BY 7"); |
| nextConnection(); |
| String query = "SELECT NEXT VALUE FOR alpha.zeta, NEXT VALUE FOR alpha.zeta FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getInt(1)); |
| assertEquals(4, rs.getInt(2)); |
| assertFalse(rs.next()); |
| conn.close(); |
| } |
| |
| @Test |
| public void testMultipleSequenceValues() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.zeta START WITH 4 INCREMENT BY 7"); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.kappa START WITH 9 INCREMENT BY 2"); |
| nextConnection(); |
| String query = "SELECT NEXT VALUE FOR alpha.zeta, NEXT VALUE FOR alpha.kappa FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getInt(1)); |
| assertEquals(9, rs.getInt(2)); |
| assertTrue(rs.next()); |
| assertEquals(4+7, rs.getInt(1)); |
| assertEquals(9+2, rs.getInt(2)); |
| assertFalse(rs.next()); |
| conn.close(); |
| // Test that sequences don't have gaps (if no other client request the same sequence before we close it) |
| nextConnection(); |
| rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4+7*2, rs.getInt(1)); |
| assertEquals(9+2*2, rs.getInt(2)); |
| assertTrue(rs.next()); |
| assertEquals(4+7*3, rs.getInt(1)); |
| assertEquals(9+2*3, rs.getInt(2)); |
| assertFalse(rs.next()); |
| conn.close(); |
| } |
| |
| @Test |
| public void testMultipleSequencesNoCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute( |
| "CREATE SEQUENCE alpha.zeta START WITH 4 INCREMENT BY 7 MAXVALUE 24"); |
| conn.createStatement().execute( |
| "CREATE SEQUENCE alpha.kappa START WITH 9 INCREMENT BY -2 MINVALUE 5"); |
| nextConnection(); |
| String query = |
| "SELECT NEXT VALUE FOR alpha.zeta, NEXT VALUE FOR alpha.kappa FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getInt(1)); |
| assertEquals(9, rs.getInt(2)); |
| assertTrue(rs.next()); |
| assertEquals(4 + 7, rs.getInt(1)); |
| assertEquals(9 - 2, rs.getInt(2)); |
| assertFalse(rs.next()); |
| conn.close(); |
| |
| nextConnection(); |
| rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4 + 7 * 2, rs.getInt(1)); |
| assertEquals(9 - 2 * 2, rs.getInt(2)); |
| try { |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| SQLException sqlEx1 = |
| SequenceUtil.getException("ALPHA", "ZETA", |
| SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE); |
| SQLException sqlEx2 = |
| SequenceUtil.getException("ALPHA", "KAPPA", |
| SQLExceptionCode.SEQUENCE_VAL_REACHED_MIN_VALUE); |
| verifyExceptions(e, Lists.newArrayList(sqlEx1.getMessage(), sqlEx2.getMessage())); |
| } |
| conn.close(); |
| } |
| |
| @Test |
| public void testMultipleSequencesCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute( |
| "CREATE SEQUENCE alpha.zeta START WITH 4 INCREMENT BY 7 MINVALUE 4 MAXVALUE 19 CYCLE"); |
| conn.createStatement().execute( |
| "CREATE SEQUENCE alpha.kappa START WITH 9 INCREMENT BY -2 MINVALUE 5 MAXVALUE 9 CYCLE"); |
| nextConnection(); |
| String query = |
| "SELECT NEXT VALUE FOR alpha.zeta, NEXT VALUE FOR alpha.kappa FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getInt(1)); |
| assertEquals(9, rs.getInt(2)); |
| assertTrue(rs.next()); |
| assertEquals(4 + 7, rs.getInt(1)); |
| assertEquals(9 - 2, rs.getInt(2)); |
| assertFalse(rs.next()); |
| conn.close(); |
| |
| nextConnection(); |
| rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4 + 7 * 2, rs.getInt(1)); |
| assertEquals(9 - 2 * 2, rs.getInt(2)); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getInt(1)); |
| assertEquals(9, rs.getInt(2)); |
| conn.close(); |
| } |
| |
| @Test |
| public void testCompilerOptimization() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE seq.perf START WITH 3 INCREMENT BY 2"); |
| conn.createStatement().execute("CREATE TABLE t (k INTEGER NOT NULL PRIMARY KEY, v1 VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true"); |
| nextConnection(); |
| conn.createStatement().execute("CREATE INDEX idx ON t(v1) INCLUDE (v2)"); |
| nextConnection(); |
| PhoenixStatement stmt = conn.createStatement().unwrap(PhoenixStatement.class); |
| stmt.optimizeQuery("SELECT k, NEXT VALUE FOR seq.perf FROM t WHERE v1 = 'bar'"); |
| } |
| |
| @Test |
| public void testSelectRowAndSequence() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.epsilon START WITH 1 INCREMENT BY 4"); |
| conn.createStatement().execute("CREATE TABLE test.foo ( id INTEGER NOT NULL PRIMARY KEY)"); |
| nextConnection(); |
| conn.createStatement().execute("UPSERT INTO test.foo (id) VALUES (NEXT VALUE FOR alpha.epsilon)"); |
| conn.commit(); |
| nextConnection(); |
| String query = "SELECT NEXT VALUE FOR alpha.epsilon, id FROM test.foo"; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(5, rs.getInt(1)); |
| assertEquals(1, rs.getInt(2)); |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testSelectNextValueForOverMultipleBatches() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| conn.createStatement().execute("CREATE TABLE foo (k BIGINT NOT NULL PRIMARY KEY)"); |
| |
| nextConnection(); |
| PreparedStatement stmt = conn.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"); |
| for (int i = 0; i < BATCH_SIZE * 2 + 1; i++) { |
| stmt.execute(); |
| } |
| conn.commit(); |
| nextConnection(); |
| ResultSet rs = conn.createStatement().executeQuery("SELECT count(*),max(k) FROM foo"); |
| assertTrue(rs.next()); |
| assertEquals(BATCH_SIZE * 2 + 1, rs.getInt(1)); |
| assertEquals(BATCH_SIZE * 2 + 1, rs.getInt(2)); |
| } |
| |
| @Test |
| public void testSelectNextValueForGroupBy() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| conn.createStatement().execute("CREATE TABLE foo (k BIGINT NOT NULL PRIMARY KEY, v VARCHAR)"); |
| conn.createStatement().execute("CREATE TABLE bar (k BIGINT NOT NULL PRIMARY KEY, v VARCHAR)"); |
| |
| nextConnection(); |
| PreparedStatement stmt = conn.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar, ?)"); |
| stmt.setString(1, "a"); |
| stmt.execute(); |
| stmt.setString(1, "a"); |
| stmt.execute(); |
| stmt.setString(1, "b"); |
| stmt.execute(); |
| stmt.setString(1, "b"); |
| stmt.execute(); |
| stmt.setString(1, "c"); |
| stmt.execute(); |
| conn.commit(); |
| |
| nextConnection(); |
| ResultSet rs = conn.createStatement().executeQuery("SELECT k from foo"); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(3, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(5, rs.getInt(1)); |
| assertFalse(rs.next()); |
| |
| nextConnection(); |
| conn.setAutoCommit(true);; |
| conn.createStatement().execute("UPSERT INTO bar SELECT NEXT VALUE FOR foo.bar,v FROM foo GROUP BY v"); |
| nextConnection(); |
| rs = conn.createStatement().executeQuery("SELECT * from bar"); |
| assertTrue(rs.next()); |
| assertEquals(6, rs.getInt(1)); |
| assertEquals("a", rs.getString(2)); |
| assertTrue(rs.next()); |
| assertEquals(7, rs.getInt(1)); |
| assertEquals("b", rs.getString(2)); |
| assertTrue(rs.next()); |
| assertEquals(8, rs.getInt(1)); |
| assertEquals("c", rs.getString(2)); |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testSelectNextValueForMultipleConn() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| conn.createStatement().execute("CREATE TABLE foo (k BIGINT NOT NULL PRIMARY KEY)"); |
| |
| nextConnection(); |
| Connection conn1 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| |
| PreparedStatement stmt1 = conn1.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"); |
| for (int i = 0; i < BATCH_SIZE+ 1; i++) { |
| stmt1.execute(); |
| } |
| conn1.commit(); |
| |
| nextConnection(); |
| Connection conn2 = conn; |
| PreparedStatement stmt2 = conn2.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"); |
| stmt2.execute(); |
| stmt1.close(); // Should still continue with next value, even on separate connection |
| for (int i = 0; i < BATCH_SIZE; i++) { |
| stmt2.execute(); |
| } |
| conn2.commit(); |
| conn2.close(); |
| conn1.close(); |
| |
| nextConnection(); |
| // No gaps exist even when sequences were generated from different connections |
| ResultSet rs = conn.createStatement().executeQuery("SELECT k FROM foo"); |
| for (int i = 0; i < (BATCH_SIZE+ 1)*2; i++) { |
| assertTrue(rs.next()); |
| assertEquals(i+1, rs.getInt(1)); |
| } |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testSelectNextValueForMultipleConnWithStmtClose() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| conn.createStatement().execute("CREATE TABLE foo (k BIGINT NOT NULL PRIMARY KEY)"); |
| |
| nextConnection(); |
| Connection conn1 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| |
| PreparedStatement stmt1 = conn1.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"); |
| for (int i = 0; i < BATCH_SIZE+ 1; i++) { |
| stmt1.execute(); |
| } |
| conn1.commit(); |
| stmt1.close(); |
| |
| nextConnection(); |
| Connection conn2 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| |
| PreparedStatement stmt2 = conn2.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"); |
| for (int i = 0; i < BATCH_SIZE + 1; i++) { |
| stmt2.execute(); |
| } |
| conn2.commit(); |
| conn2.close(); |
| conn1.close(); |
| |
| nextConnection(); |
| ResultSet rs = conn.createStatement().executeQuery("SELECT k FROM foo"); |
| for (int i = 0; i < 2*(BATCH_SIZE + 1); i++) { |
| assertTrue(rs.next()); |
| assertEquals(i+1, rs.getInt(1)); |
| } |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testSelectNextValueForMultipleConnWithConnClose() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| conn.createStatement().execute("CREATE TABLE foo (k BIGINT NOT NULL PRIMARY KEY)"); |
| |
| nextConnection(); |
| Connection conn1 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| |
| PreparedStatement stmt1 = conn1.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"); |
| for (int i = 0; i < BATCH_SIZE+ 1; i++) { |
| stmt1.execute(); |
| } |
| conn1.commit(); |
| conn1.close(); |
| |
| nextConnection(); |
| Connection conn2 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| |
| PreparedStatement stmt2 = conn2.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"); |
| for (int i = 0; i < BATCH_SIZE + 1; i++) { |
| stmt2.execute(); |
| } |
| conn2.commit(); |
| conn2.close(); |
| |
| nextConnection(); |
| ResultSet rs = conn.createStatement().executeQuery("SELECT k FROM foo"); |
| for (int i = 0; i < 2*(BATCH_SIZE + 1); i++) { |
| assertTrue(rs.next()); |
| assertEquals(i+1, rs.getInt(1)); |
| } |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testDropCachedSeq1() throws Exception { |
| testDropCachedSeq(false); |
| } |
| |
| @Test |
| public void testDropCachedSeq2() throws Exception { |
| testDropCachedSeq(true); |
| } |
| |
| private void testDropCachedSeq(boolean detectDeleteSeqInEval) throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| conn.createStatement().execute("CREATE SEQUENCE bar.bas START WITH 101"); |
| conn.createStatement().execute("CREATE TABLE foo (k BIGINT NOT NULL PRIMARY KEY)"); |
| |
| nextConnection(); |
| Connection conn1 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| |
| String stmtStr1a = "UPSERT INTO foo VALUES(NEXT VALUE FOR foo.bar)"; |
| PreparedStatement stmt1a = conn1.prepareStatement(stmtStr1a); |
| stmt1a.execute(); |
| stmt1a.execute(); |
| String stmtStr1b = "UPSERT INTO foo VALUES(NEXT VALUE FOR bar.bas)"; |
| PreparedStatement stmt1b = conn1.prepareStatement(stmtStr1b); |
| stmt1b.execute(); |
| stmt1b.execute(); |
| stmt1b.execute(); |
| conn1.commit(); |
| |
| nextConnection(); |
| Connection conn2 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| |
| PreparedStatement stmt2 = conn2.prepareStatement("UPSERT INTO foo VALUES(NEXT VALUE FOR bar.bas)"); |
| stmt2.execute(); |
| conn2.commit(); |
| |
| nextConnection(); |
| ResultSet rs = conn.createStatement().executeQuery("SELECT k FROM foo"); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(101, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(102, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(103, rs.getInt(1)); |
| assertTrue(rs.next()); |
| assertEquals(104, rs.getInt(1)); |
| assertFalse(rs.next()); |
| |
| nextConnection(); |
| conn.createStatement().execute("DROP SEQUENCE bar.bas"); |
| |
| nextConnection(); |
| stmt1a = conn.prepareStatement(stmtStr1a); |
| stmt1a.execute(); |
| if (!detectDeleteSeqInEval) { |
| stmt1a.execute(); // Will allocate new batch for foo.bar and get error for bar.bas, but ignore it |
| } |
| |
| stmt1b = conn.prepareStatement(stmtStr1b); |
| try { |
| stmt1b.execute(); // Will try to get new batch, but fail b/c sequence has been dropped |
| fail(); |
| } catch (SequenceNotFoundException e) { |
| } |
| conn1.close(); |
| conn2.close(); |
| } |
| |
| @Test |
| public void testExplainPlanValidatesSequences() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE bar"); |
| conn.createStatement().execute("CREATE TABLE foo (k BIGINT NOT NULL PRIMARY KEY)"); |
| |
| nextConnection(); |
| Connection conn2 = conn; |
| conn = null; // So that call to nextConnection doesn't close it |
| ResultSet rs = conn2.createStatement().executeQuery("EXPLAIN SELECT NEXT VALUE FOR bar FROM foo"); |
| assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER FOO\n" + |
| " SERVER FILTER BY FIRST KEY ONLY\n" + |
| "CLIENT RESERVE VALUES FROM 1 SEQUENCE", QueryUtil.getExplainPlan(rs)); |
| |
| nextConnection(); |
| rs = conn.createStatement().executeQuery("SELECT sequence_name, current_value FROM SYSTEM.\"SEQUENCE\" WHERE sequence_name='BAR'"); |
| assertTrue(rs.next()); |
| assertEquals("BAR", rs.getString(1)); |
| assertEquals(1, rs.getInt(2)); |
| conn.close(); |
| conn2.close(); |
| |
| nextConnection(); |
| try { |
| conn.createStatement().executeQuery("EXPLAIN SELECT NEXT VALUE FOR zzz FROM foo"); |
| fail(); |
| } catch (SequenceNotFoundException e) { |
| // expected |
| } |
| conn.close(); |
| } |
| |
| @Test |
| public void testSelectNextValueAsInput() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY 2"); |
| nextConnection(); |
| String query = "SELECT LPAD(ENCODE(NEXT VALUE FOR foo.bar,'base62'),5,'0') FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals("00003", rs.getString(1)); |
| } |
| |
| @Test |
| public void testSelectNextValueInArithmetic() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY 2"); |
| nextConnection(); |
| String query = "SELECT NEXT VALUE FOR foo.bar+1 FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getInt(1)); |
| } |
| |
| private void nextConnection(Properties props) throws Exception { |
| if (conn != null) conn.close(); |
| long ts = nextTimestamp(); |
| props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts)); |
| conn = DriverManager.getConnection(getUrl(), props); |
| } |
| |
| // if nextConnection() is not used to get to get a connection, make sure you call .close() so that connections are |
| // not leaked |
| private void nextConnection() throws Exception { |
| nextConnection(PropertiesUtil.deepCopy(TEST_PROPERTIES)); |
| } |
| |
| @Test |
| public void testSequenceDefault() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(1, 2, 3); |
| conn.createStatement().execute("DROP SEQUENCE foo.bar"); |
| |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar INCREMENT BY -1"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(1, 0, -1); |
| conn.createStatement().execute("DROP SEQUENCE foo.bar"); |
| |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar MINVALUE 10"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(10, 11, 12); |
| conn.createStatement().execute("DROP SEQUENCE foo.bar"); |
| |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar INCREMENT BY -1 MINVALUE 10 "); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(Long.MAX_VALUE, Long.MAX_VALUE - 1, Long.MAX_VALUE - 2); |
| conn.createStatement().execute("DROP SEQUENCE foo.bar"); |
| |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar MAXVALUE 0"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(Long.MIN_VALUE, Long.MIN_VALUE + 1, Long.MIN_VALUE + 2); |
| conn.createStatement().execute("DROP SEQUENCE foo.bar"); |
| |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar INCREMENT BY -1 MAXVALUE 0"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(0, -1, -2); |
| } |
| |
| @Test |
| public void testSequenceValidateStartValue() throws Exception { |
| nextConnection(); |
| try { |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar1 START WITH 1 INCREMENT BY 1 MINVALUE 2 MAXVALUE 3"); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.STARTS_WITH_MUST_BE_BETWEEN_MIN_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| |
| try { |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar2 START WITH 4 INCREMENT BY 1 MINVALUE 2 MAXVALUE 3"); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.STARTS_WITH_MUST_BE_BETWEEN_MIN_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceValidateMinValue() throws Exception { |
| nextConnection(); |
| try { |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar MINVALUE abc"); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.MINVALUE_MUST_BE_CONSTANT.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceValidateMaxValue() throws Exception { |
| nextConnection(); |
| try { |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar MAXVALUE null"); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.MAXVALUE_MUST_BE_CONSTANT.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceValidateMinValueLessThanOrEqualToMaxValue() throws Exception { |
| nextConnection(); |
| try { |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar MINVALUE 2 MAXVALUE 1"); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals( |
| SQLExceptionCode.MINVALUE_MUST_BE_LESS_THAN_OR_EQUAL_TO_MAXVALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceValidateIncrementConstant() throws Exception { |
| nextConnection(); |
| try { |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar INCREMENT null"); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.INCREMENT_BY_MUST_BE_CONSTANT.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceValidateIncrementNotEqualToZero() throws Exception { |
| nextConnection(); |
| try { |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar INCREMENT 0"); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.INCREMENT_BY_MUST_NOT_BE_ZERO.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceStartWithMinMaxSameValueIncreasingCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY 1 MINVALUE 3 MAXVALUE 3 CYCLE CACHE 1"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(3, 3, 3); |
| } |
| |
| @Test |
| public void testSequenceStartWithMinMaxSameValueDecreasingCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY -1 MINVALUE 3 MAXVALUE 3 CYCLE CACHE 2"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(3, 3, 3); |
| } |
| |
| @Test |
| public void testSequenceStartWithMinMaxSameValueIncreasingNoCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY 1 MINVALUE 3 MAXVALUE 3 CACHE 1"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(3); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceStartWithMinMaxSameValueDecreasingNoCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY -1 MINVALUE 3 MAXVALUE 3 CACHE 2"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(3); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MIN_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceIncreasingCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 2 INCREMENT BY 3 MINVALUE 1 MAXVALUE 10 CYCLE CACHE 2"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(2, 5, 8, 1, 4, 7, 10, 1, 4); |
| } |
| |
| @Test |
| public void testSequenceDecreasingCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY -2 MINVALUE 1 MAXVALUE 10 CYCLE CACHE 2"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(3, 1, 10, 8, 6, 4, 2, 10, 8); |
| } |
| |
| @Test |
| public void testSequenceIncreasingNoCycle() throws Exception { |
| nextConnection(); |
| // client throws exception |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar START WITH 2 INCREMENT BY 3 MINVALUE 1 MAXVALUE 10 CACHE 100"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(2, 5, 8); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceIncreasingUsingMaxValueNoCycle() throws Exception { |
| nextConnection(); |
| // server throws exception |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar START WITH 8 INCREMENT BY 2 MINVALUE 1 MAXVALUE 10 CACHE 2"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(8, 10); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceDecreasingNoCycle() throws Exception { |
| nextConnection(); |
| // client will throw exception |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 4 INCREMENT BY -2 MINVALUE 1 MAXVALUE 10 CACHE 100"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(4, 2); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MIN_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceDecreasingUsingMinValueNoCycle() throws Exception { |
| nextConnection(); |
| // server will throw exception |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar START WITH 3 INCREMENT BY -2 MINVALUE 1 MAXVALUE 10 CACHE 2"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(3, 1); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MIN_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceIncreasingOverflowNoCycle() throws Exception { |
| nextConnection(); |
| // start with Long.MAX_VALUE |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar START WITH 9223372036854775807 INCREMENT BY 1 CACHE 10"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(Long.MAX_VALUE); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceIncreasingOverflowCycle() throws Exception { |
| nextConnection(); |
| // start with Long.MAX_VALUE |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 9223372036854775807 INCREMENT BY 9223372036854775807 CYCLE CACHE 10"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(Long.MAX_VALUE, Long.MIN_VALUE, -1, Long.MAX_VALUE - 1, |
| Long.MIN_VALUE, -1); |
| } |
| |
| @Test |
| public void testSequenceDecreasingOverflowNoCycle() throws Exception { |
| nextConnection(); |
| // start with Long.MIN_VALUE + 1 |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar START WITH -9223372036854775807 INCREMENT BY -1 CACHE 10"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(Long.MIN_VALUE + 1, Long.MIN_VALUE); |
| try { |
| ResultSet rs = conn.createStatement().executeQuery(NEXT_VAL_SQL); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MIN_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testSequenceDecreasingOverflowCycle() throws Exception { |
| nextConnection(); |
| // start with Long.MIN_VALUE + 1 |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH -9223372036854775807 INCREMENT BY -9223372036854775807 CYCLE CACHE 10"); |
| nextConnection(); |
| assertSequenceValuesForSingleRow(Long.MIN_VALUE + 1, Long.MAX_VALUE, 0, Long.MIN_VALUE + 1, |
| Long.MAX_VALUE, 0); |
| } |
| |
| @Test |
| public void testMultipleSequenceValuesNoCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar START WITH 1 INCREMENT BY 2 MINVALUE 1 MAXVALUE 10 CACHE 2"); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar2"); |
| nextConnection(); |
| assertSequenceValuesMultipleSeq(1, 3); |
| assertSequenceValuesMultipleSeq(5, 7); |
| |
| ResultSet rs = conn.prepareStatement(NEXT_VAL_SQL).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(9, rs.getInt(1)); |
| try { |
| assertTrue(rs.next()); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| |
| try { |
| rs = conn.prepareStatement(NEXT_VAL_SQL).executeQuery(); |
| rs.next(); |
| fail(); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| @Test |
| public void testMultipleSequenceValuesCycle() throws Exception { |
| nextConnection(); |
| conn.createStatement() |
| .execute( |
| "CREATE SEQUENCE foo.bar START WITH 1 INCREMENT BY 2 MINVALUE 1 MAXVALUE 10 CYCLE CACHE 2"); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar2"); |
| nextConnection(); |
| assertSequenceValuesMultipleSeq(1, 3); |
| assertSequenceValuesMultipleSeq(5, 7); |
| assertSequenceValuesMultipleSeq(9, 1); |
| assertSequenceValuesMultipleSeq(3, 5); |
| assertSequenceValuesMultipleSeq(7, 9); |
| assertSequenceValuesMultipleSeq(1, 3); |
| assertSequenceValuesMultipleSeq(5, 7); |
| } |
| |
| @Test |
| public void testUpsertSelectGroupByWithSequence() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE foo.bar"); |
| nextConnection(); |
| |
| conn.createStatement() |
| .execute( |
| "CREATE TABLE EVENTS (event_id BIGINT NOT NULL PRIMARY KEY, user_id char(15), val BIGINT )"); |
| conn.createStatement() |
| .execute( |
| "CREATE TABLE METRICS (metric_id char(15) NOT NULL PRIMARY KEY, agg_id char(15), metric_val INTEGER )"); |
| |
| nextConnection(); |
| // 2 rows for user1, 3 rows for user2 and 1 row for user3 |
| insertEvent(1, "user1", 1); |
| insertEvent(2, "user2", 1); |
| insertEvent(3, "user1", 1); |
| insertEvent(4, "user2", 1); |
| insertEvent(5, "user2", 1); |
| insertEvent(6, "user3", 1); |
| conn.commit(); |
| nextConnection(); |
| |
| conn.createStatement() |
| .execute( |
| "UPSERT INTO METRICS SELECT 'METRIC_'||(LPAD(ENCODE(NEXT VALUE FOR foo.bar,'base62'),5,'0')), user_id, sum(val) FROM events GROUP BY user_id ORDER BY user_id"); |
| conn.commit(); |
| nextConnection(); |
| |
| PreparedStatement stmt = |
| conn.prepareStatement("SELECT metric_id, agg_id, metric_val FROM METRICS"); |
| ResultSet rs = stmt.executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals("METRIC_00001", rs.getString("metric_id")); |
| assertEquals("user1", rs.getString("agg_id")); |
| assertEquals(2, rs.getLong("metric_val")); |
| assertTrue(rs.next()); |
| assertEquals("METRIC_00002", rs.getString("metric_id")); |
| assertEquals("user2", rs.getString("agg_id")); |
| assertEquals(3, rs.getLong("metric_val")); |
| assertTrue(rs.next()); |
| assertEquals("METRIC_00003", rs.getString("metric_id")); |
| assertEquals("user3", rs.getString("agg_id")); |
| assertEquals(1, rs.getLong("metric_val")); |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| /** |
| * Test to validate that the bug discovered in PHOENIX-2149 has been fixed. There was an issue |
| * whereby, when closing connections and returning sequences we were not setting the limit |
| * reached flag correctly and this was causing the max value to be ignored as the LIMIT_REACHED_FLAG |
| * value was being unset from true to false. |
| */ |
| public void testNextValuesForSequenceClosingConnections() throws Exception { |
| |
| // Create Sequence |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE seqtest.closeconn START WITH 4990 MINVALUE 4990 MAXVALUE 5000 CACHE 10"); |
| nextConnection(); |
| |
| // Call NEXT VALUE FOR 1 time more than available values in the Sequence. We expected the final time |
| // to throw an error as we will have reached the max value |
| try { |
| long val = 0L; |
| for (int i = 0; i <= 11; i++) { |
| ResultSet rs = conn.createStatement().executeQuery(String.format(SELECT_NEXT_VALUE_SQL, "seqtest.closeconn")); |
| rs.next(); |
| val = rs.getLong(1); |
| nextConnection(); |
| } |
| fail("Expect to fail as we have arrived at the max sequence value " + val); |
| } catch (SQLException e) { |
| assertEquals(SQLExceptionCode.SEQUENCE_VAL_REACHED_MAX_VALUE.getErrorCode(), |
| e.getErrorCode()); |
| assertTrue(e.getNextException() == null); |
| } |
| } |
| |
| private void insertEvent(long id, String userId, long val) throws SQLException { |
| PreparedStatement stmt = conn.prepareStatement("UPSERT INTO events VALUES(?,?,?)"); |
| stmt.setLong(1, id); |
| stmt.setString(2, userId); |
| stmt.setLong(3, val); |
| stmt.execute(); |
| } |
| |
| /** |
| * Helper to verify the sequence values returned in multiple ResultSets each containing one row |
| * @param seqVals expected sequence values (one per ResultSet) |
| */ |
| private void assertSequenceValuesForSingleRow(long... seqVals) |
| throws SQLException { |
| PreparedStatement stmt = conn.prepareStatement(NEXT_VAL_SQL); |
| for (long seqVal : seqVals) { |
| ResultSet rs = stmt.executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(seqVal, rs.getLong(1)); |
| assertFalse(rs.next()); |
| rs.close(); |
| } |
| stmt.close(); |
| } |
| |
| /** |
| * Helper to verify the sequence values returned in a single ResultSet containing multiple row |
| * @param seqVals expected sequence values (from one ResultSet) |
| */ |
| private void assertSequenceValuesMultipleSeq(long... seqVals) throws SQLException { |
| PreparedStatement stmt = conn.prepareStatement(NEXT_VAL_SQL); |
| ResultSet rs = stmt.executeQuery(); |
| for (long seqVal : seqVals) { |
| assertTrue(rs.next()); |
| assertEquals(seqVal, rs.getLong(1)); |
| } |
| assertFalse(rs.next()); |
| rs.close(); |
| stmt.close(); |
| } |
| |
| private void verifyExceptions(SQLException sqlE, List<String> expectedExceptions) { |
| List<String> missingExceptions = Lists.newArrayList(expectedExceptions); |
| List<String> unexpectedExceptions = Lists.newArrayList(); |
| do { |
| if (!expectedExceptions.contains(sqlE.getMessage())) { |
| unexpectedExceptions.add(sqlE.getMessage()); |
| } |
| missingExceptions.remove(sqlE.getMessage()); |
| } while ((sqlE = sqlE.getNextException()) != null); |
| if (unexpectedExceptions.size() != 0 && missingExceptions.size() != 0) { |
| fail("Actual exceptions does not match expected exceptions. Unexpected exceptions : " |
| + unexpectedExceptions + " missing exceptions : " + missingExceptions); |
| } |
| } |
| |
| @Test |
| public void testValidateBeforeReserve() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute( |
| "CREATE TABLE foo (k VARCHAR PRIMARY KEY, l BIGINT)"); |
| conn.createStatement().execute( |
| "CREATE SEQUENCE foo.bar"); |
| |
| nextConnection(); |
| ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT NEXT VALUE FOR foo.bar FROM foo"); |
| assertTrue(rs.next()); |
| conn.createStatement().execute( |
| "UPSERT INTO foo VALUES ('a', NEXT VALUE FOR foo.bar)"); |
| conn.createStatement().execute( |
| "UPSERT INTO foo VALUES ('b', NEXT VALUE FOR foo.bar)"); |
| conn.commit(); |
| |
| nextConnection(); |
| rs = conn.createStatement().executeQuery("SELECT * FROM foo"); |
| assertTrue(rs.next()); |
| assertEquals("a",rs.getString(1)); |
| assertEquals(1,rs.getLong(2)); |
| assertTrue(rs.next()); |
| assertEquals("b",rs.getString(1)); |
| assertEquals(2,rs.getLong(2)); |
| assertFalse(rs.next()); |
| |
| nextConnection(); |
| PreparedStatement stmt = conn.prepareStatement("SELECT NEXT VALUE FOR foo.bar FROM foo"); |
| ParameterMetaData md = stmt.getParameterMetaData(); |
| assertEquals(0,md.getParameterCount()); |
| rs = stmt.executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(3, rs.getLong(1)); |
| assertTrue(rs.next()); |
| assertEquals(4, rs.getLong(1)); |
| assertFalse(rs.next()); |
| } |
| |
| @Test |
| public void testNoFromClause() throws Exception { |
| ResultSet rs; |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE myseq START WITH 1 INCREMENT BY 1"); |
| conn.createStatement().execute("CREATE SEQUENCE anotherseq START WITH 2 INCREMENT BY 3"); |
| nextConnection(); |
| rs = conn.createStatement().executeQuery("EXPLAIN SELECT NEXT VALUE FOR myseq"); |
| assertEquals("CLIENT RESERVE VALUES FROM 1 SEQUENCE", QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery("SELECT NEXT VALUE FOR myseq"); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| rs = conn.createStatement().executeQuery("EXPLAIN SELECT CURRENT VALUE FOR myseq"); |
| assertEquals("CLIENT RESERVE VALUES FROM 1 SEQUENCE", QueryUtil.getExplainPlan(rs)); |
| rs = conn.createStatement().executeQuery("SELECT CURRENT VALUE FOR myseq"); |
| assertTrue(rs.next()); |
| assertEquals(1, rs.getInt(1)); |
| rs = conn.createStatement().executeQuery("SELECT NEXT VALUE FOR myseq, NEXT VALUE FOR anotherseq"); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertEquals(2, rs.getInt(2)); |
| rs = conn.createStatement().executeQuery("SELECT CURRENT VALUE FOR myseq, NEXT VALUE FOR anotherseq"); |
| assertTrue(rs.next()); |
| assertEquals(2, rs.getInt(1)); |
| assertEquals(5, rs.getInt(2)); |
| } |
| |
| @Test |
| public void testReturnAllSequencesNotCalledForNoOpenConnections() throws Exception { |
| nextConnection(); |
| conn.createStatement().execute("CREATE SEQUENCE alpha.zeta START WITH 3 INCREMENT BY 2 CACHE 5"); |
| nextConnection(); |
| String query = "SELECT NEXT VALUE FOR alpha.zeta FROM SYSTEM.\"SEQUENCE\""; |
| ResultSet rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(3, rs.getInt(1)); |
| assertFalse(rs.next()); |
| rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(5, rs.getInt(1)); |
| assertFalse(rs.next()); |
| conn.close(); |
| |
| // verify that calling close() does not return sequence values back to the server |
| query = "SELECT CURRENT_VALUE FROM SYSTEM.\"SEQUENCE\" WHERE SEQUENCE_SCHEMA='ALPHA' AND SEQUENCE_NAME='ZETA'"; |
| rs = conn.prepareStatement(query).executeQuery(); |
| assertTrue(rs.next()); |
| assertEquals(13, rs.getInt(1)); |
| assertFalse(rs.next()); |
| } |
| |
| } |