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 !! ");
}
}
}