Hi All,

I have come across a curious case with Phoenix (4.14.1) cursors where a
particular query would carry on looping forever if executed when a local
index is present. If however, I execute the same query without a local
index on the table, then it works as expected.

Please find attached a standalone test case that you should be able to run
to reproduce this problem (though you may need to modify the JDBC_URL
constant). You can run it either with CREATE_INDEX = true or CREATE_INDEX =
false. Below are the outputs

With : CREATE_INDEX = true;

Connecting To : jdbc:phoenix:localhost:63214
CREATE TABLE IF NOT EXISTS SOME_NUMBERS
(
   ID                             VARCHAR    NOT NULL,
   NAME                           VARCHAR    ,
   ANOTHER_VALUE                  VARCHAR    ,
   TRANSACTION_TIME               TIMESTAMP  ,
   CONSTRAINT pk PRIMARY KEY(ID)
) IMMUTABLE_STORAGE_SCHEME=ONE_CELL_PER_COLUMN,
UPDATE_CACHE_FREQUENCY=900000,
COLUMN_ENCODED_BYTES=NONE,
IMMUTABLE_ROWS=true

Creating Index: CREATE LOCAL INDEX index_01 ON SOME_NUMBERS(NAME,
TRANSACTION_TIME DESC) INCLUDE(ANOTHER_VALUE)

Inserting Some Items

Doing Count SQL : SELECT COUNT(1) AS TOTAL_ITEMS FROM SOME_NUMBERS
TOTAL COUNT : 10

Cursor SQL : DECLARE MyCursor CURSOR FOR SELECT NAME,ANOTHER_VALUE FROM
SOME_NUMBERS where NAME like 'ReferenceData.Country/%' ORDER BY
TRANSACTION_TIME DESC
ITEMS returned by count : 10 | Items Returned by Cursor : 40
Aborting the Cursor, as it is more than the count!
Exception in thread "main" java.lang.RuntimeException: The cursor returned
a different number of rows from the count !!

With : CREATE_INDEX = false;

Connecting To : jdbc:phoenix:localhost:63214
CREATE TABLE IF NOT EXISTS SOME_NUMBERS
(
   ID                             VARCHAR    NOT NULL,
   NAME                           VARCHAR    ,
   ANOTHER_VALUE                  VARCHAR    ,
   TRANSACTION_TIME               TIMESTAMP  ,
   CONSTRAINT pk PRIMARY KEY(ID)
) IMMUTABLE_STORAGE_SCHEME=ONE_CELL_PER_COLUMN,
UPDATE_CACHE_FREQUENCY=900000,
COLUMN_ENCODED_BYTES=NONE,
IMMUTABLE_ROWS=true

Not Creating the Index

Inserting Some Items

Doing Count SQL : SELECT COUNT(1) AS TOTAL_ITEMS FROM SOME_NUMBERS
TOTAL COUNT : 10

Cursor SQL : DECLARE MyCursor CURSOR FOR SELECT NAME,ANOTHER_VALUE FROM
SOME_NUMBERS where NAME like 'ReferenceData.Country/%' ORDER BY
TRANSACTION_TIME DESC
CLOSING THE CURSOR
Result : 0
ITEMS returned by count : 10 | Items Returned by Cursor : 10
ALL GOOD - No Exception

Any idea what might be going on here?

Regards,
package com.jsteenkamp.phoenix;

import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.util.UUID;

/**
 *  Reproducing the eternal cursor issue.
 */
public class PhoenixEternalCursorTest
{
	private final boolean CREATE_INDEX = true; //With false this works, with true it doesn't
	
	private final String JDBC_URL = "jdbc:phoenix:localhost:63214";
	
	private final java.sql.Connection conn;
	private final Statement stmt;
	
	public static void main(String... args) throws Exception 
	{
		PhoenixEternalCursorTest ectest = new PhoenixEternalCursorTest();
		ectest.testEternalCursor();
		System.out.println("ALL GOOD - No Exception");
	}
	
	public PhoenixEternalCursorTest() throws Exception
	{
		System.out.println("Connecting To : " + JDBC_URL);
		conn = DriverManager.getConnection(JDBC_URL, "sa", "");
		stmt = conn.createStatement();
	}
	
	public void testEternalCursor() throws Exception
	{
		stmt.execute("DROP TABLE IF EXISTS SOME_NUMBERS");
		String createTable = "CREATE TABLE IF NOT EXISTS SOME_NUMBERS\n" + 
				"(	\n" + 
				"   ID                             VARCHAR    NOT NULL,\n" + 
				"   NAME                           VARCHAR    ,\n" +
				"   ANOTHER_VALUE                  VARCHAR    ,\n" + 
				"   TRANSACTION_TIME               TIMESTAMP  ,\n" +
				"   CONSTRAINT pk PRIMARY KEY(ID)\n" + 
			    ") " +
				"IMMUTABLE_STORAGE_SCHEME=ONE_CELL_PER_COLUMN,\n" + 
				"UPDATE_CACHE_FREQUENCY=900000,\n" + 
				"COLUMN_ENCODED_BYTES=NONE,\n" + 
				"IMMUTABLE_ROWS=true";
		
		String createIndex = "CREATE LOCAL INDEX index_01 ON SOME_NUMBERS(NAME, TRANSACTION_TIME DESC) INCLUDE(ANOTHER_VALUE)";
		System.out.println(createTable);
	
		stmt.execute(createTable);
		
		if(CREATE_INDEX == true)
		{
			System.out.println("\nCreating Index: " + createIndex + "\n");
			stmt.execute(createIndex);
		}
		else
		{
			System.out.println("\nNot Creating the Index\n");
		}
		
		//Insert Some  Items.
		System.out.println("\nInserting Some Items");
		DecimalFormat dmf = new DecimalFormat("0000");
		final String prefix = "ReferenceData.Country/";
		for (int i = 0; i < 5; i++)
		{
			for (int j = 0; j < 2; j++)
			{
				PreparedStatement prstmt = conn.prepareStatement("UPSERT INTO SOME_NUMBERS VALUES(?,?,?,?)");
				prstmt.setString(1,UUID.randomUUID().toString());
				prstmt.setString(2,prefix + dmf.format(i));
				prstmt.setString(3,UUID.randomUUID().toString());
				prstmt.setTimestamp(4, new Timestamp(System.currentTimeMillis()));
				prstmt.execute();
				conn.commit();
				prstmt.close();
			}
		}
 
		String countSQL = "SELECT COUNT(1) AS TOTAL_ITEMS FROM SOME_NUMBERS ";
		System.out.println("\nDoing Count SQL : " + countSQL);
		ResultSet rs = stmt.executeQuery(countSQL);
		rs.next();
		final int totalCount = rs.getInt("TOTAL_ITEMS");
		System.out.println("TOTAL COUNT : " + totalCount + "\n");
		rs.close();
		
		//Now a Cursor
		String cursorSQL = "DECLARE MyCursor CURSOR FOR SELECT NAME,ANOTHER_VALUE FROM SOME_NUMBERS where NAME like 'ReferenceData.Country/%' ORDER BY TRANSACTION_TIME DESC";
		System.out.println("\nCursor SQL : " + cursorSQL);
		PreparedStatement cursorStatement = conn.prepareStatement(cursorSQL);
		cursorStatement.execute(); 
		PreparedStatement openCursorStatement = conn.prepareStatement("OPEN MyCursor");
		openCursorStatement.execute();
		
		PreparedStatement next10Rows = conn.prepareStatement("FETCH NEXT 10 ROWS FROM MyCursor");
		int itemsReturnedByCursor = 0;
		while(true)
		{
			ResultSet cursorRS = next10Rows.executeQuery();
			int rowsReadBeforeEmpty = 0;
			while(cursorRS.next())
			{
				itemsReturnedByCursor++;
				rowsReadBeforeEmpty++;
			}
			if(rowsReadBeforeEmpty > 0 )
			{
				cursorRS.close();
			}
			else
			{
				System.out.println("CLOSING THE CURSOR");
				int res = conn.prepareStatement("CLOSE MyCursor").executeUpdate();
				System.out.println("Result : " + res);
				break;
			}
			
			if(itemsReturnedByCursor > (totalCount * 3)) 
			{
				System.err.println("Aborting the Cursor, as it is more than the count!");
				break;
			}
		}
		System.out.println("ITEMS returned by count : " + totalCount + " | Items Returned by Cursor : " + itemsReturnedByCursor  );
		if(totalCount != itemsReturnedByCursor) 
		{
			throw new RuntimeException("The cursor returned a different number of rows from the count !! ");
		}
	}
	
}

















Reply via email to