/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.rest.dml;
import com.foundationdb.ais.model.Index;
import com.foundationdb.ais.model.aisb2.AISBBasedBuilder;
import com.foundationdb.ais.model.aisb2.NewAISBuilder;
import com.foundationdb.ais.model.aisb2.NewTableBuilder;
import com.foundationdb.server.error.KeyColumnMismatchException;
import com.foundationdb.server.error.NoSuchColumnException;
import com.foundationdb.server.types.mcompat.mtypes.MTypesTranslator;
import com.foundationdb.server.types.service.TestTypesRegistry;
import com.foundationdb.util.WrappingByteSource;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
public class PrimaryKeyParserTest {
private static Index createIndex(int colCount, boolean binary) {
NewAISBuilder builder = AISBBasedBuilder.create("test",
MTypesTranslator.INSTANCE);
String[] colNames = new String[colCount];
NewTableBuilder table = builder.table("t");
char colName = 'a';
for(int i = 0; i < colCount; ++i) {
colNames[i] = String.valueOf(colName++);
if (binary) {
table.colVarBinary(colNames[i], 16, false);
}
else {
table.colInt(colNames[i], false);
}
}
table.pk(colNames);
return builder.ais(true).getTable("test", "t").getPrimaryKey().getIndex();
}
private static void test(String input, Index pk, List<List<Object>> expected) {
List<List<Object>> actual = PrimaryKeyParser.parsePrimaryKeys(input, pk);
assertEquals(expected, actual);
}
private static List<List<Object>> llist(Object... pks) {
List<List<Object>> list = new ArrayList<>();
for(Object s : pks) {
list.add(asList(s));
}
return list;
}
private static List<List<Object>> blist(int... ints) {
byte[] bytes = new byte[ints.length];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte)ints[i];
}
return asList(asList((Object)new WrappingByteSource(bytes)));
}
private static String urlEnc(String s) {
try {
return URLEncoder.encode(s, "UTF8");
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@Test
public void singleColumn() {
Index pk = createIndex(1, false);
test("1", pk, llist("1"));
test("1;2", pk, llist("1", "2"));
test("-10;10;-100;100", pk, llist("-10", "10", "-100", "100"));
}
@Test
public void multiColumn() {
Index pk = createIndex(2, false);
test("10,100", pk, asList(asList((Object)"10", (Object)"100")));
test("20,200;30,300", pk, asList(asList((Object)"20", (Object)"200"), asList((Object)"30", (Object)"300")));
pk = createIndex(3, false);
test("1,2,3;4,5,6;7,8,9", pk, asList(asList((Object)"1", (Object)"2", (Object)"3"), asList((Object)"4", (Object)"5", (Object)"6"), asList((Object)"7", (Object)"8", (Object)"9")));
}
@Test
public void columnNameQualified() {
Index pk = createIndex(1, false);
test("a=1", pk, llist("1"));
test("a=1;a=2;a=10", pk, llist("1", "2", "10"));
pk = createIndex(3, false);
test("a=1,b=2,c=3;a=10,b=20,c=30", pk, asList(asList((Object)"1", (Object)"2", (Object)"3"), asList((Object)"10", (Object)"20", (Object)"30")));
}
@Test
public void urlEncoded() {
Index pk = createIndex(1, false);
test(urlEnc("zip zap"), pk, llist("zip zap"));
test(urlEnc("a=b=c=d"), pk, llist("a=b=c=d"));
test(urlEnc("☃"), pk, llist("☃"));
test(urlEnc("foo,bar") + ";" + urlEnc("cab;cop"), pk, llist("foo,bar", "cab;cop"));
pk = createIndex(2, false);
test("a=" + urlEnc("Acme/Corp") + ",b=" + urlEnc("@example.com"), pk, asList(asList((Object)"Acme/Corp", (Object)"@example.com")));
}
@Test
public void outOfOrderQualified() {
Index pk = createIndex(3, false);
test("a=1,b=2,c=3", pk, asList(asList((Object)"1", (Object)"2", (Object)"3")));
test("c=3,b=2,a=1", pk, asList(asList((Object)"1", (Object)"2", (Object)"3")));
test("b=2,c=3,a=1", pk, asList(asList((Object)"1", (Object)"2", (Object)"3")));
}
@Test
public void mixedQualifiedSeparatePKs() {
Index pk = createIndex(1, false);
test("a=1;2;a=3", pk, llist("1", "2", "3"));
test("4;a=5;6", pk, llist("4", "5", "6"));
}
@Test(expected=KeyColumnMismatchException.class)
public void underColumn() {
Index pk = createIndex(2, false);
test("1", pk, llist("1"));
}
@Test(expected=KeyColumnMismatchException.class)
public void overColumn() {
Index pk = createIndex(1, false);
test("1,2", pk, asList(asList((Object)"1", (Object)"2")));
}
@Test(expected=KeyColumnMismatchException.class)
public void columnNotInIndex() {
Index pk = createIndex(1, false);
test("z=1", pk, llist("1"));
}
@Test(expected=KeyColumnMismatchException.class)
public void mixedQualifiedSamePK() {
Index pk = createIndex(2, false);
test("a=1,2", pk, asList(asList((Object)"1", (Object)"2")));
}
@Test
public void urlEncodedBinary() {
Index pk = createIndex(1, true);
test("a=%01%02%03", pk, blist(0x01, 0x02, 0x03));
// NOTE: This does not work as a URL component because Jetty gets an error
// setting up the decoded path component before calling our handler.
test("a=%FF%FE%FD", pk, blist(0xFF, 0xFE, 0xFD));
}
@Test
public void hexBinary() {
Index pk = createIndex(1, true);
test("a=hex:010203", pk, blist(0x01, 0x02, 0x03));
test("a=hex:FFFEFD", pk, blist(0xFF, 0xFE, 0xFD));
}
@Test
public void base64Binary() {
Index pk = createIndex(1, true);
test("a=base64:AQIDBA==", pk, blist(0x01, 0x02, 0x03, 0x04));
// NOTE: This could not actually be a URL component because of the slash.
test("a=base64://79/A==", pk, blist(0xFF, 0xFE, 0xFD, 0xFC));
}
}