package org.basex.server;
import static org.basex.query.func.Function.*;
import static org.basex.util.Token.*;
import static org.junit.Assert.*;
import java.io.*;
import org.basex.*;
import org.basex.api.client.*;
import org.basex.api.dom.*;
import org.basex.core.*;
import org.basex.core.cmd.*;
import org.basex.io.in.*;
import org.basex.io.out.*;
import org.basex.io.serial.*;
import org.basex.query.util.list.*;
import org.basex.query.value.item.*;
import org.basex.query.value.node.*;
import org.basex.query.value.seq.*;
import org.basex.query.value.type.*;
import org.basex.util.*;
import org.junit.*;
import org.junit.Test;
/**
* This class tests the client/server query API.
*
* @author BaseX Team 2005-17, BSD License
* @author Christian Gruen
*/
public abstract class SessionTest extends SandboxTest {
/** Output stream. */
ArrayOutput out;
/** Client session. */
Session session;
/** Stops a session. */
@After
public final void stopSession() {
try {
session.execute(new DropDB(NAME));
session.close();
} catch(final IOException ex) {
fail(Util.message(ex));
}
}
/**
* Runs a query command and retrieves the result as string.
* @throws IOException I/O exception
*/
@Test
public final void command() throws IOException {
assertEqual("A", session.execute("xquery 'A'"));
}
/** Runs an erroneous query command.
* @throws IOException I/O exception */
@Test(expected = BaseXException.class)
public final void commandError() throws IOException {
session.execute("xquery (");
}
/**
* Runs a query command and retrieves the result as string.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void commandErr() throws IOException {
session.execute("1,<a/>+''");
}
/**
* Creates new databases.
* @throws IOException I/O exception
*/
@Test
public final void create() throws IOException {
session.create(NAME, new ArrayInput(""));
assertEqual("", session.query(_DB_OPEN.args(NAME)).execute());
session.create(NAME, new ArrayInput("<X/>"));
assertEqual("<X/>", session.query(_DB_OPEN.args(NAME)).execute());
}
/**
* Stops because of invalid input.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void createErr() throws IOException {
session.create(NAME, new ArrayInput("<"));
}
/**
* Stops because of an invalid database name.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void createNameErr() throws IOException {
session.create("", new ArrayInput(""));
}
/**
* Adds documents to a database.
* @throws IOException I/O exception
*/
@Test
public final void add() throws IOException {
session.execute("create db " + NAME);
session.add(NAME, new ArrayInput("<X/>"));
assertEqual("1", session.query("count(" + _DB_OPEN.args(NAME) + ')').execute());
for(int i = 0; i < 9; i++) session.add(NAME, new ArrayInput("<X/>"));
assertEqual("10", session.query("count(" + _DB_OPEN.args(NAME) + ')').execute());
}
/**
* Adds a file with an invalid file name.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void addNameErr() throws IOException {
session.execute("create db " + NAME);
session.add("", new ArrayInput("<X/>"));
}
/**
* Adds a file with missing input.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void addNoInput() throws IOException {
session.execute("create db " + NAME);
session.add("", new ArrayInput(""));
}
/**
* Replaces documents in a database.
* @throws IOException I/O exception
*/
@Test
public final void replace() throws IOException {
session.execute("create db " + NAME);
assertEqual("0", session.query("count(" + _DB_OPEN.args(NAME) + ')').execute());
session.replace(NAME, new ArrayInput("<X/>"));
assertEqual("1", session.query("count(" + _DB_OPEN.args(NAME) + ')').execute());
session.replace(NAME + '2', new ArrayInput("<X/>"));
assertEqual("2", session.query("count(" + _DB_OPEN.args(NAME) + ')').execute());
session.replace(NAME + '2', new ArrayInput("<X/>"));
assertEqual("2", session.query("count(" + _DB_OPEN.args(NAME) + ')').execute());
}
/**
* Replaces a file with an invalid file name.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void replaceNameErr() throws IOException {
session.execute("create db " + NAME);
session.replace("", new ArrayInput("<X/>"));
}
/**
* Adds a file with missing input.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void replaceNoInput() throws IOException {
session.execute("create db " + NAME);
session.replace("", new ArrayInput(""));
}
/**
* Stores binary content in the database.
* @throws IOException I/O exception
*/
@Test
public final void store() throws IOException {
session.execute("create db " + NAME);
session.store("X", new ArrayInput("!"));
assertEqual("true", session.query(_DB_IS_RAW.args(NAME, "X")).execute());
session.store("X", new ArrayInput(""));
assertEqual("", session.query(_DB_RETRIEVE.args(NAME, "X")).execute());
session.store("X", new ArrayInput(new byte[] { 0, 1, -1 }));
assertEqual("AAH/", session.query(STRING.args(_DB_RETRIEVE.args(NAME, "X"))).execute());
session.execute("drop db " + NAME);
}
/** Stores binary content.
* @throws IOException I/O exception */
@Test
public void storeBinary() throws IOException {
session.execute("create db " + NAME);
session.store("X", new ArrayInput(new byte[] { -128, -2, -1, 0, 1, 127 }));
final Query query = session.query(_CONVERT_BINARY_TO_BYTES.args(_DB_RETRIEVE.args(NAME, "X")));
assertEqual("-128\n-2\n-1\n0\n1\n127", query.execute());
}
/**
* Stores binary content in the database.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void storeNoDB() throws IOException {
session.store("X", new ArrayInput("!"));
}
/**
* Stores binary content in the database.
* @throws IOException I/O exception
*/
@Test(expected = BaseXException.class)
public final void storeInvalid() throws IOException {
session.execute("create db " + NAME);
session.store("..", new ArrayInput("!"));
}
/** Retrieves binary content.
* @throws IOException I/O exception */
@Test
public void retrieveBinary() throws IOException {
session.execute("create db " + NAME);
session.store("X", new ArrayInput("\0"));
assertEqual("\0", session.execute("retrieve X"));
}
/** Retrieves empty content.
* @throws IOException I/O exception */
@Test
public void retrieveEmpty() throws IOException {
session.execute("create db " + NAME);
session.store("X", new ArrayInput(""));
assertEqual("", session.execute("retrieve X"));
}
/** Runs a query and retrieves the result as string.
* @throws IOException I/O exception */
@Test
public void query() throws IOException {
final Query query = session.query("1");
assertEqual("1", query.execute());
}
/** Runs a query and retrieves the result as string.
* @throws IOException I/O exception */
@Test
public void query2() throws IOException {
final Query query = session.query("1");
if(!query.more()) fail("No result returned");
assertEqual("1", query.next());
}
/** Runs a query and retrieves the empty result as string.
* @throws IOException I/O exception */
@Test
public void queryNoResult() throws IOException {
try(Query query = session.query("()")) {
assertFalse("No result was expected.", query.more());
}
}
/** Tolerate multiple close calls.
* @throws IOException I/O exception */
@Test
public void queryClose() throws IOException {
try(Query query = session.query("()")) {
query.close();
}
}
/** Runs a query, using more().
* @throws IOException I/O exception */
@Test
public void queryInit() throws IOException {
final Query query = session.query("()");
assertFalse("No result was expected.", query.more());
}
/** Runs a query and retrieves multiple results as string.
* @throws IOException I/O exception */
@Test
public void queryMore() throws IOException {
try(Query query = session.query("1 to 3")) {
int c = 0;
while(query.more()) assertEqual(Integer.toString(++c), query.next());
}
}
/** Queries binary content.
* @throws IOException I/O exception */
@Test
public void queryNullBinary() throws IOException {
session.execute("create db " + NAME);
session.store("X", new ArrayInput("\0"));
assertEqual("\0", session.execute("xquery " + _DB_RETRIEVE.args(NAME, "X")));
assertEqual("\0", session.query(_DB_RETRIEVE.args(NAME, "X")).execute());
final Query q = session.query(_DB_RETRIEVE.args(NAME, "X"));
assertTrue(q.more());
assertEqual("\0", q.next());
assertFalse(q.more());
}
/** Queries empty content.
* @throws IOException I/O exception */
@Test
public void queryEmptyBinary() throws IOException {
session.execute("create db " + NAME);
session.store("X", new ArrayInput(""));
assertEqual("", session.execute("xquery " + _DB_RETRIEVE.args(NAME, "X")));
assertEqual("", session.query(_DB_RETRIEVE.args(NAME, "X")).execute());
final Query q = session.query(_DB_RETRIEVE.args(NAME, "X"));
assertTrue(q.more());
assertEqual("", q.next());
assertNull(q.next());
}
/** Queries empty content.
* @throws IOException I/O exception */
@Test
public void queryEmptyString() throws IOException {
final Query q = session.query("'',1");
assertTrue(q.more());
assertEqual("", q.next());
assertTrue(q.more());
assertEqual("1", q.next());
assertNull(q.next());
}
/** Queries binary content (works only if output stream is specified).
* @throws IOException I/O exception */
@Test
public void queryBinary() throws IOException {
if(out == null) return;
session.execute("create db " + NAME);
final byte[] tmp = { 0, 1, 2, 127, 0, -1, -2, -128 };
session.store("X", new ArrayInput(tmp));
final String retr = _DB_RETRIEVE.args(NAME, "X");
// check command
session.execute("xquery " + retr + ',' + retr);
assertArrayEquals(concat(tmp, token(Prop.NL), tmp), out.next());
// check query execution
session.query(retr + ',' + retr).execute();
assertArrayEquals(concat(tmp, token(Prop.NL), tmp), out.next());
// check iterator
final Query q = session.query(retr + ',' + retr);
q.next();
assertArrayEquals(tmp, out.next());
q.next();
assertArrayEquals(tmp, out.next());
assertNull(q.next());
}
/** Runs a query, omitting more().
* @throws IOException I/O exception */
@Test
public void queryNoMore() throws IOException {
try(Query query = session.query("1 to 2")) {
assertEqual("1", query.next());
assertEqual("2", query.next());
assertNull(query.next());
}
}
/** Runs a query with an external variable declaration.
* @throws IOException I/O exception */
@Test
public void queryBind() throws IOException {
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("$a", "4");
assertEqual("4", query.execute());
query.bind("$a", "5");
assertEqual("5", query.next());
query.bind("$a", "6");
assertEqual("6", query.next());
}
}
/** Runs a query with an external variable declaration.
* @throws IOException exception
*/
@Test(expected = BaseXException.class)
public void queryBind2() throws IOException {
session.query("declare variable $a external; $a").next();
}
/** Runs a query with an external variable declaration.
* @throws IOException I/O exception */
@Test
public void queryBindURI() throws IOException {
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("$a", "X", "xs:anyURI");
assertEqual("X", query.next());
}
}
/** Runs a query with an external variable declaration.
* @throws IOException I/O exception */
@Test
public void queryBindEmptySequence() throws IOException {
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("a", "()", "empty-sequence()");
assertNull(query.next());
}
}
/** Runs a query with an external variable declaration.
* @throws IOException I/O exception */
@Test
public void queryBindInt() throws IOException {
try(Query query = session.query("declare variable $a as xs:integer external; $a")) {
query.bind("a", "5", "xs:integer");
assertEqual("5", query.next());
}
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("a", Int.get(1), "xs:integer");
assertEqual("1", query.next());
}
}
/** Runs a query with an external variable declaration.
* @throws IOException I/O exception */
@Test
public void queryBindSequence() throws IOException {
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("a", "1\u00012", "xs:integer");
assertEqual("1", query.next());
assertEqual("2", query.next());
}
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("a", "09\u0002xs:hexBinary\u00012", "xs:integer");
assertEqual("\t", query.next());
assertEqual("2", query.next());
}
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("a", new ItemList().add(Int.get(1)).add(Str.get("X")).value());
assertEqual("1", query.next());
assertEqual("X", query.next());
}
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("a", IntSeq.get(new long[] { 1, 2 }, AtomType.INT));
assertEqual("1", query.next());
assertEqual("2", query.next());
}
try(Query query = session.query("declare variable $a external; $a")) {
query.bind("a", IntSeq.get(new long[] { 1, 2 }, AtomType.INT), "xs:integer");
assertEqual("1", query.next());
assertEqual("2", query.next());
}
}
/** Runs a query with an external variable declaration.
* @throws IOException I/O exception */
@Test
public void queryBindDynamic() throws IOException {
try(Query query = session.query("declare variable $a as xs:integer external; $a")) {
query.bind("a", "1");
assertEqual("1", query.execute());
}
}
/** Binds a document node to an external variable.
* @throws IOException I/O exception */
@Test
public void queryBindDoc() throws IOException {
try(Query query = session.query("declare variable $a external; $a//text()")) {
query.bind("$a", "<a>XML</a>", "document-node()");
assertEqual("XML", query.execute());
}
}
/** Binds a node to an external variable.
* @throws IOException I/O exception */
@Test
public void queryBindBXNode() throws IOException {
try(Query query = session.query("declare variable $a as element() external; $a")) {
query.bind("$a", BXNode.get(new FElem("a")));
assertEqual("<a/>", query.execute());
}
final String string = "declare variable $a external; $a";
try(Query query = session.query(string)) {
query.bind("$a", BXNode.get(new FElem("a")));
assertEqual("<a/>", query.execute());
}
try(Query query = session.query(string)) {
query.bind("$a", BXNode.get(new FDoc().add(new FElem("a"))));
assertEqual("<a/>", query.execute());
}
try(Query query = session.query(string)) {
query.bind("$a", BXNode.get(new FTxt("a")));
assertEqual("a", query.execute());
}
try(Query query = session.query(string)) {
query.bind("$a", BXNode.get(new FPI("a", "b")));
assertEqual("<?a b?>", query.execute());
}
try(Query query = session.query(string)) {
query.bind("$a", BXNode.get(new FComm("a")));
assertEqual("<!--a-->", query.execute());
}
}
/** Runs a query with a bound context value.
* @throws IOException I/O exception */
@Test
public void queryContext() throws IOException {
try(Query query = session.query(".")) {
query.context("5");
assertEqual("5", query.next());
}
}
/** Runs a query with a bound context value.
* @throws IOException I/O exception */
@Test
public void queryContextInt() throws IOException {
try(Query query = session.query(". * 2")) {
query.context("6", "xs:integer");
assertEqual("12", query.next());
}
}
/** Runs a query with a bound context value.
* @throws IOException I/O exception */
@Test
public void queryContextVar() throws IOException {
try(Query query = session.query("declare variable $a := .; $a")) {
query.context("<a/>", "element()");
assertEqual("<a/>", query.next());
}
}
/** Runs a query, omitting more().
* @throws IOException I/O exception */
@Test
public void queryInfo() throws IOException {
try(Query query = session.query("1 to 2")) {
query.execute();
}
}
/** Runs a query and checks the serialization parameters.
* @throws IOException I/O exception */
@Test
public void queryOptions() throws IOException {
try(Query query = session.query(SerializerOptions.ENCODING.arg("US-ASCII") + "()")) {
query.execute();
final SerializerOptions sp = new SerializerOptions();
sp.assign(query.options());
assertEquals("US-ASCII", sp.get(SerializerOptions.ENCODING));
}
}
/** Runs a query and checks the updating flag.
* @throws IOException I/O exception */
@Test
public void queryUpdating() throws IOException {
// test non-updating query
try(Query query = session.query("12345678")) {
assertFalse(query.updating());
assertEqual("12345678", query.execute());
assertFalse(query.updating());
}
// test updating query
try(Query query = session.query("insert node <a/> into <b/>")) {
assertTrue(query.updating());
assertEqual("", query.execute());
assertTrue(query.updating());
}
}
/** Runs an erroneous query.
* @throws IOException expected exception*/
@Test(expected = BaseXException.class)
public void queryError() throws IOException {
session.query("(").next();
}
/** Runs an erroneous query.
* @throws IOException expected exception */
@Test(expected = BaseXException.class)
public void queryError2() throws IOException {
session.query("(1,'a')[. eq 1]").execute();
}
/** Runs an erroneous query.
* @throws IOException expected exception*/
@Test(expected = BaseXException.class)
public void queryError3() throws IOException {
final Query query = session.query("(1,'a')[. eq 1]");
assertEqual("1", query.next());
query.next();
}
/** Runs two queries in parallel.
* @throws IOException I/O exception */
@Test
public void queryParallel() throws IOException {
try(Query query1 = session.query("1 to 2"); Query query2 = session.query("reverse(3 to 4)")) {
assertEqual("1", query1.next());
assertEqual("4", query2.next());
assertEqual("2", query1.next());
assertEqual("3", query2.next());
assertNull(query1.next());
assertNull(query2.next());
}
}
/** Runs 5 queries in parallel.
* @throws IOException I/O exception */
@Test
public void queryParallel2() throws IOException {
final int size = 8;
final Query[] cqs = new Query[size];
for(int q = 0; q < size; q++) cqs[q] = session.query(Integer.toString(q));
for(int q = 0; q < size; q++) assertEqual(Integer.toString(q), cqs[q].next());
for(final Query query : cqs) query.close();
}
/** Binds maps to external variables via JSON.
* @throws IOException I/O exception */
@Test
public void queryBindJson() throws IOException {
final String var = "declare variable $x external;",
map = "{\"foo\":[1,2,3],\"bar\":{\"a\":null,\"\":false}}";
final String[][] tests = {
{"for $k in map:keys($x) order by $k descending return $k", "foo\nbar"},
{"every $k in $x('foo')?* satisfies $k eq $x('foo')(xs:integer($k))", "true"},
{"empty($x('bar')('a')) and not($x('bar')(''))", "true"},
};
for(final String[] test : tests) {
try(Query qu = session.query(var + test[0])) {
qu.bind("$x", map, "json");
assertEqual(test[1], qu.execute());
}
}
}
/** Runs a query and retrieves XML entities as string.
* @throws IOException I/O exception */
@Test
public void queryEntities() throws IOException {
final Query query = session.query("'&<>'"'");
assertEqual("&<>'\"", query.next());
}
/** Runs a query and retrieves a map.
* @throws IOException I/O exception */
@Test
public void queryJSON() throws IOException {
final Query query = session.query(SerializerOptions.INDENT.arg("no") + "map { 'a': '&' }");
assertEqual("map{\"a\":\"&\"}", query.next());
}
/**
* Checks if the most recent output equals the specified string.
* @param exp expected string
* @param ret string returned from the client API
*/
protected void assertEqual(final String exp, final String ret) {
final String result = (out != null ? out : ret).toString();
if(out != null) out.reset();
assertEquals(exp, normNL(result));
}
}