diff --git a/java/ddlogapi.c b/java/ddlogapi.c index e281aa31c..2b168aa4b 100644 --- a/java/ddlogapi.c +++ b/java/ddlogapi.c @@ -970,3 +970,262 @@ JNIEXPORT jlong JNICALL Java_ddlogapi_DDlogAPI_ddlog_1delete_1key_1cmd( ddlog_cmd* result = ddlog_delete_key_cmd(table, (ddlog_record*)handle); return (jlong)result; } + +enum Kind { + Kind_IsNull = 0x80, + Kind_Nullable = 0x40, + Kind_Long = 0, + Kind_String = 1, + Kind_Bool = 2 +}; + +JNIEXPORT jbyteArray JNICALL Java_ddlogapi_DDlogAPI_ddlog_1encode_1record( + JNIEnv* env, jclass obj, jlong handle) { + if (handle == 0) { + throwDDlogException(env, "null buffer supplied"); + return NULL; + } + ddlog_record* rec = (ddlog_record*)handle; + // First scan the ddlog record and measure the size required for the buffer + jboolean isStruct = (jboolean)ddlog_is_struct(rec); + if (!isStruct) { + throwDDlogException(env, "Record is not a struct"); + return NULL; + } + size_t buffer_size = 4; // size + size_t cons_size; + const char* constructor = ddlog_get_constructor_with_length(rec, &cons_size); + buffer_size += 4 + cons_size; + size_t fields = 0; + for (; ; fields++) { + // fprintf(stderr, "Measuring field %ld\n", fields); + const ddlog_record* field = ddlog_get_struct_field(rec, fields); + if (field == NULL) + break; + if (ddlog_is_struct(field)) { + // The only struct supported is Option + size_t inner_cons_size; + const char* inner_cons = ddlog_get_constructor_with_length(rec, &inner_cons_size); + if (inner_cons_size != 4) { + throwDDlogException(env, "Unsupported structure"); + return NULL; + } + if (!memcmp(inner_cons, "None", 4)) + continue; + if (memcmp(inner_cons, "Some", 4)) { + throwDDlogException(env, "Unsupported structure"); + return NULL; + } + const ddlog_record* inner = ddlog_get_struct_field(field, 0); + if (field == NULL) { + throwDDlogException(env, "'Some' does not have 1 field"); + return NULL; + } + if (ddlog_get_struct_field(field, 1)) { + throwDDlogException(env, "'Some' has more than 1 field"); + return NULL; + } + field = inner; + } + if (ddlog_is_bool(field)) { + buffer_size += 1; + } else if (ddlog_is_int(field)) { + ssize_t bytes = ddlog_get_int(field, NULL, 0); + if (bytes > sizeof(jlong)) { + fprintf(stderr, "integer size too large %ld", bytes); + throwDDlogException(env, "longs are the only integers ones supported"); + return NULL; + } + buffer_size += sizeof(jlong); + } else if (ddlog_is_string(field)) { + size_t bytes = ddlog_get_strlen(field); + buffer_size += 4 + bytes; + } else { + throwDDlogException(env, "Unsupported field type"); + return NULL; + } + } + + buffer_size += fields; + jbyteArray result = (*env)->NewByteArray(env, buffer_size); + // Scan again and copy. + int f = fields; + size_t data_index = 4 + fields; + jint size = cons_size; + (*env)->SetByteArrayRegion(env, result, data_index, sizeof(jint), (const char*)&size); + data_index += sizeof(jint); + (*env)->SetByteArrayRegion(env, result, data_index, cons_size, constructor); + data_index += cons_size; + + (*env)->SetByteArrayRegion(env, result, 0, 4, (const jbyte*)&f); + for (size_t i = 0; i < fields; i++) { + const ddlog_record* field = ddlog_get_struct_field(rec, i); + jbyte kind = 0; + if (ddlog_is_struct(field)) { + size_t inner_cons_size; + const char* inner_cons = ddlog_get_constructor_with_length(rec, &inner_cons_size); + if (inner_cons_size == 4 && !memcmp(inner_cons, "None", 4)) { + jbyte b = Kind_IsNull; + (*env)->SetByteArrayRegion(env, result, 4 + i, 1, (const jbyte*)&b); + continue; + } + field = ddlog_get_struct_field(field, 0); + kind = Kind_Nullable; + } + if (ddlog_is_bool(field)) { + kind |= Kind_Bool; + bool b = ddlog_get_bool(field); + jbyte v = b ? 1 : 0; + (*env)->SetByteArrayRegion(env, result, data_index, 1, (const jbyte*)&v); + data_index++; + } else if (ddlog_is_int(field)) { + kind |= Kind_Long; + jlong v = ddlog_get_i64(field); + (*env)->SetByteArrayRegion(env, result, data_index, sizeof(jlong), (const jbyte*)&v); + data_index += sizeof(jlong); + } else if (ddlog_is_string(field)) { + kind |= Kind_String; + size_t bytes; + const char* str = ddlog_get_str_with_length(field, &bytes); + jint v = bytes; + (*env)->SetByteArrayRegion(env, result, data_index, sizeof(jint), (const jbyte*)&v); + data_index += sizeof(jint); + (*env)->SetByteArrayRegion(env, result, data_index, bytes, str); + data_index += bytes; + } + (*env)->SetByteArrayRegion(env, result, 4 + i, 1, &kind); + } + return result; +} + +JNIEXPORT jlong JNICALL Java_ddlogapi_DDlogAPI_ddlog_1create_1sql_1record( + JNIEnv* env, jclass obj, jbyteArray bytes) { + jbyte *buf = (*env)->GetByteArrayElements(env, bytes, NULL); + if (buf == NULL) { + throwDDlogException(env, "null buffer supplied"); + return 0; + } + jbyte* original = buf; + size_t size = (*env)->GetArrayLength(env, bytes); + jint expect = sizeof(jint); + if (size < expect) { + throwDDlogException(env, "Buffer size too small for fields"); + return 0; + } + jint len = *(int*)buf; + buf += expect; + size -= expect; + + if (size < len) { + throwDDlogException(env, "Buffer size too small for kinds"); + return 0; + } + jbyte* encodings = buf; + buf += len; + size -= len; + + expect = sizeof(jint); + if (size < expect) { + fprintf(stderr, "Remaining size %ld\n", size); + throwDDlogException(env, "Buffer size too small for constructor size"); + return 0; + } + jint constrsz = *(jint*)buf; + buf += expect; + size -= expect; + if (size < (size_t)constrsz) { + fprintf(stderr, "Remaining size %ld\n", size); + throwDDlogException(env, "Buffer size too small for constructor"); + return 0; + } + const char* constructor = buf; + buf += constrsz; + size -= constrsz; + ddlog_record** fields = malloc(len * sizeof(ddlog_record*)); + if (fields == NULL) + return 0; + size_t index; + for (index = 0; index < len; index++) { + // fprintf(stderr, "Producing field %ld, remaining %ld\n", index, size); + jbyte kind = encodings[index]; + ddlog_record* rec; + if (kind & Kind_IsNull) { + // fprintf(stderr, "Field is NULL\n"); + rec = ddlog_struct_static_cons("None", NULL, 0); + fields[index] = rec; + continue; + } + switch (kind & 0x3F) { + case Kind_Long: { + expect = sizeof(jlong); + if (size < expect) { + fprintf(stderr, "Remaining size %ld\n", size); + throwDDlogException(env, "Buffer size too small for long"); + goto error; + } + jlong l = *(jlong*)buf; + buf += expect; + size -= expect; + rec = ddlog_i64(l); + break; + } + case Kind_String: { + expect = sizeof(jint); + if (size < expect) { + fprintf(stderr, "Remaining size %ld\n", size); + throwDDlogException(env, "Buffer size too small for string size"); + goto error; + } + jint sz = *(jint*)buf; + buf += expect; + size -= expect; + if (size < (size_t)sz) { + fprintf(stderr, "Remaining size %ld\n", size); + throwDDlogException(env, "Buffer size too small for string"); + goto error; + } + rec = ddlog_string_with_length(buf, sz); + buf += sz; + size -= sz; + break; + } + case Kind_Bool: { + expect = 1; + if (size < expect) { + fprintf(stderr, "Remaining size %ld\n", size); + throwDDlogException(env, "Buffer size too small for bool"); + goto error; + } + jbyte b = *buf; + buf += expect; + size -= expect; + rec = ddlog_bool(b); + break; + } + default: + throwDDlogException(env, "Unexpected kind"); + goto error; + } + if (kind & Kind_Nullable) + // Treat rec as an array with 1 element + rec = ddlog_struct_static_cons("Some", &rec, 1); + fields[index] = rec; + } + + if (size != 0) { + fprintf(stderr, "Remaining size %ld\n", size); + throwDDlogException(env, "The buffer was not consumed entirely"); + goto error; + } + ddlog_record* result = ddlog_struct_with_length(constructor, constrsz, fields, len); + free(fields); + (*env)->ReleaseByteArrayElements(env, bytes, original, JNI_ABORT); + return (jlong)result; + + error: + //for (size_t j = 0; j < index; j++) + //ddlog_free(fields[j]); + //free(fields); + (*env)->ReleaseByteArrayElements(env, bytes, original, JNI_ABORT); + return 0; +} diff --git a/java/ddlogapi/DDlogAPI.java b/java/ddlogapi/DDlogAPI.java index 63da6b1ac..ee4a75765 100644 --- a/java/ddlogapi/DDlogAPI.java +++ b/java/ddlogapi/DDlogAPI.java @@ -39,6 +39,10 @@ public class DDlogAPI { static native void ddlog_enable_cpu_profiling(long hprog, boolean enable) throws DDlogException; static native long ddlog_log_replace_callback(int module, long old_cbinfo, ObjIntConsumer cb, int max_level); static native long ddlog_log_replace_default_callback(long old_cbinfo, ObjIntConsumer cb, int max_level); + // These methods are here for supporting the SQL-to-DDlog compiler. + // Returns a handle to a ddlog_record. + public static native long ddlog_create_sql_record(byte[] buffer) throws DDlogException; + public static native byte[] ddlog_encode_record(long handle) throws DDlogException; static native void ddlog_free(long handle); @@ -805,17 +809,23 @@ public static boolean compileDDlogProgram( static boolean loaded = false; + public static void loadLibrary() { + if (loaded) + return; + final Path libraryPath = Paths.get(libName(ddlogLibrary)).toAbsolutePath(); + System.load(libraryPath.toString()); + } + /** * Load the the ddlogLibrary in the current process. * @return The API that can be used to interact with this library. */ public static DDlogAPI loadDDlog() throws DDlogException { if (loaded) - throw new RuntimeException("Attempt to load a secon dddlog library. " + throw new RuntimeException("Attempt to load a second dddlog library. " + " Only one library can be loaded safely."); + loadLibrary(); loaded = true; - final Path libraryPath = Paths.get(libName(ddlogLibrary)).toAbsolutePath(); - System.load(libraryPath.toString()); return new ddlogapi.DDlogAPI(1, null, false); } } diff --git a/java/ddlogapi/DDlogRecord.java b/java/ddlogapi/DDlogRecord.java index 970f6df81..3f0d0335f 100644 --- a/java/ddlogapi/DDlogRecord.java +++ b/java/ddlogapi/DDlogRecord.java @@ -57,7 +57,7 @@ private long checkHandle() { return this.handle; } - private static DDlogRecord fromHandle(long handle) { + public static DDlogRecord fromHandle(long handle) { DDlogRecord result = new DDlogRecord(); if (handle == 0) throw new RuntimeException("Received invalid handle."); @@ -70,7 +70,7 @@ private static DDlogRecord fromHandle(long handle) { * Creates an object where the handle is shared with other * objects. */ - static DDlogRecord fromSharedHandle(long handle) { + public static DDlogRecord fromSharedHandle(long handle) { DDlogRecord result = fromHandle(handle); result.shared = true; return result; @@ -297,7 +297,7 @@ public BigInteger getInt() { throw new RuntimeException("Unexpected error in DDlogAPI.ddlog_get_int()"); return new BigInteger(buf); } - + public int getTupleSize() { if (!DDlogAPI.ddlog_is_tuple(this.checkHandle())) throw new RuntimeException("Value is not a tuple"); @@ -426,9 +426,7 @@ public String toString() { } if (DDlogAPI.ddlog_is_string(this.handle)) { - String s = DDlogAPI.ddlog_get_str(this.handle); - // TODO: this should escape some characters... - return "\"" + s + "\""; + return DDlogAPI.ddlog_get_str(this.handle); } StringBuilder builder = new StringBuilder(); diff --git a/sql/install-ddlog-jar.sh b/sql/install-ddlog-jar.sh index f4c6c8779..a618b775e 100755 --- a/sql/install-ddlog-jar.sh +++ b/sql/install-ddlog-jar.sh @@ -1,4 +1,6 @@ -#!/bin/sh +#!/bin/bash -cd ../java +pushd ../java +make mvn install:install-file -Dfile=ddlogapi.jar -DgroupId=ddlog -DartifactId=ddlogapi -Dversion=0.1 -Dpackaging=jar +popd diff --git a/sql/src/main/java/com/vmware/ddlog/ir/DDlogProgram.java b/sql/src/main/java/com/vmware/ddlog/ir/DDlogProgram.java index f702f6bd6..56ba0827e 100644 --- a/sql/src/main/java/com/vmware/ddlog/ir/DDlogProgram.java +++ b/sql/src/main/java/com/vmware/ddlog/ir/DDlogProgram.java @@ -13,6 +13,7 @@ import com.vmware.ddlog.util.Linq; +import javax.annotation.Nullable; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.ArrayList; @@ -81,6 +82,14 @@ public boolean compare(DDlogProgram other, IComparePolicy policy) { return true; } + @Nullable + public DDlogRelationDeclaration getRelation(String name) { + for (DDlogRelationDeclaration d: this.relations) + if (d.getName().equals(name)) + return d; + return null; + } + public void toFile(String filename) throws FileNotFoundException { try (PrintWriter out = new PrintWriter(filename)) { out.println(this.toString()); diff --git a/sql/src/main/java/com/vmware/ddlog/ir/DDlogRelationDeclaration.java b/sql/src/main/java/com/vmware/ddlog/ir/DDlogRelationDeclaration.java index 0ced3382d..c42760c40 100644 --- a/sql/src/main/java/com/vmware/ddlog/ir/DDlogRelationDeclaration.java +++ b/sql/src/main/java/com/vmware/ddlog/ir/DDlogRelationDeclaration.java @@ -71,6 +71,13 @@ public DDlogType getType() { return this.type; } + @Nullable + public DDlogType getPrimaryKeyType() { + if (this.keyExpression != null) + return this.keyExpression.type; + return null; + } + public static String relationName(String name) { return "R" + name; } diff --git a/sql/src/main/java/com/vmware/ddlog/ir/DDlogTStruct.java b/sql/src/main/java/com/vmware/ddlog/ir/DDlogTStruct.java index 04d5b6b5f..59ed397fb 100644 --- a/sql/src/main/java/com/vmware/ddlog/ir/DDlogTStruct.java +++ b/sql/src/main/java/com/vmware/ddlog/ir/DDlogTStruct.java @@ -51,7 +51,6 @@ public DDlogType setMayBeNull(boolean mayBeNull) { return this; } - public String getName() { return this.name; } public List getFields() { return this.args; } @@ -81,4 +80,43 @@ public DDlogType getFieldType(String col) { this.error("Field " + col + " not present in struct " + this.name); return null; // unreachable } + + public enum Kind { + Null((byte)0x80), // set if a value is null + Nullable((byte)0x40), // set if a value may be null + Long((byte)0), + String((byte)1), + Bool((byte)2); + + public final byte encoding; + + Kind(byte encoding) { this.encoding = encoding; } + } + + @Nullable + private byte[] encodings = null; + + public SqlRecord createEmptyRecord() { + if (this.encodings == null) { + this.encodings = new byte[this.args.size()]; + for (int i = 0; i < this.args.size(); i++) { + DDlogType t = this.args.get(i).getType(); + byte mask = 0; + if (t.mayBeNull) { + mask = Kind.Nullable.encoding; + } + if (t.is(DDlogTSigned.class)) { + mask |= Kind.Long.encoding; + } else if (t.is(DDlogTBool.class)) { + mask |= Kind.Bool.encoding; + } else if (t.is(DDlogTString.class)) { + mask |= Kind.String.encoding; + } else { + throw new RuntimeException("Type not yet handled " + t); + } + this.encodings[i] = mask; + } + } + return new SqlRecord(this.name, this.args.size(), this.encodings); + } } diff --git a/sql/src/main/java/com/vmware/ddlog/ir/DDlogType.java b/sql/src/main/java/com/vmware/ddlog/ir/DDlogType.java index bf2141b65..4d8065ea7 100644 --- a/sql/src/main/java/com/vmware/ddlog/ir/DDlogType.java +++ b/sql/src/main/java/com/vmware/ddlog/ir/DDlogType.java @@ -38,7 +38,6 @@ protected DDlogType(boolean mayBeNull) { * True if the given type is a numeric type. * @param type Type to analyze. */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isNumeric(DDlogType type) { return type instanceof IsNumericType; } diff --git a/sql/src/main/java/com/vmware/ddlog/ir/SqlRecord.java b/sql/src/main/java/com/vmware/ddlog/ir/SqlRecord.java new file mode 100644 index 000000000..5de2f51dc --- /dev/null +++ b/sql/src/main/java/com/vmware/ddlog/ir/SqlRecord.java @@ -0,0 +1,171 @@ +package com.vmware.ddlog.ir; + +import ddlogapi.DDlogAPI; +import ddlogapi.DDlogException; +import ddlogapi.DDlogRecord; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Objects; + +/** + * This class is a simplified form of DDlogRecord optimized for SQL datatypes only. + */ +public class SqlRecord { + public final String constructor; + private final Object[] data; + private int index; + public final byte[] encodings; + + SqlRecord(String constructor, int capacity, byte[] encodings) { + this.constructor = constructor; + this.data = new Object[capacity]; + this.encodings = encodings.clone(); + this.index = 0; + if (capacity > Byte.MAX_VALUE) + throw new RuntimeException("Record too large: " + capacity); + } + + protected static String getString(ByteBuffer buffer, int size) { + String s = new String(buffer.array(), buffer.position(), size); + buffer.position(buffer.position() + size); + return s; + } + + public SqlRecord(byte[] data) { + ByteBuffer bs = ByteBuffer.wrap(data); + if (ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)) + bs.order(ByteOrder.BIG_ENDIAN); + else + bs.order(ByteOrder.LITTLE_ENDIAN); + int capacity = bs.getInt(); + this.data = new Object[capacity]; + this.encodings = new byte[capacity]; + bs.get(this.encodings); + int size = bs.getInt(); + this.constructor = getString(bs, size); + for (int i = 0; i < capacity; i++) { + byte enc = this.encodings[i]; + if ((enc & DDlogTStruct.Kind.Null.encoding) != 0) { + this.data[i] = null; + continue; + } + enc &= 0x3F; + if (enc == DDlogTStruct.Kind.Bool.encoding) { + this.data[i] = bs.get() == 1; + } else if (enc == DDlogTStruct.Kind.Long.encoding) { + this.data[i] = bs.getLong(); + } else if (enc == DDlogTStruct.Kind.String.encoding) { + size = bs.getInt(); + this.data[i] = getString(bs, size); + } else { + throw new RuntimeException("Unexpected encoding: 0x" + Integer.toHexString(this.encodings[i])); + } + } + if (bs.position() != bs.capacity()) + throw new RuntimeException("Not all data consumed"); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SqlRecord sqlRecord = (SqlRecord) o; + return constructor.equals(sqlRecord.constructor) && + Arrays.equals(data, sqlRecord.data); + } + + @Override + public int hashCode() { + int result = Objects.hash(constructor); + result = 31 * result + Arrays.hashCode(data); + return result; + } + + public int capacity() { + return this.data.length; + } + + private void checkNull(@Nullable Object o) { + if (o == null) + this.encodings[this.index] |= DDlogTStruct.Kind.Null.encoding; + } + + public void add(@Nullable Object i) { + this.checkNull(i); + this.data[this.index++] = i; + } + + public byte[] getEncoding() { + if (this.index != this.capacity()) + throw new RuntimeException("Object has " + this.capacity() + " size, but only " + + this.index + " fields are present"); + // First pass: compute buffer size + int size = 0; + size += 4; // capacity + size += this.encodings.length; + byte[] constructor = this.constructor.getBytes(); + byte[][] bufs = new byte[this.capacity()][]; + size += 4 + constructor.length; + for (int i = 0; i < this.data.length; i++) { + Object o = this.data[i]; + if (o == null) + continue; + byte enc = (byte)(this.encodings[i] & 0x3F); + if (enc == DDlogTStruct.Kind.Bool.encoding) { + size += 1; + } else if (enc == DDlogTStruct.Kind.Long.encoding) { + size += 8; + } else if (enc == DDlogTStruct.Kind.String.encoding) { + bufs[i] = ((String)o).getBytes(); + size += 4 + bufs[i].length; + } else { + throw new RuntimeException("Unhandled type " + o.getClass()); + } + } + + ByteBuffer bb = ByteBuffer.allocate(size); + if (ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN)) + bb.order(ByteOrder.BIG_ENDIAN); + else + bb.order(ByteOrder.LITTLE_ENDIAN); + bb.putInt(this.capacity()); + bb.put(this.encodings); + bb.putInt(constructor.length); + bb.put(constructor); + for (int i = 0; i < this.data.length; i++) { + Object o = this.data[i]; + byte enc = (byte)(this.encodings[i] & 0x3F); + if (o == null) { + assert (this.encodings[i] & DDlogTStruct.Kind.Nullable.encoding) != 0; + this.encodings[i] |= DDlogTStruct.Kind.Null.encoding; + continue; + } + if (enc == DDlogTStruct.Kind.Bool.encoding) { + bb.put((byte)((Boolean)o ? 1 : 0)); + } else if (enc == DDlogTStruct.Kind.Long.encoding) { + bb.putLong((long)o); + } else if (enc == DDlogTStruct.Kind.String.encoding) { + bb.putInt(bufs[i].length); + bb.put(bufs[i]); + } else { + throw new RuntimeException("Unhandled type " + o.getClass()); + } + } + return bb.array(); + } + + @Nullable + public Object getData(int index) { + return this.data[index]; + } + + public DDlogRecord createRecord() throws DDlogException { + byte[] encoding = this.getEncoding(); + System.out.println(); + long handle = DDlogAPI.ddlog_create_sql_record(encoding); + return DDlogRecord.fromHandle(handle); + } +} diff --git a/sql/src/main/java/com/vmware/ddlog/translator/Translator.java b/sql/src/main/java/com/vmware/ddlog/translator/Translator.java index 473e287da..1632b09e1 100644 --- a/sql/src/main/java/com/vmware/ddlog/translator/Translator.java +++ b/sql/src/main/java/com/vmware/ddlog/translator/Translator.java @@ -19,6 +19,7 @@ // If these are missing you have not run the sql/install-ddlog-jar.sh script import com.vmware.ddlog.ir.DDlogIRNode; import com.vmware.ddlog.ir.DDlogProgram; +import com.vmware.ddlog.ir.DDlogType; import org.jooq.DSLContext; import org.jooq.Field; @@ -80,6 +81,10 @@ public Map, List>> getTablesAndFields(final DSLContex return tablesToFields; } + public DDlogType resolveType(DDlogType type) { + return this.translationContext.resolveType(type); + } + public DDlogProgram generateSqlLibrary() { return SqlSemantics.semantics.generateLibrary(); } diff --git a/sql/src/test/java/ddlog/SqlRecordTest.java b/sql/src/test/java/ddlog/SqlRecordTest.java new file mode 100644 index 000000000..70070b55f --- /dev/null +++ b/sql/src/test/java/ddlog/SqlRecordTest.java @@ -0,0 +1,199 @@ +package ddlog; + +import com.vmware.ddlog.ir.*; +import com.vmware.ddlog.translator.Translator; +import ddlogapi.DDlogAPI; +import ddlogapi.DDlogException; +import ddlogapi.DDlogRecord; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class SqlRecordTest extends BaseQueriesTest { + @Test + public void sqlRecordTest() { + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance)); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64)); + fields.add(new DDlogField(null,"b", DDlogTBool.instance)); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add("String"); + rec.add(2L); + rec.add(true); + byte[] encoding = rec.getEncoding(); + SqlRecord rec1 = new SqlRecord(encoding); + Assert.assertEquals(rec.capacity(), rec1.capacity()); + Assert.assertEquals(rec.constructor, rec1.constructor); + Assert.assertEquals("String", rec1.getData(0)); + Assert.assertEquals(2L, rec1.getData(1)); + Assert.assertEquals(true, rec1.getData(2)); + } + + @Test + public void sqlRecordTestNullable() { + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance.setMayBeNull(true))); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64.setMayBeNull(true))); + fields.add(new DDlogField(null,"b", DDlogTBool.instance.setMayBeNull(true))); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add("String"); + rec.add(2L); + rec.add(true); + byte[] encoding = rec.getEncoding(); + SqlRecord rec1 = new SqlRecord(encoding); + Assert.assertEquals(rec.capacity(), rec1.capacity()); + Assert.assertEquals(rec.constructor, rec1.constructor); + Assert.assertEquals("String", rec1.getData(0)); + Assert.assertEquals(2L, rec1.getData(1)); + Assert.assertEquals(true, rec1.getData(2)); + } + + @Test(expected = RuntimeException.class) + public void notEnoughFieldsTest() { + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance)); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64)); + fields.add(new DDlogField(null,"b", DDlogTBool.instance)); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add("String"); + rec.add(2L); + rec.getEncoding(); + } + + @Test(expected = ClassCastException.class) + public void wrongDataTest() { + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance)); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64)); + fields.add(new DDlogField(null,"b", DDlogTBool.instance)); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add("String"); + rec.add(2L); + rec.add(3L); + rec.getEncoding(); + } + + @Test + public void sqlRecordTestNull() { + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance.setMayBeNull(true))); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64.setMayBeNull(true))); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add(null); + rec.add(null); + byte[] encoding = rec.getEncoding(); + SqlRecord rec1 = new SqlRecord(encoding); + Assert.assertEquals(rec.capacity(), rec1.capacity()); + Assert.assertEquals(rec.constructor, rec1.constructor); + Assert.assertNull(rec1.getData(0)); + Assert.assertNull(rec1.getData(1)); + } + + @Test + public void sqlRecordTestSomeNull() { + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance.setMayBeNull(true))); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64)); + fields.add(new DDlogField(null,"b", DDlogTBool.instance.setMayBeNull(true))); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add(null); + rec.add(3L); + rec.add(null); + byte[] encoding = rec.getEncoding(); + SqlRecord rec1 = new SqlRecord(encoding); + Assert.assertEquals(rec.capacity(), rec1.capacity()); + Assert.assertEquals(rec.constructor, rec1.constructor); + Assert.assertNull(rec1.getData(0)); + Assert.assertEquals(3L, rec1.getData(1)); + Assert.assertNull(rec1.getData(2)); + } + + @Test + public void sqlToDDlogRecordTest() throws DDlogException { + DDlogAPI.loadLibrary(); + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance)); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64)); + fields.add(new DDlogField(null,"b", DDlogTBool.instance)); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add("String"); + rec.add(2L); + rec.add(true); + DDlogRecord ddrec = rec.createRecord(); + Assert.assertNotNull(ddrec); + Assert.assertTrue(ddrec.isStruct()); + Assert.assertEquals(rec.constructor, ddrec.getStructName()); + Assert.assertEquals(rec.getData(0), ddrec.getStructField(0).getString()); + Assert.assertEquals(rec.getData(1), ddrec.getStructField(1).getInt().longValue()); + Assert.assertEquals(rec.getData(2), ddrec.getStructField(2).getBoolean()); + } + + @Test + public void sqlToDDlogRecordTestNullable() throws DDlogException { + DDlogAPI.loadLibrary(); + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance.setMayBeNull(true))); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64.setMayBeNull(true))); + fields.add(new DDlogField(null,"b", DDlogTBool.instance.setMayBeNull(true))); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add("String"); + rec.add(2L); + rec.add(true); + DDlogRecord ddrec = rec.createRecord(); + Assert.assertNotNull(ddrec); + Assert.assertTrue(ddrec.isStruct()); + Assert.assertEquals(rec.constructor, ddrec.getStructName()); + Assert.assertEquals(rec.getData(0), ddrec.getStructField(0).getStructField(0).getString()); + Assert.assertEquals(rec.getData(1), ddrec.getStructField(1).getStructField(0).getInt().longValue()); + Assert.assertEquals(rec.getData(2), ddrec.getStructField(2).getStructField(0).getBoolean()); + } + + void showByteArray(byte[] data) { + for (byte b: data) + System.out.print(Integer.toHexString(b) + " "); + System.out.println(); + } + + @Test + public void twoWayConversion() throws DDlogException { + DDlogAPI.loadLibrary(); + List fields = new ArrayList(); + fields.add(new DDlogField(null,"s", DDlogTString.instance)); + fields.add(new DDlogField(null,"x", DDlogTSigned.signed64)); + fields.add(new DDlogField(null,"b", DDlogTBool.instance)); + DDlogTStruct struct = new DDlogTStruct(null, "table", fields); + SqlRecord rec = struct.createEmptyRecord(); + rec.add("String"); + rec.add(2L); + rec.add(true); + byte[] original = rec.getEncoding(); + DDlogRecord ddrec = rec.createRecord(); + byte[] encoding = DDlogAPI.ddlog_encode_record(ddrec.getHandleAndInvalidate()); + Assert.assertArrayEquals(original, encoding); + SqlRecord rec1 = new SqlRecord(encoding); + Assert.assertEquals(rec, rec1); + } + + @Test + public void createRecordFromRelation() { + Translator t = this.createInputTables(false); + DDlogProgram ddprogram = t.getDDlogProgram(); + DDlogRelationDeclaration decl = ddprogram.getRelation("Rt2"); + Assert.assertNotNull(decl); + DDlogType type = decl.getType(); + DDlogTStruct ts = t.resolveType(type).to(DDlogTStruct.class); + SqlRecord rec = ts.createEmptyRecord(); + rec.add(2L); + Assert.assertNotNull(rec.getEncoding()); + } +}