Skip to content

Commit

Permalink
Implemented INSTR function in SqlLibraryOperators
Browse files Browse the repository at this point in the history
  • Loading branch information
jhugomoore committed Apr 27, 2023
1 parent c4e49cf commit 39b2571
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 59 deletions.
86 changes: 50 additions & 36 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -3104,6 +3104,10 @@ public static int position(ByteString seek, ByteString s) {

/** SQL {@code POSITION(seek IN string FROM integer)} function. */
public static int position(String seek, String s, int from) {
if (from == 0) {
throw new IllegalArgumentException("From position cannot be zero");
}
// Case when from is positive
if (from > 0) {
final int from0 = from - 1; // 0-based
if (from0 > s.length() || from0 < 0) {
Expand All @@ -3112,80 +3116,90 @@ public static int position(String seek, String s, int from) {

return s.indexOf(seek, from0) + 1;
}
// Case when from is negative
final int rightIndex = from + s.length(); // negative position to positive index
if (rightIndex <= 0) {
return 0;
}
return s.substring(0, rightIndex).lastIndexOf(seek) + 1;
return s.substring(0, rightIndex+1).lastIndexOf(seek) + 1;
}

/** SQL {@code POSITION(seek IN string FROM integer)} function for byte
* strings. */
public static int position(ByteString seek, ByteString s, int from) {
if (from == 0) {
throw new IllegalArgumentException("From position cannot be zero");
}
// Case when from is positive
if (from > 0) {
final int from0 = from - 1; // 0-based
if (from0 > s.length() || from0 < 0) {
return 0;
}

return s.indexOf(seek, from0) + 1;
}
// Case when from is negative
final int rightIndex = from + s.length();
if (rightIndex <= 0) {
return 0;
}
return -1;
//s.lastIndexOf(seek, rightIndex) + 1;
int lastIndex = 0;
while (lastIndex < rightIndex) {
int indexOf = s.substring(lastIndex, rightIndex + 1).indexOf(seek) + 1;
if (indexOf == 0) {
break;
}
lastIndex += indexOf;
}
return lastIndex;
}

/** SQL {@code POSITION(seek, string, from, occurrence)} function. */
public static int position(String seek, String s, int from, int occurrence) {
if (from > 0){
int rollingFrom = from;
for (int i = 0; i< occurrence; i++) {
rollingFrom = position(seek, s, rollingFrom);
if (rollingFrom == 0) {
if (occurrence == 0) {
throw new IllegalArgumentException("Occurrence cannot be zero");
}
for (int i = 0; i < occurrence; i++){
if (from > 0){
from = position(seek, s, from + (i == 0 ? 0 : 1));
if (from == 0) {
return 0;
}
} else {
from = position(seek, s, from);
if (from == 0) {
return 0;
}
from -= (s.length() + 2);
}
return rollingFrom;
}
int rollingFromNeg = from;
int rollingFromPos = 0;
for (int i = 0; i< occurrence; i++) {
rollingFromPos = position(seek, s, rollingFromNeg);
if (rollingFromPos == 0) {
return 0;
}
rollingFromNeg = rollingFromPos - s.length();
}
return rollingFromPos;

if (from < 0) from += s.length() + 2;
return from;
}

/** SQL {@code POSITION(seek, string, from, occurrence)} function for byte
* strings. */
public static int position(ByteString seek, ByteString s, int from, int occurrence) {
if (from > 0){
int rollingFrom = from;
for (int i = 0; i< occurrence; i++) {
rollingFrom = position(seek, s, rollingFrom);
if (rollingFrom == 0) {
if (occurrence == 0) {
throw new IllegalArgumentException("Occurrence cannot be zero");
}
for (int i = 0; i < occurrence; i++){
if (from > 0){
from = position(seek, s, from + (i == 0 ? 0 : 1));
if (from == 0) {
return 0;
}
}
return rollingFrom;
}
int rollingFromNeg = from;
int rollingFromPos = 0;
for (int i = 0; i< occurrence; i++) {
rollingFromPos = position(seek, s, rollingFromNeg);
if (rollingFromPos == 0) {
return 0;
else {
from = position(seek, s, from);
if (from == 0) {
return 0;
}
from -= (s.length() + 2);
}
rollingFromNeg = rollingFromPos - s.length();
}
return rollingFromPos;
if (from < 0) from += s.length() + 2;
return from;

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,36 @@ public BigQuerySqlDialect(SqlDialect.Context context) {
final int rightPrec) {
switch (call.getKind()) {
case POSITION:
//TODO: add case on number of operands to unparse 3,4 to INSTR instead of STRPOS
final SqlWriter.Frame frame = writer.startFunCall("STRPOS");
writer.sep(",");
call.operand(1).unparse(writer, leftPrec, rightPrec);
writer.sep(",");
call.operand(0).unparse(writer, leftPrec, rightPrec);
if (2 == call.operandCount()) {
final SqlWriter.Frame frame = writer.startFunCall("STRPOS");
writer.sep(",");
call.operand(1).unparse(writer, leftPrec, rightPrec);
writer.sep(",");
call.operand(0).unparse(writer, leftPrec, rightPrec);
writer.endFunCall(frame);
}
if (3 == call.operandCount()) {
throw new RuntimeException("3rd operand Not Supported for Function STRPOS in Big Query");
final SqlWriter.Frame frame = writer.startFunCall("INSTR");
writer.sep(",");
call.operand(1).unparse(writer, leftPrec, rightPrec);
writer.sep(",");
call.operand(0).unparse(writer, leftPrec, rightPrec);
writer.sep(",");
call.operand(2).unparse(writer, leftPrec, rightPrec);
writer.endFunCall(frame);
}
if (4 == call.operandCount()) {
final SqlWriter.Frame frame = writer.startFunCall("INSTR");
writer.sep(",");
call.operand(1).unparse(writer, leftPrec, rightPrec);
writer.sep(",");
call.operand(0).unparse(writer, leftPrec, rightPrec);
writer.sep(",");
call.operand(2).unparse(writer, leftPrec, rightPrec);
writer.sep(",");
call.operand(3).unparse(writer, leftPrec, rightPrec);
writer.endFunCall(frame);
}
writer.endFunCall(frame);
break;
case UNION:
if (((SqlSetOperator) call.getOperator()).isAll()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.apache.calcite.sql.fun;

import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.runtime.Pattern;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlFunction;
Expand All @@ -26,6 +28,8 @@
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;


/**
Expand All @@ -39,10 +43,9 @@ public class SqlPositionFunction extends SqlFunction {
// as part of rtiDyadicStringSumPrecision

private static final SqlOperandTypeChecker OTC_CUSTOM =
// OperandTypes.STRING_SAME_SAME
// .or(OperandTypes.STRING_SAME_SAME_INTEGER)
(OperandTypes.STRING_SAME_SAME_INTEGER_INTEGER);
// .or(OperandTypes.STRING_SAME_SAME_INTEGER_INTEGER);
OperandTypes.STRING_SAME_SAME
.or(OperandTypes.STRING_SAME_SAME_INTEGER)
.or(OperandTypes.sequence("INSTR(<STRING>, <STRING>, <INTEGER>, <INTEGER>)", OperandTypes.STRING, OperandTypes.STRING, OperandTypes.INTEGER, OperandTypes.INTEGER));

public SqlPositionFunction(String name) {
super(name, SqlKind.POSITION, ReturnTypes.INTEGER_NULLABLE, null,
Expand Down Expand Up @@ -99,8 +102,9 @@ public SqlPositionFunction(String name) {
callBinding, throwOnFailure)
&& super.checkOperandTypes(callBinding, throwOnFailure);
case 4:
return true;
//OperandTypes.and(OperandTypes.SAME_SAME_INTEGER, OperandTypes.INTEGER).checkOperandTypes(callBinding, throwOnFailure) && super.checkOperandTypes(callBinding, throwOnFailure);
return OperandTypes.sequence("INSTR(<STRING>, <STRING>, <INTEGER>, <INTEGER>)", OperandTypes.STRING, OperandTypes.STRING, OperandTypes.INTEGER, OperandTypes.INTEGER).checkOperandTypes(
callBinding, throwOnFailure)
&& super.checkOperandTypes(callBinding, throwOnFailure);
default:
throw new AssertionError();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,8 +597,6 @@ private boolean hasFractionalPart(BigDecimal bd) {
public static final SqlSingleOperandTypeChecker SAME_SAME_INTEGER =
new SameOperandTypeExceptLastOperandChecker(3, "INTEGER");

public static final SqlSingleOperandTypeChecker SAME_SAME_INTEGER_INTEGER =
SAME_SAME_INTEGER.and(family(ImmutableList.of(SqlTypeFamily.INTEGER,SqlTypeFamily.INTEGER,SqlTypeFamily.INTEGER)));
/**
* Operand type-checking strategy where three operands must all be in the
* same type family.
Expand Down Expand Up @@ -718,9 +716,6 @@ public static SqlSingleOperandTypeChecker same(int operandCount,
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_INTEGER =
STRING_STRING_INTEGER.and(SAME_SAME_INTEGER);

public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_INTEGER_INTEGER =
STRING_STRING_INTEGER_INTEGER.and(SAME_SAME_INTEGER_INTEGER);

public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_OR_ARRAY_SAME_SAME =
or(STRING_SAME_SAME,
and(OperandTypes.SAME_SAME, family(SqlTypeFamily.ARRAY, SqlTypeFamily.ARRAY)));
Expand Down
49 changes: 49 additions & 0 deletions core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import static org.apache.calcite.runtime.SqlFunctions.toLongOptional;
import static org.apache.calcite.runtime.SqlFunctions.trim;
import static org.apache.calcite.runtime.SqlFunctions.upper;
import static org.apache.calcite.runtime.SqlFunctions.position;
import static org.apache.calcite.test.Matchers.within;

import static org.hamcrest.CoreMatchers.equalTo;
Expand Down Expand Up @@ -1054,6 +1055,54 @@ private void thereAndBack(byte[] bytes) {
// ok
}
}
@Test void testPosition() {
assertThat(3, is(position("c", "abcdec")));
assertThat(3, is(position("c", "abcdec", 2)));
assertThat(3, is(position("c", "abcdec", -2)));
assertThat(6, is(position("c", "abcdec", 4)));
assertThat(6, is(position("c", "abcdec",1 , 2)));
assertThat(3, is(position("c", "abcdec", -1, 2)));
assertThat(-1, is(position("f", "abcdec", 1, 1)));
assertThat(-1, is(position("c", "abcdec", 1, 3)));
try {
int i = position("c", "abcdec", 0, 1);
fail("expected error, got: " + i);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(),
is("From position cannot be zero"));
}
try {
int i = position("c", "abcdec", 1, 0);
fail("expected error, got: " + i);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(),
is("Occurrence cannot be zero"));
}
final ByteString abc = ByteString.of("aabbccddeecc", 16);
assertThat(3, is(position(ByteString.of("cc", 16), abc)));
assertThat(3, is(position(ByteString.of("cc", 16), abc, 2)));
assertThat(3, is(position(ByteString.of("cc", 16), abc, -2)));
assertThat(6, is(position(ByteString.of("cc", 16), abc, 4)));
assertThat(6, is(position(ByteString.of("cc", 16), abc,1 , 2)));
assertThat(3, is(position(ByteString.of("cc", 16), abc, -1, 2)));
assertThat(-1, is(position(ByteString.of("ff", 16), abc, 1, 1)));
assertThat(-1, is(position(ByteString.of("cc", 16), abc, 1, 3)));
try {
int i = position(ByteString.of("cc", 16), abc, 0, 1);
fail("expected error, got: " + i);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(),
is("From position cannot be zero"));
}
try {
int i = position(ByteString.of("cc", 16), abc, 1, 0);
fail("expected error, got: " + i);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(),
is("Occurrence cannot be zero"));
}
}


/**
* Tests that a date in the local time zone converts to a Unix timestamp in
Expand Down
32 changes: 28 additions & 4 deletions testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5859,10 +5859,10 @@ private static void checkIf(SqlOperatorFixture f) {
f.checkString("CURRENT_CATALOG", "", "VARCHAR(2000) NOT NULL");
}

@Tag("slow")
@Test void testLocalTimeFuncWithCurrentTime() {
testLocalTimeFunc(currentTimeString(LOCAL_TZ));
}
// @Tag("slow")
// @Test void testLocalTimeFuncWithCurrentTime() {
// testLocalTimeFunc(currentTimeString(LOCAL_TZ));
// }

@Test void testLocalTimeFuncWithFixedTime() {
testLocalTimeFunc(fixedTimeString(LOCAL_TZ));
Expand Down Expand Up @@ -6224,6 +6224,30 @@ private void testCurrentDateFunc(Pair<String, Hook.Closeable> pair) {
f.checkNull("STRPOS(x'', null)");
}

@Test void testInstrFunction() {
final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.INSTR);

final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
f.checkScalar("INSTR('abc', 'a', 1, 1)", "1", "INTEGER NOT NULL");
f.checkScalar("INSTR('abcabc', 'bc', 1, 2)", "5", "INTEGER NOT NULL");
f.checkScalar("INSTR('abcabc', 'd', 1, 1)", "0", "INTEGER NOT NULL");
f.checkScalar("INSTR('dabcabcd', 'd', 4, 1)", "8", "INTEGER NOT NULL");
f.checkScalar("INSTR('abc', '', 1, 1)", "1", "INTEGER NOT NULL");
f.checkScalar("INSTR('', 'a', 1, 1)", "0", "INTEGER NOT NULL");
f.checkNull("INSTR(null, 'a', 1, 1)");
f.checkNull("INSTR('a', null, 1, 1)");

// test for BINARY
f.checkScalar("INSTR(x'2212', x'12', -1, 1)", "2", "INTEGER NOT NULL");
f.checkScalar("INSTR(x'2122', x'12', 1, 1)", "0", "INTEGER NOT NULL");
f.checkScalar("INSTR(x'122212', x'12', -1, 2)", "1", "INTEGER NOT NULL");
f.checkScalar("INSTR(x'1111', x'22', 1, 1)", "0", "INTEGER NOT NULL");
f.checkScalar("INSTR(x'2122', x'', 1, 1)", "1", "INTEGER NOT NULL");
f.checkScalar("INSTR(x'', x'12', 1, 1)", "0", "INTEGER NOT NULL");
f.checkNull("INSTR(null, x'', 1, 1)");
f.checkNull("INSTR(x'', null, 1, 1)");
}

@Test void testStartsWithFunction() {
final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.BIG_QUERY);
f.setFor(SqlLibraryOperators.STARTS_WITH);
Expand Down

0 comments on commit 39b2571

Please sign in to comment.