diff --git a/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java b/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java index dfe8b515..7561455a 100644 --- a/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java +++ b/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java @@ -22,7 +22,6 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -40,6 +39,16 @@ import static org.junit.Assert.*; @RunWith(AndroidJUnit4.class) public class FFIIntegrationTest { + class DBSetupResult { + TxReport schemaReport; + TxReport dataReport; + + public DBSetupResult(TxReport schemaReport, TxReport dataReport) { + this.schemaReport = schemaReport; + this.dataReport = dataReport; + } + } + Mentat mentat = null; @Test @@ -95,7 +104,8 @@ public class FFIIntegrationTest { return this.mentat; } - public TxReport populateWithTypesSchema(Mentat mentat) { + public DBSetupResult populateWithTypesSchema(Mentat mentat) { + InProgress transaction = mentat.beginTransaction(); String schema = "[\n" + " [:db/add \"b\" :db/ident :foo/boolean]\n" + " [:db/add \"b\" :db/valueType :db.type/boolean]\n" + @@ -122,7 +132,7 @@ public class FFIIntegrationTest { " [:db/add \"u\" :db/valueType :db.type/uuid]\n" + " [:db/add \"u\" :db/cardinality :db.cardinality/one]\n" + " ]"; - TxReport report = mentat.transact(schema); + TxReport report = transaction.transact(schema); Long stringEntid = report.getEntidForTempId("s"); String data = "[\n" + @@ -135,13 +145,16 @@ public class FFIIntegrationTest { " [:db/add \"a\" :foo/uuid #uuid \"550e8400-e29b-41d4-a716-446655440000\"]\n" + " [:db/add \"b\" :foo/boolean false]\n" + " [:db/add \"b\" :foo/ref "+ stringEntid +"]\n" + + " [:db/add \"b\" :foo/keyword :foo/string]\n" + " [:db/add \"b\" :foo/long 50]\n" + " [:db/add \"b\" :foo/instant #inst \"2018-01-01T11:00:00.000Z\"]\n" + " [:db/add \"b\" :foo/double 22.46]\n" + " [:db/add \"b\" :foo/string \"Silence is worse; all truths that are kept silent become poisonous.\"]\n" + " [:db/add \"b\" :foo/uuid #uuid \"4cb3f828-752d-497a-90c9-b1fd516d5644\"]\n" + " ]"; - return mentat.transact(data); + TxReport dataReport = transaction.transact(data); + transaction.commit(); + return new DBSetupResult(report, dataReport); } @Test @@ -168,7 +181,7 @@ public class FFIIntegrationTest { Mentat mentat = openAndInitializeCitiesStore(); String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindString("?name", "Wallingford").runScalar(new ScalarResultHandler() { + mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -187,7 +200,7 @@ public class FFIIntegrationTest { Mentat mentat = openAndInitializeCitiesStore(); String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"; final Expectation expectation = new Expectation(); - mentat.query(query).runColl(new CollResultHandler() { + mentat.query(query).run(new CollResultHandler() { @Override public void handleList(CollResult list) { assertNotNull(list); @@ -208,7 +221,7 @@ public class FFIIntegrationTest { Mentat mentat = openAndInitializeCitiesStore(); String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"; final Expectation expectation = new Expectation(); - mentat.query(query).runColl(new CollResultHandler() { + mentat.query(query).run(new CollResultHandler() { @Override public void handleList(CollResult list) { assertNotNull(list); @@ -234,7 +247,7 @@ public class FFIIntegrationTest { " [?c :community/type :community.type/website]\n" + " [(fulltext $ :community/category \"food\") [[?c ?cat]]]]"; final Expectation expectation = new Expectation(); - mentat.query(query).runTuple(new TupleResultHandler() { + mentat.query(query).run(new TupleResultHandler() { @Override public void handleRow(TupleResult row) { assertNotNull(row); @@ -333,11 +346,11 @@ public class FFIIntegrationTest { @Test public void bindingLongValueSucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindLong("?long", 25).runScalar(new ScalarResultHandler() { + mentat.query(query).bind("?long", 25).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -354,12 +367,12 @@ public class FFIIntegrationTest { @Test public void bindingRefValueSucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; long stringEntid = mentat.entIdForAttribute(":foo/string"); final Long bEntid = report.getEntidForTempId("b"); String query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindEntidReference("?ref", stringEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?ref", stringEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -376,12 +389,12 @@ public class FFIIntegrationTest { @Test public void bindingRefKwValueSucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; String refKeyword = ":foo/string"; final Long bEntid = report.getEntidForTempId("b"); String query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindKeywordReference("?ref", refKeyword).runScalar(new ScalarResultHandler() { + mentat.query(query).bindKeywordReference("?ref", refKeyword).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -398,11 +411,11 @@ public class FFIIntegrationTest { @Test public void bindingKwValueSucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindKeyword("?kw", ":foo/string").runScalar(new ScalarResultHandler() { + mentat.query(query).bindKeyword("?kw", ":foo/string").run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -419,13 +432,13 @@ public class FFIIntegrationTest { @Test public void bindingDateValueSucceeds() throws InterruptedException, ParseException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); Date date = new Date(1523896758000L); String query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindDate("?now", date).runTuple(new TupleResultHandler() { + mentat.query(query).bind("?now", date).run(new TupleResultHandler() { @Override public void handleRow(TupleResult row) { assertNotNull(row); @@ -446,7 +459,7 @@ public class FFIIntegrationTest { Mentat mentat = this.openAndInitializeCitiesStore(); String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindString("?name", "Wallingford").runScalar(new ScalarResultHandler() { + mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -463,12 +476,12 @@ public class FFIIntegrationTest { @Test public void bindingUuidValueSucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]"; UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); final Expectation expectation = new Expectation(); - mentat.query(query).bindUUID("?uuid", uuid).runScalar(new ScalarResultHandler() { + mentat.query(query).bind("?uuid", uuid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -485,11 +498,11 @@ public class FFIIntegrationTest { @Test public void bindingBooleanValueSucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindBoolean("?bool", true).runScalar(new ScalarResultHandler() { + mentat.query(query).bind("?bool", true).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -507,11 +520,11 @@ public class FFIIntegrationTest { @Test public void bindingDoubleValueSucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindDouble("?double", 11.23).runScalar(new ScalarResultHandler() { + mentat.query(query).bind("?double", 11.23).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -528,11 +541,11 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToLong() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -550,11 +563,11 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToRef() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :where [?e :foo/long 25]]"; final Expectation expectation = new Expectation(); - mentat.query(query).runScalar(new ScalarResultHandler() { + mentat.query(query).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -572,11 +585,11 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToKeyword() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -594,11 +607,11 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToBoolean() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -616,11 +629,11 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToDouble() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -638,14 +651,14 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToDate() throws InterruptedException, ParseException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]"; final Expectation expectation = new Expectation(); DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.ENGLISH); format.parse("2017-01-01T11:00:00+00:00"); final Calendar expectedDate = format.getCalendar(); - mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -663,11 +676,11 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToString() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]"; final Expectation expectation = new Expectation(); - mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -685,12 +698,12 @@ public class FFIIntegrationTest { @Test public void typedValueConvertsToUUID() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]"; final UUID expectedUUID = UUID.fromString("550e8400-e29b-41d4-a716-446655440000"); final Expectation expectation = new Expectation(); - mentat.query(query).bindEntidReference("?e", aEntid).runScalar(new ScalarResultHandler() { + mentat.query(query).bindEntidReference("?e", aEntid).run(new ScalarResultHandler() { @Override public void handleValue(TypedValue value) { assertNotNull(value); @@ -708,7 +721,7 @@ public class FFIIntegrationTest { @Test public void valueForAttributeOfEntitySucceeds() throws InterruptedException { Mentat mentat = new Mentat(); - TxReport report = this.populateWithTypesSchema(mentat); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); TypedValue value = mentat.valueForAttributeOfEntity(":foo/long", aEntid); assertNotNull(value); @@ -722,4 +735,511 @@ public class FFIIntegrationTest { long entid = mentat.entIdForAttribute(":foo/long"); assertEquals(65540, entid); } + + @Test + public void testInProgressTransact() { + Mentat mentat = new Mentat(); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; + assertNotNull(report); + + } + + @Test + public void testInProgressRollback() { + Mentat mentat = new Mentat(); + TxReport report = this.populateWithTypesSchema(mentat).dataReport; + assertNotNull(report); + long aEntid = report.getEntidForTempId("a"); + TypedValue preLongValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid); + assertEquals(25, preLongValue.asLong().longValue()); + + InProgress inProgress = mentat.beginTransaction(); + report = inProgress.transact("[[:db/add "+ aEntid +" :foo/long 22]]"); + assertNotNull(report); + inProgress.rollback(); + + TypedValue postLongValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid); + assertEquals(25, postLongValue.asLong().longValue()); + } + + @Test + public void testInProgressEntityBuilder() throws InterruptedException { + Mentat mentat = new Mentat(); + DBSetupResult reports = this.populateWithTypesSchema(mentat); + long bEntid = reports.dataReport.getEntidForTempId("b"); + final long longEntid = reports.schemaReport.getEntidForTempId("l"); + final long stringEntid = reports.schemaReport.getEntidForTempId("s"); + + // test that the values are as expected + String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" + + " :in ?e\n" + + " :where [?e :foo/boolean ?b]\n" + + " [?e :foo/instant ?i]\n" + + " [?e :foo/uuid ?u]\n" + + " [?e :foo/long ?l]\n" + + " [?e :foo/double ?d]\n" + + " [?e :foo/string ?s]\n" + + " [?e :foo/keyword ?k]\n" + + " [?e :foo/ref ?r]]"; + + final Expectation expectation1 = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(false, row.asBool(0)); + assertEquals(new Date(1514804400000l), row.asDate(1)); + assertEquals(UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644"), row.asUUID(2)); + assertEquals(50, row.asLong(3).longValue()); + assertEquals(new Double(22.46), row.asDouble(4)); + assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5)); + assertEquals(":foo/string", row.asKeyword(6)); + assertEquals(stringEntid, row.asEntid(7).longValue()); + expectation1.fulfill(); + } + }); + + synchronized (expectation1) { + expectation1.wait(1000); + } + assertTrue(expectation1.isFulfilled); + + InProgressBuilder builder = mentat.entityBuilder(); + builder.add(bEntid, ":foo/boolean", true); + final Date newDate = new Date(1524743301000l); + builder.add(bEntid, ":foo/instant", newDate); + final UUID newUUID = UUID.randomUUID(); + builder.add(bEntid, ":foo/uuid", newUUID); + builder.add(bEntid, ":foo/long", 75); + builder.add(bEntid, ":foo/double", 81.3); + builder.add(bEntid, ":foo/string", "Become who you are!"); + builder.addKeyword(bEntid, ":foo/keyword", ":foo/long"); + builder.addRef(bEntid, ":foo/ref", longEntid); + builder.commit(); + + + final Expectation expectation2 = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(true, row.asBool(0)); + System.out.println(row.asDate(1).getTime()); + assertEquals(newDate, row.asDate(1)); + assertEquals(newUUID, row.asUUID(2)); + assertEquals(75, row.asLong(3).longValue()); + assertEquals(new Double(81.3), row.asDouble(4)); + assertEquals("Become who you are!", row.asString(5)); + assertEquals(":foo/long", row.asKeyword(6)); + assertEquals(longEntid, row.asEntid(7).longValue()); + expectation2.fulfill(); + } + }); + + synchronized (expectation2) { + expectation2.wait(1000); + } + assertTrue(expectation2.isFulfilled); + } + + @Test + public void testEntityBuilderForEntid() throws InterruptedException { + Mentat mentat = new Mentat(); + DBSetupResult reports = this.populateWithTypesSchema(mentat); + long bEntid = reports.dataReport.getEntidForTempId("b"); + final long longEntid = reports.schemaReport.getEntidForTempId("l"); + final long stringEntid = reports.schemaReport.getEntidForTempId("s"); + + // test that the values are as expected + String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" + + " :in ?e\n" + + " :where [?e :foo/boolean ?b]\n" + + " [?e :foo/instant ?i]\n" + + " [?e :foo/uuid ?u]\n" + + " [?e :foo/long ?l]\n" + + " [?e :foo/double ?d]\n" + + " [?e :foo/string ?s]\n" + + " [?e :foo/keyword ?k]\n" + + " [?e :foo/ref ?r]]"; + + final Expectation expectation1 = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(false, row.asBool(0)); + assertEquals(new Date(1514804400000l), row.asDate(1)); + assertEquals(UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644"), row.asUUID(2)); + assertEquals(50, row.asLong(3).longValue()); + assertEquals(new Double(22.46), row.asDouble(4)); + assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5)); + assertEquals(":foo/string", row.asKeyword(6)); + assertEquals(stringEntid, row.asEntid(7).longValue()); + expectation1.fulfill(); + } + }); + + synchronized (expectation1) { + expectation1.wait(1000); + } + assertTrue(expectation1.isFulfilled); + + EntityBuilder builder = mentat.entityBuilder(bEntid); + builder.add(":foo/boolean", true); + final Date newDate = new Date(1524743301000l); + builder.add(":foo/instant", newDate); + final UUID newUUID = UUID.randomUUID(); + builder.add(":foo/uuid", newUUID); + builder.add(":foo/long", 75); + builder.add(":foo/double", 81.3); + builder.add(":foo/string", "Become who you are!"); + builder.addKeyword(":foo/keyword", ":foo/long"); + builder.addRef(":foo/ref", longEntid); + builder.commit(); + + + final Expectation expectation2 = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(true, row.asBool(0)); + System.out.println(row.asDate(1).getTime()); + assertEquals(newDate, row.asDate(1)); + assertEquals(newUUID, row.asUUID(2)); + assertEquals(75, row.asLong(3).longValue()); + assertEquals(new Double(81.3), row.asDouble(4)); + assertEquals("Become who you are!", row.asString(5)); + assertEquals(":foo/long", row.asKeyword(6)); + assertEquals(longEntid, row.asEntid(7).longValue()); + expectation2.fulfill(); + } + }); + + synchronized (expectation2) { + expectation2.wait(1000); + } + assertTrue(expectation2.isFulfilled); + } + + @Test + public void testEntityBuilderForTempid() throws InterruptedException { + Mentat mentat = new Mentat(); + DBSetupResult reports = this.populateWithTypesSchema(mentat); + final long longEntid = reports.schemaReport.getEntidForTempId("l"); + + EntityBuilder builder = mentat.entityBuilder("c"); + builder.add(":foo/boolean", true); + final Date newDate = new Date(1524743301000l); + builder.add(":foo/instant", newDate); + final UUID newUUID = UUID.randomUUID(); + builder.add(":foo/uuid", newUUID); + builder.add(":foo/long", 75); + builder.add(":foo/double", 81.3); + builder.add(":foo/string", "Become who you are!"); + builder.addKeyword(":foo/keyword", ":foo/long"); + builder.addRef(":foo/ref", longEntid); + TxReport report = builder.commit(); + long cEntid = report.getEntidForTempId("c"); + + // test that the values are as expected + String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" + + " :in ?e\n" + + " :where [?e :foo/boolean ?b]\n" + + " [?e :foo/instant ?i]\n" + + " [?e :foo/uuid ?u]\n" + + " [?e :foo/long ?l]\n" + + " [?e :foo/double ?d]\n" + + " [?e :foo/string ?s]\n" + + " [?e :foo/keyword ?k]\n" + + " [?e :foo/ref ?r]]"; + + final Expectation expectation = new Expectation(); + mentat.query(query).bindEntidReference("?e", cEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(true, row.asBool(0)); + System.out.println(row.asDate(1).getTime()); + assertEquals(newDate, row.asDate(1)); + assertEquals(newUUID, row.asUUID(2)); + assertEquals(75, row.asLong(3).longValue()); + assertEquals(new Double(81.3), row.asDouble(4)); + assertEquals("Become who you are!", row.asString(5)); + assertEquals(":foo/long", row.asKeyword(6)); + assertEquals(longEntid, row.asEntid(7).longValue()); + expectation.fulfill(); + } + }); + + synchronized (expectation) { + expectation.wait(1000); + } + assertTrue(expectation.isFulfilled); + } + + @Test + public void testInProgressBuilderTransact() throws InterruptedException { + Mentat mentat = new Mentat(); + DBSetupResult reports = this.populateWithTypesSchema(mentat); + long aEntid = reports.dataReport.getEntidForTempId("a"); + long bEntid = reports.dataReport.getEntidForTempId("b"); + final long longEntid = reports.schemaReport.getEntidForTempId("l"); + InProgressBuilder builder = mentat.entityBuilder(); + builder.add(bEntid, ":foo/boolean", true); + final Date newDate = new Date(1524743301000l); + builder.add(bEntid, ":foo/instant", newDate); + final UUID newUUID = UUID.randomUUID(); + builder.add(bEntid, ":foo/uuid", newUUID); + builder.add(bEntid, ":foo/long", 75); + builder.add(bEntid, ":foo/double", 81.3); + builder.add(bEntid, ":foo/string", "Become who you are!"); + builder.addKeyword(bEntid, ":foo/keyword", ":foo/long"); + builder.addRef(bEntid, ":foo/ref", longEntid); + InProgressTransactionResult result = builder.transact(); + assertNotNull(result); + assertNotNull(result.getInProgress()); + assertNotNull(result.getReport()); + result.getInProgress().transact("[[:db/add "+ aEntid +" :foo/long 22]]"); + result.getInProgress().commit(); + + // test that the values are as expected + String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" + + " :in ?e\n" + + " :where [?e :foo/boolean ?b]\n" + + " [?e :foo/instant ?i]\n" + + " [?e :foo/uuid ?u]\n" + + " [?e :foo/long ?l]\n" + + " [?e :foo/double ?d]\n" + + " [?e :foo/string ?s]\n" + + " [?e :foo/keyword ?k]\n" + + " [?e :foo/ref ?r]]"; + + final Expectation expectation = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(true, row.asBool(0)); + System.out.println(row.asDate(1).getTime()); + assertEquals(newDate, row.asDate(1)); + assertEquals(newUUID, row.asUUID(2)); + assertEquals(75, row.asLong(3).longValue()); + assertEquals(new Double(81.3), row.asDouble(4)); + assertEquals("Become who you are!", row.asString(5)); + assertEquals(":foo/long", row.asKeyword(6)); + assertEquals(longEntid, row.asEntid(7).longValue()); + expectation.fulfill(); + } + }); + + synchronized (expectation) { + expectation.wait(1000); + } + assertTrue(expectation.isFulfilled); + + TypedValue longValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid); + assertEquals(22, longValue.asLong().longValue()); + } + + @Test + public void testEntityBuilderTransact() throws InterruptedException { + Mentat mentat = new Mentat(); + DBSetupResult reports = this.populateWithTypesSchema(mentat); + long aEntid = reports.dataReport.getEntidForTempId("a"); + long bEntid = reports.dataReport.getEntidForTempId("b"); + final long longEntid = reports.schemaReport.getEntidForTempId("l"); + + EntityBuilder builder = mentat.entityBuilder(bEntid); + builder.add(":foo/boolean", true); + final Date newDate = new Date(1524743301000l); + builder.add(":foo/instant", newDate); + final UUID newUUID = UUID.randomUUID(); + builder.add(":foo/uuid", newUUID); + builder.add(":foo/long", 75); + builder.add(":foo/double", 81.3); + builder.add(":foo/string", "Become who you are!"); + builder.addKeyword(":foo/keyword", ":foo/long"); + builder.addRef(":foo/ref", longEntid); + InProgressTransactionResult result = builder.transact(); + assertNotNull(result); + assertNotNull(result.getInProgress()); + assertNotNull(result.getReport()); + result.getInProgress().transact("[[:db/add "+ aEntid +" :foo/long 22]]"); + result.getInProgress().commit(); + + // test that the values are as expected + String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" + + " :in ?e\n" + + " :where [?e :foo/boolean ?b]\n" + + " [?e :foo/instant ?i]\n" + + " [?e :foo/uuid ?u]\n" + + " [?e :foo/long ?l]\n" + + " [?e :foo/double ?d]\n" + + " [?e :foo/string ?s]\n" + + " [?e :foo/keyword ?k]\n" + + " [?e :foo/ref ?r]]"; + + final Expectation expectation = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(true, row.asBool(0)); + System.out.println(row.asDate(1).getTime()); + assertEquals(newDate, row.asDate(1)); + assertEquals(newUUID, row.asUUID(2)); + assertEquals(75, row.asLong(3).longValue()); + assertEquals(new Double(81.3), row.asDouble(4)); + assertEquals("Become who you are!", row.asString(5)); + assertEquals(":foo/long", row.asKeyword(6)); + assertEquals(longEntid, row.asEntid(7).longValue()); + expectation.fulfill(); + } + }); + + synchronized (expectation) { + expectation.wait(1000); + } + assertTrue(expectation.isFulfilled); + + TypedValue longValue = mentat.valueForAttributeOfEntity(":foo/long", aEntid); + assertEquals(22, longValue.asLong().longValue()); + } + + @Test + public void testEntityBuilderRetract() throws InterruptedException { + Mentat mentat = new Mentat(); + DBSetupResult reports = this.populateWithTypesSchema(mentat); + long bEntid = reports.dataReport.getEntidForTempId("b"); + final long longEntid = reports.schemaReport.getEntidForTempId("l"); + final long stringEntid = reports.schemaReport.getEntidForTempId("s"); + + // test that the values are as expected + String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" + + " :in ?e\n" + + " :where [?e :foo/boolean ?b]\n" + + " [?e :foo/instant ?i]\n" + + " [?e :foo/uuid ?u]\n" + + " [?e :foo/long ?l]\n" + + " [?e :foo/double ?d]\n" + + " [?e :foo/string ?s]\n" + + " [?e :foo/keyword ?k]\n" + + " [?e :foo/ref ?r]]"; + + final Expectation expectation1 = new Expectation(); + final Date previousDate = new Date(1514804400000l); + final UUID previousUuid = UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644"); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(false, row.asBool(0)); + assertEquals(previousDate, row.asDate(1)); + assertEquals(previousUuid, row.asUUID(2)); + assertEquals(50, row.asLong(3).longValue()); + assertEquals(new Double(22.46), row.asDouble(4)); + assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5)); + assertEquals(":foo/string", row.asKeyword(6)); + assertEquals(stringEntid, row.asEntid(7).longValue()); + expectation1.fulfill(); + } + }); + + synchronized (expectation1) { + expectation1.wait(1000); + } + + EntityBuilder builder = mentat.entityBuilder(bEntid); + builder.retract(":foo/boolean", false); + builder.retract(":foo/instant", previousDate); + builder.retract(":foo/uuid", previousUuid); + builder.retract(":foo/long", 50); + builder.retract(":foo/double", 22.46); + builder.retract(":foo/string", "Silence is worse; all truths that are kept silent become poisonous."); + builder.retractKeyword(":foo/keyword", ":foo/string"); + builder.retractRef(":foo/ref", stringEntid); + builder.commit(); + + final Expectation expectation2 = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNull(row); + expectation2.fulfill(); + } + }); + + synchronized (expectation2) { + expectation2.wait(1000); + } + } + + @Test + public void testInProgressBuilderRetract() throws InterruptedException { + Mentat mentat = new Mentat(); + DBSetupResult reports = this.populateWithTypesSchema(mentat); + long bEntid = reports.dataReport.getEntidForTempId("b"); + final long longEntid = reports.schemaReport.getEntidForTempId("l"); + final long stringEntid = reports.schemaReport.getEntidForTempId("s"); + + // test that the values are as expected + String query = "[:find [?b ?i ?u ?l ?d ?s ?k ?r]\n" + + " :in ?e\n" + + " :where [?e :foo/boolean ?b]\n" + + " [?e :foo/instant ?i]\n" + + " [?e :foo/uuid ?u]\n" + + " [?e :foo/long ?l]\n" + + " [?e :foo/double ?d]\n" + + " [?e :foo/string ?s]\n" + + " [?e :foo/keyword ?k]\n" + + " [?e :foo/ref ?r]]"; + + final Expectation expectation1 = new Expectation(); + final Date previousDate = new Date(1514804400000l); + final UUID previousUuid = UUID.fromString("4cb3f828-752d-497a-90c9-b1fd516d5644"); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNotNull(row); + assertEquals(false, row.asBool(0)); + assertEquals(previousDate, row.asDate(1)); + assertEquals(previousUuid, row.asUUID(2)); + assertEquals(50, row.asLong(3).longValue()); + assertEquals(new Double(22.46), row.asDouble(4)); + assertEquals("Silence is worse; all truths that are kept silent become poisonous.", row.asString(5)); + assertEquals(":foo/string", row.asKeyword(6)); + assertEquals(stringEntid, row.asEntid(7).longValue()); + expectation1.fulfill(); + } + }); + + synchronized (expectation1) { + expectation1.wait(1000); + } + + InProgressBuilder builder = mentat.entityBuilder(); + builder.retract(bEntid, ":foo/boolean", false); + builder.retract(bEntid, ":foo/instant", previousDate); + builder.retract(bEntid, ":foo/uuid", previousUuid); + builder.retract(bEntid, ":foo/long", 50); + builder.retract(bEntid, ":foo/double", 22.46); + builder.retract(bEntid, ":foo/string", "Silence is worse; all truths that are kept silent become poisonous."); + builder.retractKeyword(bEntid, ":foo/keyword", ":foo/string"); + builder.retractRef(bEntid, ":foo/ref", stringEntid); + builder.commit(); + + final Expectation expectation2 = new Expectation(); + mentat.query(query).bindEntidReference("?e", bEntid).run(new TupleResultHandler() { + @Override + public void handleRow(TupleResult row) { + assertNull(row); + expectation2.fulfill(); + } + }); + + synchronized (expectation2) { + expectation2.wait(1000); + } + } } diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/EntityBuilder.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/EntityBuilder.java new file mode 100644 index 00000000..e2f067bd --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/EntityBuilder.java @@ -0,0 +1,357 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * Copyright 2018 Mozilla + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. */ + +package com.mozilla.mentat; + +import android.util.Log; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.UUID; + +/** + * This class wraps a raw pointer that points to a Rust `EntityBuilder` object. + * + * {@link EntityBuilder} provides a programmatic interface to performing assertions on a specific entity. + * It provides functions for adding and retracting values for attributes for an entity within + * an in progress transaction. + *

+ * The `transact` function will transact the assertions that have been added to the {@link EntityBuilder} + * and pass back the {@link TxReport} that was generated by this transact and the {@link InProgress} that was + * used to perform the transact. This enables you to perform further transacts on the same {@link InProgress} + * before committing. + *

+ *

{@code
+ * long aEntid = txReport.getEntidForTempId("a");
+ * long bEntid = txReport.getEntidForTempId("b");
+ * EntityBuilder builder = mentat.getEntityBuilderForEntid(bEntid);
+ * builder.add(":foo/boolean", true);
+ * builder.add(":foo/instant", newDate);
+ * InProgress inProgress = builder.transact().getInProgress();
+ * inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
+ * inProgress.commit();
+ * }
+ *

+ * The `commit` function will transact and commit the assertions that have been added to the {@link EntityBuilder}. + * It will consume the {@link InProgress} used to perform the transact. It returns the {@link TxReport} generated by + * the transact. After calling `commit`, a new transaction must be started by calling

Mentat.beginTransaction()
+ * in order to perform further actions. + *

+ *

{@code
+ * long aEntid = txReport.getEntidForTempId("a");
+ * EntityBuilder builder = mentat.getEntityBuilderForEntid(bEntid);
+ * builder.add(":foo/boolean", true);
+ * builder.add(":foo/instant", newDate);
+ * builder.commit();
+ * }
+ */ +public class EntityBuilder extends RustObject { + + public EntityBuilder(Pointer pointer) { + this.rawPointer = pointer; + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_add_long(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void addRef(String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_add_ref(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void addKeyword(String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_add_keyword(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(String keyword, boolean value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_add_boolean(this.rawPointer, keyword, value ? 1 : 0); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(String keyword, double value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_add_double(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(String keyword, Date value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_add_timestamp(this.rawPointer, keyword, value.getTime() * 1_000); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_add_string(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(String keyword, UUID value) { + this.validate(); + + RustResult result = JNA.INSTANCE.entity_builder_add_uuid(this.rawPointer, keyword, getPointerForUUID(value)); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_long(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retractRef(String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_ref(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retractKeyword(String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_keyword(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(String keyword, boolean value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_boolean(this.rawPointer, keyword, value ? 1 : 0); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(String keyword, double value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_double(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(String keyword, Date value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_timestamp(this.rawPointer, keyword, value.getTime() * 1_000); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_string(this.rawPointer, keyword, value); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(String keyword, UUID value) { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_retract_uuid(this.rawPointer, keyword, this.getPointerForUUID(value)); + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + } + } + + /** + * Transacts the added assertions. This consumes the pointer associated with this {@link EntityBuilder} + * such that no further assertions can be added after the `transact` has completed. To perform + * further assertions, use the {@link InProgress} inside the {@link InProgressTransactionResult} + * returned from this function. + *

+ * This does not commit the transaction. In order to do so, `commit` can be called on the + * {@link InProgress} inside the {@link InProgressTransactionResult} returned from this function. + * + * TODO throw exception if error occurs + * + * @return A {@link InProgressTransactionResult} containing the current {@link InProgress} and + * the {@link TxReport} generated by the transact. + */ + public InProgressTransactionResult transact() { + this.validate(); + InProgressTransactionResult result = JNA.INSTANCE.entity_builder_transact(this.rawPointer); + this.rawPointer = null; + return result; + } + + /** + * Transacts the added assertions and commits. This consumes the pointer associated with this + * {@link EntityBuilder} and the associated {@link InProgress} such that no further assertions + * can be added after the `commit` has completed. + *

+ * To perform further assertions, a new `{@link InProgress} or {@link EntityBuilder} should be + * created. + * + * TODO throw exception if error occurs + * + * @return + */ + public TxReport commit() { + this.validate(); + RustResult result = JNA.INSTANCE.entity_builder_commit(this.rawPointer); + this.rawPointer = null; + if (result.isFailure()) { + Log.e("EntityBuilder", result.err); + return null; + } + + return new TxReport(result.ok); + } + + @Override + public void close() throws IOException { + if (this.rawPointer != null) { + JNA.INSTANCE.entity_builder_destroy(this.rawPointer); + } + } +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgress.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgress.java new file mode 100644 index 00000000..0af3fba8 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgress.java @@ -0,0 +1,195 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * Copyright 2018 Mozilla + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. */ + +package com.mozilla.mentat; + +import android.util.Log; + +import com.sun.jna.Pointer; + +import java.io.IOException; + +/** + * This class wraps a raw pointer that points to a Rust {@link InProgress} object. + *

+ * {@link InProgress} allows for multiple transacts to be performed in a single transaction. + * Each transact performed results in a {@link TxReport} that can be used to gather information + * to be used in subsequent transacts. + *

+ * Committing an {@link InProgress} commits all the transacts that have been performed using + * that {@link InProgress}. + *

+ * Rolling back and {@link InProgress} rolls back all the transacts that have been performed + * using that {@link InProgress}. + *

+ *
{@code
+ * do {
+ * let inProgress = try mentat.beginTransaction()
+ * let txReport = try inProgress.transact(transaction: "[[:db/add "a" :foo/long 22]]")
+ * let aEntid = txReport.entid(forTempId: "a")
+ * let report = try inProgress.transact(transaction: "[[:db/add "b" :foo/ref \(aEntid)] [:db/add "b" :foo/boolean true]]")
+ * try inProgress.commit()
+ * } catch {
+ * ...
+ * }
+ * }
+ *

+ * {@link InProgress} also provides a number of functions to generating an builder to assert datoms + * programatically. The two types of builder are {@link InProgressBuilder} and {@link EntityBuilder}. + *

+ * {@link InProgressBuilder} takes the current {@link InProgress} and provides a programmatic + * interface to add and retract values from entities for which there exists an `Entid`. The provided + * {@link InProgress} is used to perform the transacts. + *

+ *
{@code
+ * long aEntid = txReport.getEntidForTempId("a");
+ * long bEntid = txReport.getEntidForTempId("b");
+ * InProgress inProgress = mentat.beginTransaction();
+ * InProgressBuilder builder = inProgress.builder();
+ * builder.add(bEntid, ":foo/boolean", true);
+ * builder.add(aEntid, ":foo/instant", newDate);
+ * inProgress  = builder.transact().getInProgress();
+ * inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
+ * inProgress.commit();
+ * }
+ * }
+ *

+ * {@link EntityBuilder} takes the current {@link InProgress} and either an `Entid` or a `tempid` to + * provide a programmatic interface to add and retract values from a specific entity. The provided + * {@link InProgress} is used to perform the transacts. + *

+ *
{@code
+ * long aEntid = txReport.getEntidForTempId("a");
+ * long bEntid = txReport.getEntidForTempId("b");
+ * InProgress inProgress = mentat.beginTransaction();
+ * EntityBuilder builder = inProgress.builderForEntid(bEntid);
+ * builder.add(":foo/boolean", true);
+ * builder.add(":foo/instant", newDate);
+ * inProgress  = builder.transact().getInProgress();
+ * inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
+ * inProgress.commit();
+ * }
+ */ +public class InProgress extends RustObject { + + public InProgress(Pointer pointer) { + this.rawPointer = pointer; + } + + /** + * Creates an {@link InProgressBuilder} using this {@link InProgress} . + * + * @return an {@link InProgressBuilder} for this {@link InProgress} + */ + public InProgressBuilder builder() { + this.validate(); + InProgressBuilder builder = new InProgressBuilder(JNA.INSTANCE.in_progress_builder(this.rawPointer)); + this.rawPointer = null; + return builder; + } + + /** + * Creates an `EntityBuilder` using this `InProgress` for the entity with `entid`. + * + * + * @param entid The `Entid` for this entity. + * @return an `EntityBuilder` for this `InProgress` + */ + public EntityBuilder builderForEntid(long entid){ + this.validate(); + EntityBuilder builder = new EntityBuilder(JNA.INSTANCE.in_progress_entity_builder_from_entid(this.rawPointer, entid)); + this.rawPointer = null; + return builder; + } + + /** + * Creates an `EntityBuilder` using this `InProgress` for a new entity with `tempid`. + * + * + * @param tempid The temporary identifier for this entity. + * @return an `EntityBuilder` for this `InProgress` + */ + public EntityBuilder builderForTempid(String tempid){ + this.validate(); + EntityBuilder builder = new EntityBuilder(JNA.INSTANCE.in_progress_entity_builder_from_temp_id(this.rawPointer, tempid)); + this.rawPointer = null; + return builder; + } + + /** + Transacts the `transaction` + + This does not commit the transaction. In order to do so, `commit` can be called. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the transaction failed. + - Throws: `ResultError.empty` if no `TxReport` is returned from the transact. + + - Returns: The `TxReport` generated by the transact. + */ + /** + * Transacts the `transaction` + * + * This does not commit the transaction. In order to do so, `commit` can be called. + * + * TODO throw Exception on result failure. + * + * @param transaction The EDN string to be transacted. + * @return The `TxReport` generated by the transact. + */ + public TxReport transact(String transaction) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_transact(this.rawPointer, transaction); + if (result.isFailure()) { + Log.e("InProgress", result.err); + return null; + } + return new TxReport(result.ok); + } + + /** + * Commits all the transacts that have been performed on this `InProgress`, either directly + * or through a Builder. This consumes the pointer associated with this {@link InProgress} such + * that no further assertions can be performed after the `commit` has completed. + * + * TODO throw exception if error occurs + */ + public void commit() { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_commit(this.rawPointer); + this.rawPointer = null; + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Rolls back all the transacts that have been performed on this `InProgress`, either directly + * or through a Builder. + * + * TODO throw exception if error occurs + */ + public void rollback() { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_rollback(this.rawPointer); + this.rawPointer = null; + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + @Override + public void close() throws IOException { + if (this.rawPointer != null) { + JNA.INSTANCE.in_progress_destroy(this.rawPointer); + } + } +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgressBuilder.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgressBuilder.java new file mode 100644 index 00000000..81072fb2 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgressBuilder.java @@ -0,0 +1,382 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * Copyright 2018 Mozilla + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. */ + +package com.mozilla.mentat; + +import android.util.Log; + +import com.sun.jna.Pointer; + +import java.io.IOException; +import java.util.Date; +import java.util.UUID; + +/** + * This class wraps a raw pointer that points to a Rust `InProgressBuilder` object. + * + * {@link InProgressBuilder} provides a programmatic interface to performing assertions for entities. + * It provides functions for adding and retracting values for attributes for an entity within + * an in progress transaction. + *

+ * The `transact` function will transact the assertions that have been added to the {@link InProgressBuilder} + * and pass back the {@link TxReport} that was generated by this transact and the {@link InProgress} that was + * used to perform the transact. This enables you to perform further transacts on the same {@link InProgress} + * before committing. + *

+ *

{@code
+ * long aEntid = txReport.getEntidForTempId("a");
+ * long bEntid = txReport.getEntidForTempId("b");
+ * InProgressBuilder builder = mentat.getEntityBuilder();
+ * builder.add(aEntid, ":foo/boolean", true);
+ * builder.add(bEntid, ":foo/instant", newDate);
+ * InProgress inProgress = builder.transact().getInProgress();
+ * inProgress.transact("[[:db/add \(aEntid) :foo/long 22]]");
+ * inProgress.commit();
+ * }
+ *

+ * The `commit` function will transact and commit the assertions that have been added to the + * {@link InProgressBuilder}. + * It will consume the {@link InProgress} used to perform the transact. It returns the {@link TxReport} + * generated by the transact. After calling `commit`, a new transaction must be started by calling + *

Mentat.beginTransaction()
in order to perform further actions. + *

+ *

{@code
+ * long aEntid = txReport.getEntidForTempId("a");
+ * long bEntid = txReport.getEntidForTempId("b");
+ * InProgressBuilder builder = mentat.getEntityBuilder();
+ * builder.add(aEntid, ":foo/boolean", true);
+ * builder.add(bEntid, ":foo/instant", newDate);
+ * builder.commit();
+ * }
+ */ +public class InProgressBuilder extends RustObject { + + public InProgressBuilder(Pointer pointer) { + this.rawPointer = pointer; + } + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(long entid, String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_add_long(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void addRef(long entid, String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_add_ref(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void addKeyword(long entid, String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_add_keyword(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(long entid, String keyword, boolean value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_add_boolean(this.rawPointer, entid, keyword, value ? 1 : 0); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(long entid, String keyword, double value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_add_double(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(long entid, String keyword, Date value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_add_timestamp(this.rawPointer, entid, keyword, value.getTime() * 1_000); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(long entid, String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_add_string(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Asserts the value of attribute `keyword` to be the provided `value`. + * // TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be asserted + */ + public void add(long entid, String keyword, UUID value) { + this.validate(); + + RustResult result = JNA.INSTANCE.in_progress_builder_add_uuid(this.rawPointer, entid, keyword, getPointerForUUID(value)); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(long entid, String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_long(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retractRef(long entid, String keyword, long value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_ref(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retractKeyword(long entid, String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_keyword(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(long entid, String keyword, boolean value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_boolean(this.rawPointer, entid, keyword, value ? 1 : 0); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(long entid, String keyword, double value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_double(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(long entid, String keyword, Date value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_timestamp(this.rawPointer, entid, keyword, value.getTime() * 1_000); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(long entid, String keyword, String value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_string(this.rawPointer, entid, keyword, value); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Retracts the value of attribute `keyword` from the provided `value`. + * + * TODO throw exception if error occurs + * + * @param entid The `Entid` of the entity to be touched. + * @param keyword The name of the attribute in the format `:namespace/name`. + * @param value The value to be retracted + */ + public void retract(long entid, String keyword, UUID value) { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_retract_uuid(this.rawPointer, entid, keyword, this.getPointerForUUID(value)); + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + } + } + + /** + * Transacts the added assertions. This consumes the pointer associated with this {@link InProgressBuilder} + * such that no further assertions can be added after the `transact` has completed. To perform + * further assertions, use the {@link InProgress} inside the {@link InProgressTransactionResult} + * returned from this function. + *

+ * This does not commit the transaction. In order to do so, `commit` can be called on the + * {@link InProgress} inside the {@link InProgressTransactionResult} returned from this function. + * + * TODO throw exception if error occurs + * + * @return A {@link InProgressTransactionResult} containing the current {@link InProgress} and + * the {@link TxReport} generated by the transact. + */ + public InProgressTransactionResult transact() { + this.validate(); + InProgressTransactionResult result = JNA.INSTANCE.in_progress_builder_transact(this.rawPointer); + this.rawPointer = null; + return result; + } + + /** + * Transacts the added assertions and commits. This consumes the pointer associated with this + * {@link InProgressBuilder} and the associated {@link InProgress} such that no further assertions + * can be added after the `commit` has completed. + *

+ * To perform further assertions, a new `{@link InProgress} or {@link InProgressBuilder} should be + * created. + * + * TODO throw exception if error occurs + * + * @return + */ + public TxReport commit() { + this.validate(); + RustResult result = JNA.INSTANCE.in_progress_builder_commit(this.rawPointer); + this.rawPointer = null; + if (result.isFailure()) { + Log.e("InProgressBuilder", result.err); + return null; + } + + return new TxReport(result.ok); + } + + @Override + public void close() throws IOException { + if (this.rawPointer != null) { + JNA.INSTANCE.in_progress_builder_destroy(this.rawPointer); + } + } +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgressTransactionResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgressTransactionResult.java new file mode 100644 index 00000000..c7b39c8a --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/InProgressTransactionResult.java @@ -0,0 +1,56 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * Copyright 2018 Mozilla + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. */ + +package com.mozilla.mentat; + +import android.util.Log; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class InProgressTransactionResult extends Structure implements Closeable { + public static class ByReference extends InProgressTransactionResult implements Structure.ByReference { + } + + public static class ByValue extends InProgressTransactionResult implements Structure.ByValue { + } + + public Pointer inProgress; + public RustResult.ByReference result; + + @Override + protected List getFieldOrder() { + return Arrays.asList("inProgress", "result"); + } + + public InProgress getInProgress() { + return new InProgress(this.inProgress); + } + public TxReport getReport() { + if (this.result.isFailure()) { + Log.e("InProgressTransactionResult", this.result.err); + return null; + } + + return new TxReport(this.result.ok); + } + + @Override + public void close() throws IOException { + if (this.getPointer() != null) { + JNA.INSTANCE.destroy(this.getPointer()); + } + } +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java index 202c23ab..4b14935e 100644 --- a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java @@ -37,12 +37,67 @@ public interface JNA extends Library { void typed_value_result_set_destroy(Pointer obj); void typed_value_result_set_iter_destroy(Pointer obj); void tx_report_destroy(Pointer obj); + void in_progress_destroy(Pointer obj); + void in_progress_builder_destroy(Pointer obj); + void entity_builder_destroy(Pointer obj); // transact RustResult store_transact(Pointer store, String transaction); Pointer tx_report_entity_for_temp_id(Pointer report, String tempid); long tx_report_get_entid(Pointer report); long tx_report_get_tx_instant(Pointer report); + RustResult store_begin_transaction(Pointer store); + + // in progress + RustResult in_progress_transact(Pointer in_progress, String transaction); + RustResult in_progress_commit(Pointer in_progress); + RustResult in_progress_rollback(Pointer in_progress); + Pointer in_progress_builder(Pointer in_progress); + Pointer in_progress_entity_builder_from_temp_id(Pointer in_progress, String temp_id); + Pointer in_progress_entity_builder_from_entid(Pointer in_progress, long entid); + + // in_progress entity building + RustResult store_in_progress_builder(Pointer store); + RustResult in_progress_builder_add_string(Pointer builder, long entid, String kw, String value); + RustResult in_progress_builder_add_long(Pointer builder, long entid, String kw, long value); + RustResult in_progress_builder_add_ref(Pointer builder, long entid, String kw, long value); + RustResult in_progress_builder_add_keyword(Pointer builder, long entid, String kw, String value); + RustResult in_progress_builder_add_timestamp(Pointer builder, long entid, String kw, long value); + RustResult in_progress_builder_add_boolean(Pointer builder, long entid, String kw, int value); + RustResult in_progress_builder_add_double(Pointer builder, long entid, String kw, double value); + RustResult in_progress_builder_add_uuid(Pointer builder, long entid, String kw, Pointer value); + RustResult in_progress_builder_retract_string(Pointer builder, long entid, String kw, String value); + RustResult in_progress_builder_retract_long(Pointer builder, long entid, String kw, long value); + RustResult in_progress_builder_retract_ref(Pointer builder, long entid, String kw, long value); + RustResult in_progress_builder_retract_keyword(Pointer builder, long entid, String kw, String value); + RustResult in_progress_builder_retract_timestamp(Pointer builder, long entid, String kw, long value); + RustResult in_progress_builder_retract_boolean(Pointer builder, long entid, String kw, int value); + RustResult in_progress_builder_retract_double(Pointer builder, long entid, String kw, double value); + RustResult in_progress_builder_retract_uuid(Pointer builder, long entid, String kw, Pointer value); + InProgressTransactionResult in_progress_builder_transact(Pointer builder); + RustResult in_progress_builder_commit(Pointer builder); + + // entity building + RustResult store_entity_builder_from_temp_id(Pointer store, String temp_id); + RustResult store_entity_builder_from_entid(Pointer store, long entid); + RustResult entity_builder_add_string(Pointer builder, String kw, String value); + RustResult entity_builder_add_long(Pointer builder, String kw, long value); + RustResult entity_builder_add_ref(Pointer builder, String kw, long value); + RustResult entity_builder_add_keyword(Pointer builder, String kw, String value); + RustResult entity_builder_add_boolean(Pointer builder, String kw, int value); + RustResult entity_builder_add_double(Pointer builder, String kw, double value); + RustResult entity_builder_add_timestamp(Pointer builder, String kw, long value); + RustResult entity_builder_add_uuid(Pointer builder, String kw, Pointer value); + RustResult entity_builder_retract_string(Pointer builder, String kw, String value); + RustResult entity_builder_retract_long(Pointer builder, String kw, long value); + RustResult entity_builder_retract_ref(Pointer builder, String kw, long value); + RustResult entity_builder_retract_keyword(Pointer builder, String kw, String value); + RustResult entity_builder_retract_boolean(Pointer builder, String kw, int value); + RustResult entity_builder_retract_double(Pointer builder, String kw, double value); + RustResult entity_builder_retract_timestamp(Pointer builder, String kw, long value); + RustResult entity_builder_retract_uuid(Pointer builder, String kw, Pointer value); + InProgressTransactionResult entity_builder_transact(Pointer builder); + RustResult entity_builder_commit(Pointer builder); // sync RustResult store_sync(Pointer store, String userUuid, String serverUri); @@ -95,7 +150,7 @@ public interface JNA extends Library { String value_at_index_into_kw(Pointer rows, int index); String value_at_index_into_string(Pointer rows, int index); Pointer value_at_index_into_uuid(Pointer rows, int index); - long value_at_index_into_boolean(Pointer rows, int index); + int value_at_index_into_boolean(Pointer rows, int index); double value_at_index_into_double(Pointer rows, int index); long value_at_index_into_timestamp(Pointer rows, int index); } diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java index 087647af..c885a728 100644 --- a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java @@ -138,6 +138,59 @@ public class Mentat extends RustObject { JNA.INSTANCE.store_unregister_observer(rawPointer, key); } + + public InProgress beginTransaction() { + RustResult result = JNA.INSTANCE.store_begin_transaction(this.rawPointer); + if (result.isSuccess()) { + return new InProgress(result.ok); + } + + if (result.isFailure()) { + Log.i("Mentat", result.err); + } + + return null; + } + + public InProgressBuilder entityBuilder() { + RustResult result = JNA.INSTANCE.store_in_progress_builder(this.rawPointer); + if (result.isSuccess()) { + return new InProgressBuilder(result.ok); + } + + if (result.isFailure()) { + Log.i("Mentat", result.err); + } + + return null; + } + + public EntityBuilder entityBuilder(long entid) { + RustResult result = JNA.INSTANCE.store_entity_builder_from_entid(this.rawPointer, entid); + if (result.isSuccess()) { + return new EntityBuilder(result.ok); + } + + if (result.isFailure()) { + Log.i("Mentat", result.err); + } + + return null; + } + + public EntityBuilder entityBuilder(String tempId) { + RustResult result = JNA.INSTANCE.store_entity_builder_from_temp_id(this.rawPointer, tempId); + if (result.isSuccess()) { + return new EntityBuilder(result.ok); + } + + if (result.isFailure()) { + Log.i("Mentat", result.err); + } + + return null; + } + @Override public void close() { if (this.rawPointer != null) { diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java index 8a4cb0f2..f8ce2a98 100644 --- a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java @@ -76,7 +76,7 @@ import java.util.UUID; *

{@code
  * String query = "[: find ?a .\n" +
  *          "        : where ... ]";
- * mentat.query(query).runScalar(new ScalarResultHandler() {
+ * mentat.query(query).run(new ScalarResultHandler() {
  *      @Override
  *      public void handleValue(TypedValue value) {
  *          ...
@@ -88,7 +88,7 @@ import java.util.UUID;
  * 
{@code
  * String query = "[: find [?a ...]\n" +
  *          "        : where ... ]";
- * mentat.query(query).runColl(new ScalarResultHandler() {
+ * mentat.query(query).run(new ScalarResultHandler() {
  *      @Override
  *      public void handleList(CollResult list) {
  *          ...
@@ -100,7 +100,7 @@ import java.util.UUID;
  * 
{@code
  * String query = "[: find [?a ?b ?c]\n" +
  *          "        : where ... ]";
- * mentat.query(query).runTuple(new TupleResultHandler() {
+ * mentat.query(query).run(new TupleResultHandler() {
  *      @Override
  *      public void handleRow(TupleResult row) {
  *          ...
@@ -121,7 +121,7 @@ public class Query extends RustObject {
      * @param value The value to be bound
      * @return  This {@link Query} such that further function can be called.
      */
-    Query bindLong(String varName, long value) {
+    Query bind(String varName, long value) {
         this.validate();
         JNA.INSTANCE.query_builder_bind_long(this.rawPointer, varName, value);
         return this;
@@ -173,7 +173,7 @@ public class Query extends RustObject {
      * @param value The value to be bound
      * @return  This {@link Query} such that further function can be called.
      */
-    Query bindBoolean(String varName, boolean value) {
+    Query bind(String varName, boolean value) {
         this.validate();
         JNA.INSTANCE.query_builder_bind_boolean(this.rawPointer, varName, value ? 1 : 0);
         return this;
@@ -186,7 +186,7 @@ public class Query extends RustObject {
      * @param value The value to be bound
      * @return  This {@link Query} such that further function can be called.
      */
-    Query bindDouble(String varName, double value) {
+    Query bind(String varName, double value) {
         this.validate();
         JNA.INSTANCE.query_builder_bind_double(this.rawPointer, varName, value);
         return this;
@@ -199,7 +199,7 @@ public class Query extends RustObject {
      * @param value The value to be bound
      * @return  This {@link Query} such that further function can be called.
      */
-    Query bindDate(String varName, Date value) {
+    Query bind(String varName, Date value) {
         this.validate();
         long timestamp = value.getTime() * 1000;
         JNA.INSTANCE.query_builder_bind_timestamp(this.rawPointer, varName, timestamp);
@@ -213,7 +213,7 @@ public class Query extends RustObject {
      * @param value The value to be bound
      * @return  This {@link Query} such that further function can be called.
      */
-    Query bindString(String varName, String value) {
+    Query bind(String varName, String value) {
         this.validate();
         JNA.INSTANCE.query_builder_bind_string(this.rawPointer, varName, value);
         return this;
@@ -226,15 +226,9 @@ public class Query extends RustObject {
      * @param value The value to be bound
      * @return  This {@link Query} such that further function can be called.
      */
-    Query bindUUID(String varName, UUID value) {
+    Query bind(String varName, UUID value) {
         this.validate();
-        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
-        bb.putLong(value.getMostSignificantBits());
-        bb.putLong(value.getLeastSignificantBits());
-        byte[] bytes = bb.array();
-        final Pointer bytesNativeArray = new Memory(bytes.length);
-        bytesNativeArray.write(0, bytes, 0, bytes.length);
-        JNA.INSTANCE.query_builder_bind_uuid(this.rawPointer, varName, bytesNativeArray);
+        JNA.INSTANCE.query_builder_bind_uuid(this.rawPointer, varName, getPointerForUUID(value));
         return this;
     }
 
@@ -262,7 +256,7 @@ public class Query extends RustObject {
      * TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
      * @param handler   the handler to call with the results of this query
      */
-    void runScalar(final ScalarResultHandler handler) {
+    void run(final ScalarResultHandler handler) {
         this.validate();
         RustResult result = JNA.INSTANCE.query_builder_execute_scalar(rawPointer);
         rawPointer = null;
@@ -285,7 +279,7 @@ public class Query extends RustObject {
      * TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
      * @param handler   the handler to call with the results of this query
      */
-    void runColl(final CollResultHandler handler) {
+    void run(final CollResultHandler handler) {
         this.validate();
         RustResult result = JNA.INSTANCE.query_builder_execute_coll(rawPointer);
         rawPointer = null;
@@ -303,7 +297,7 @@ public class Query extends RustObject {
      * TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
      * @param handler   the handler to call with the results of this query
      */
-    void runTuple(final TupleResultHandler handler) {
+    void run(final TupleResultHandler handler) {
         this.validate();
         RustResult result = JNA.INSTANCE.query_builder_execute_tuple(rawPointer);
         rawPointer = null;
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
index 35f7b8fb..e55c4e5f 100644
--- a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
@@ -10,9 +10,12 @@
 
 package com.mozilla.mentat;
 
+import com.sun.jna.Memory;
 import com.sun.jna.Pointer;
 
 import java.io.Closeable;
+import java.nio.ByteBuffer;
+import java.util.UUID;
 
 /**
  * Base class that wraps an non-optional {@link Pointer} representing a pointer to a Rust object.
@@ -31,4 +34,22 @@ abstract class RustObject implements Closeable {
             throw new NullPointerException(this.getClass() + " consumed");
         }
     }
+
+    public Pointer getPointerForUUID(UUID uuid) {
+        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
+        bb.putLong(uuid.getMostSignificantBits());
+        bb.putLong(uuid.getLeastSignificantBits());
+        byte[] bytes = bb.array();
+        final Pointer bytesNativeArray = new Memory(bytes.length);
+        bytesNativeArray.write(0, bytes, 0, bytes.length);
+        return bytesNativeArray;
+    }
+
+    public UUID getUUIDFromPointer(Pointer uuidPtr) {
+        byte[] bytes = uuidPtr.getByteArray(0, 16);
+        ByteBuffer bb = ByteBuffer.wrap(bytes);
+        long high = bb.getLong();
+        long low = bb.getLong();
+        return new UUID(high, low);
+    }
 }
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
index a9701682..d1b4c327 100644
--- a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
@@ -126,7 +126,7 @@ public class TupleResult extends RustObject {
      */
     public Date asDate(Integer index) {
         this.validate();
-        return new Date(JNA.INSTANCE.value_at_index_into_timestamp(this.rawPointer, index));
+        return new Date(JNA.INSTANCE.value_at_index_into_timestamp(this.rawPointer, index) * 1_000);
     }
 
     /**
@@ -150,13 +150,7 @@ public class TupleResult extends RustObject {
      */
     public UUID asUUID(Integer index) {
         this.validate();
-        Pointer uuidPtr = JNA.INSTANCE.value_at_index_into_uuid(this.rawPointer, index);
-        byte[] bytes = uuidPtr.getByteArray(0, 16);
-        ByteBuffer bb = ByteBuffer.wrap(bytes);
-        long high = bb.getLong();
-        long low = bb.getLong();
-
-        return new UUID(high, low);
+        return getUUIDFromPointer(JNA.INSTANCE.value_at_index_into_uuid(this.rawPointer, index));
     }
 
     @Override
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
index 943e9753..3c89a5df 100644
--- a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
@@ -137,12 +137,7 @@ public class TypedValue extends RustObject {
      */
     public UUID asUUID() {
         if (!this.isConsumed()) {
-            Pointer uuidPtr = JNA.INSTANCE.typed_value_into_uuid(this.rawPointer);
-            byte[] bytes = uuidPtr.getByteArray(0, 16);
-            ByteBuffer bb = ByteBuffer.wrap(bytes);
-            long high = bb.getLong();
-            long low = bb.getLong();
-            this.value = new UUID(high, low);
+            this.value = getUUIDFromPointer(JNA.INSTANCE.typed_value_into_uuid(this.rawPointer));
             this.rawPointer = null;
         }
         return (UUID)this.value;
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so
index 1160a11a..6edd9a18 100755
Binary files a/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so and b/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so differ