iOS and Android (Java) sdk framework (#643)
Documents the FFI layer for Mentat, and provides transaction functionality via an EDN string. Creates two native libraries for iOS (Swift) and Android (Java) and fully tests the FFI for both platforms. Closes #619 #614 #611
This commit is contained in:
parent
60cb5d2432
commit
013629dec6
76 changed files with 6545 additions and 288 deletions
25
.gitignore
vendored
25
.gitignore
vendored
|
@ -54,3 +54,28 @@ pom.xml.asc
|
|||
/fixtures/*.db-shm
|
||||
/fixtures/*.db-wal
|
||||
/query-parser/out/
|
||||
## Build generated
|
||||
/sdks/swift/Mentat/build/
|
||||
DerivedData
|
||||
build.xcarchive
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
/sdks/swift/Mentat/*.xcodeproj/project.xcworkspace/xcuserdata
|
||||
|
||||
## Other
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
*.xcuserstate
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
4
android_build_all.sh
Executable file
4
android_build_all.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
# This will eventually become a complete build script, not just for Android
|
||||
cargo build -p mentat_ffi --target i686-linux-android --release
|
||||
cargo build -p mentat_ffi --target armv7-linux-androideabi --release
|
||||
cargo build -p mentat_ffi --target aarch64-linux-android --release
|
|
@ -19,7 +19,7 @@ use rustc_version::{
|
|||
|
||||
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
|
||||
/// to build the project.
|
||||
static MIN_VERSION: &'static str = "1.24.0";
|
||||
static MIN_VERSION: &'static str = "1.25.0";
|
||||
|
||||
fn main() {
|
||||
let ver = version().unwrap();
|
||||
|
|
|
@ -755,6 +755,27 @@ impl Binding {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_c_string(self) -> Option<*mut c_char> {
|
||||
match self {
|
||||
Binding::Scalar(v) => v.into_c_string(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_kw_c_string(self) -> Option<*mut c_char> {
|
||||
match self {
|
||||
Binding::Scalar(v) => v.into_kw_c_string(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_uuid_c_string(self) -> Option<*mut c_char> {
|
||||
match self {
|
||||
Binding::Scalar(v) => v.into_uuid_c_string(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
[package]
|
||||
name = "mentat_ffi"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["Emily Toop <etoop@mozilla.com>"]
|
||||
|
||||
[lib]
|
||||
name = "mentat_ffi"
|
||||
crate-type = ["lib", "staticlib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[dependencies.mentat]
|
||||
path = ".."
|
||||
path = "../"
|
||||
|
|
802
ffi/src/lib.rs
802
ffi/src/lib.rs
File diff suppressed because it is too large
Load diff
|
@ -19,19 +19,18 @@ pub mod strings {
|
|||
Keyword,
|
||||
};
|
||||
|
||||
pub fn c_char_to_string(cchar: *const c_char) -> String {
|
||||
pub fn c_char_to_string(cchar: *const c_char) -> &'static str {
|
||||
let c_str = unsafe { CStr::from_ptr(cchar) };
|
||||
let r_str = c_str.to_str().unwrap_or("");
|
||||
r_str.to_string()
|
||||
c_str.to_str().unwrap_or("")
|
||||
}
|
||||
|
||||
pub fn string_to_c_char<T>(r_string: T) -> *mut c_char where T: Into<String> {
|
||||
CString::new(r_string.into()).unwrap().into_raw()
|
||||
}
|
||||
|
||||
pub fn kw_from_string(keyword_string: &'static str) -> Keyword {
|
||||
// TODO: validate. The input might not be a keyword!
|
||||
pub fn kw_from_string(mut keyword_string: String) -> Keyword {
|
||||
let attr_name = keyword_string.split_off(1);
|
||||
let attr_name = keyword_string.trim_left_matches(":");
|
||||
let parts: Vec<&str> = attr_name.split("/").collect();
|
||||
Keyword::namespaced(parts[0], parts[1])
|
||||
}
|
||||
|
|
9
sdks/android/Mentat/.gitignore
vendored
Normal file
9
sdks/android/Mentat/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
17
sdks/android/Mentat/.project
Normal file
17
sdks/android/Mentat/.project
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Mentat</name>
|
||||
<comment>Project Mentat created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
27
sdks/android/Mentat/build.gradle
Normal file
27
sdks/android/Mentat/build.gradle
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.60'
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
17
sdks/android/Mentat/gradle.properties
Normal file
17
sdks/android/Mentat/gradle.properties
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
6
sdks/android/Mentat/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
sdks/android/Mentat/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#Thu Mar 29 09:07:24 BST 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
160
sdks/android/Mentat/gradlew
vendored
Executable file
160
sdks/android/Mentat/gradlew
vendored
Executable file
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
90
sdks/android/Mentat/gradlew.bat
vendored
Normal file
90
sdks/android/Mentat/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
6
sdks/android/Mentat/library/.classpath
Normal file
6
sdks/android/Mentat/library/.classpath
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin/default"/>
|
||||
</classpath>
|
1
sdks/android/Mentat/library/.gitignore
vendored
Normal file
1
sdks/android/Mentat/library/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
23
sdks/android/Mentat/library/.project
Normal file
23
sdks/android/Mentat/library/.project
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>app</name>
|
||||
<comment>Project app created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
42
sdks/android/Mentat/library/build.gradle
Normal file
42
sdks/android/Mentat/library/build.gradle
Normal file
|
@ -0,0 +1,42 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
defaultConfig {
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
androidTest.assets.srcDirs += '../../../../fixtures'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
androidTestImplementation 'com.android.support:support-annotations:24.0.0'
|
||||
androidTestImplementation 'com.android.support.test:runner:0.5'
|
||||
androidTestImplementation 'com.android.support.test:rules:0.5'
|
||||
implementation 'com.android.support:appcompat-v7:26.1.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
implementation 'com.android.support:design:26.1.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||
implementation 'net.java.dev.jna:jna:4.5.1'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
25
sdks/android/Mentat/library/proguard-rules.pro
vendored
Normal file
25
sdks/android/Mentat/library/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ~/.android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,27 @@
|
|||
/* -*- 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 java.util.EventListener;
|
||||
|
||||
interface ExpectationEventListener extends EventListener {
|
||||
public void fulfill();
|
||||
}
|
||||
|
||||
public class Expectation implements ExpectationEventListener {
|
||||
public boolean isFulfilled = false;
|
||||
public void fulfill() {
|
||||
this.isFulfilled = true;
|
||||
synchronized (this) {
|
||||
notifyAll( );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,725 @@
|
|||
/* -*- 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.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
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;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumentation test, which will execute on an Android device.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FFIIntegrationTest {
|
||||
|
||||
Mentat mentat = null;
|
||||
|
||||
@Test
|
||||
public void openInMemoryStoreSucceeds() throws Exception {
|
||||
Mentat mentat = new Mentat();
|
||||
assertNotNull(mentat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void openStoreInLocationSucceeds() throws Exception {
|
||||
Context context = InstrumentationRegistry.getTargetContext();
|
||||
String path = context.getDatabasePath("test.db").getAbsolutePath();
|
||||
Mentat mentat = new Mentat(path);
|
||||
assertNotNull(mentat);
|
||||
}
|
||||
|
||||
public String readFile(String fileName) {
|
||||
Context testContext = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
AssetManager assetManager = testContext.getAssets();
|
||||
try {
|
||||
InputStream inputStream = assetManager.open(fileName);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
StringBuilder out = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
out.append(line + "\n");
|
||||
}
|
||||
return out.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TxReport transactCitiesSchema(Mentat mentat) {
|
||||
String citiesSchema = this.readFile("cities.schema");
|
||||
return mentat.transact(citiesSchema);
|
||||
}
|
||||
|
||||
public TxReport transactSeattleData(Mentat mentat) {
|
||||
String seattleData = this.readFile("all_seattle.edn");
|
||||
return mentat.transact(seattleData);
|
||||
}
|
||||
|
||||
public Mentat openAndInitializeCitiesStore() {
|
||||
if (this.mentat == null) {
|
||||
this.mentat = new Mentat();
|
||||
this.transactCitiesSchema(mentat);
|
||||
this.transactSeattleData(mentat);
|
||||
}
|
||||
|
||||
return this.mentat;
|
||||
}
|
||||
|
||||
public TxReport populateWithTypesSchema(Mentat mentat) {
|
||||
String schema = "[\n" +
|
||||
" [:db/add \"b\" :db/ident :foo/boolean]\n" +
|
||||
" [:db/add \"b\" :db/valueType :db.type/boolean]\n" +
|
||||
" [:db/add \"b\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" [:db/add \"l\" :db/ident :foo/long]\n" +
|
||||
" [:db/add \"l\" :db/valueType :db.type/long]\n" +
|
||||
" [:db/add \"l\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" [:db/add \"r\" :db/ident :foo/ref]\n" +
|
||||
" [:db/add \"r\" :db/valueType :db.type/ref]\n" +
|
||||
" [:db/add \"r\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" [:db/add \"i\" :db/ident :foo/instant]\n" +
|
||||
" [:db/add \"i\" :db/valueType :db.type/instant]\n" +
|
||||
" [:db/add \"i\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" [:db/add \"d\" :db/ident :foo/double]\n" +
|
||||
" [:db/add \"d\" :db/valueType :db.type/double]\n" +
|
||||
" [:db/add \"d\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" [:db/add \"s\" :db/ident :foo/string]\n" +
|
||||
" [:db/add \"s\" :db/valueType :db.type/string]\n" +
|
||||
" [:db/add \"s\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" [:db/add \"k\" :db/ident :foo/keyword]\n" +
|
||||
" [:db/add \"k\" :db/valueType :db.type/keyword]\n" +
|
||||
" [:db/add \"k\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" [:db/add \"u\" :db/ident :foo/uuid]\n" +
|
||||
" [:db/add \"u\" :db/valueType :db.type/uuid]\n" +
|
||||
" [:db/add \"u\" :db/cardinality :db.cardinality/one]\n" +
|
||||
" ]";
|
||||
TxReport report = mentat.transact(schema);
|
||||
Long stringEntid = report.getEntidForTempId("s");
|
||||
|
||||
String data = "[\n" +
|
||||
" [:db/add \"a\" :foo/boolean true]\n" +
|
||||
" [:db/add \"a\" :foo/long 25]\n" +
|
||||
" [:db/add \"a\" :foo/instant #inst \"2017-01-01T11:00:00.000Z\"]\n" +
|
||||
" [:db/add \"a\" :foo/double 11.23]\n" +
|
||||
" [:db/add \"a\" :foo/string \"The higher we soar the smaller we appear to those who cannot fly.\"]\n" +
|
||||
" [:db/add \"a\" :foo/keyword :foo/string]\n" +
|
||||
" [: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/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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactingVocabularySucceeds() {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport schemaReport = this.transactCitiesSchema(mentat);
|
||||
assertNotNull(schemaReport);
|
||||
assertTrue(schemaReport.getTxId() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactingEntitiesSucceeds() {
|
||||
Mentat mentat = new Mentat();
|
||||
this.transactCitiesSchema(mentat);
|
||||
TxReport dataReport = this.transactSeattleData(mentat);
|
||||
assertNotNull(dataReport);
|
||||
assertTrue(dataReport.getTxId() > 0);
|
||||
Long entid = dataReport.getEntidForTempId("a17592186045605");
|
||||
assertEquals(65733, entid.longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runScalarSucceeds() throws InterruptedException {
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals("KOMO Communities - Wallingford", value.asString());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runCollSucceeds() throws InterruptedException {
|
||||
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() {
|
||||
@Override
|
||||
public void handleList(CollResult list) {
|
||||
assertNotNull(list);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
assertNotNull(list.asDate(i));
|
||||
}
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runCollResultIteratorSucceeds() throws InterruptedException {
|
||||
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() {
|
||||
@Override
|
||||
public void handleList(CollResult list) {
|
||||
assertNotNull(list);
|
||||
|
||||
for(TypedValue value: list) {
|
||||
assertNotNull(value.asDate());
|
||||
}
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runTupleSucceeds() throws InterruptedException {
|
||||
Mentat mentat = openAndInitializeCitiesStore();
|
||||
String query = "[:find [?name ?cat]\n" +
|
||||
" :where\n" +
|
||||
" [?c :community/name ?name]\n" +
|
||||
" [?c :community/type :community.type/website]\n" +
|
||||
" [(fulltext $ :community/category \"food\") [[?c ?cat]]]]";
|
||||
final Expectation expectation = new Expectation();
|
||||
mentat.query(query).runTuple(new TupleResultHandler() {
|
||||
@Override
|
||||
public void handleRow(TupleResult row) {
|
||||
assertNotNull(row);
|
||||
String name = row.asString(0);
|
||||
String category = row.asString(1);
|
||||
assert(name == "Community Harvest of Southwest Seattle");
|
||||
assert(category == "sustainable food");
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runRelIteratorSucceeds() throws InterruptedException {
|
||||
Mentat mentat = openAndInitializeCitiesStore();
|
||||
String query = "[:find ?name ?cat\n" +
|
||||
" :where\n" +
|
||||
" [?c :community/name ?name]\n" +
|
||||
" [?c :community/type :community.type/website]\n" +
|
||||
" [(fulltext $ :community/category \"food\") [[?c ?cat]]]]";
|
||||
|
||||
final LinkedHashMap expectedResults = new LinkedHashMap<String, String>();
|
||||
expectedResults.put("InBallard", "food");
|
||||
expectedResults.put("Seattle Chinatown Guide", "food");
|
||||
expectedResults.put("Community Harvest of Southwest Seattle", "sustainable food");
|
||||
expectedResults.put("University District Food Bank", "food bank");
|
||||
final Expectation expectation = new Expectation();
|
||||
mentat.query(query).run(new RelResultHandler() {
|
||||
@Override
|
||||
public void handleRows(RelResult rows) {
|
||||
assertNotNull(rows);
|
||||
int index = 0;
|
||||
for (TupleResult row: rows) {
|
||||
String name = row.asString(0);
|
||||
assertNotNull(name);
|
||||
String category = row.asString(1);
|
||||
assertNotNull(category);
|
||||
String expectedCategory = expectedResults.get(name).toString();
|
||||
assertNotNull(expectedCategory);
|
||||
assertEquals(expectedCategory, category);
|
||||
++index;
|
||||
}
|
||||
assertEquals(expectedResults.size(), index);
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runRelSucceeds() throws InterruptedException {
|
||||
Mentat mentat = openAndInitializeCitiesStore();
|
||||
String query = "[:find ?name ?cat\n" +
|
||||
" :where\n" +
|
||||
" [?c :community/name ?name]\n" +
|
||||
" [?c :community/type :community.type/website]\n" +
|
||||
" [(fulltext $ :community/category \"food\") [[?c ?cat]]]]";
|
||||
|
||||
final LinkedHashMap expectedResults = new LinkedHashMap<String, String>();
|
||||
expectedResults.put("InBallard", "food");
|
||||
expectedResults.put("Seattle Chinatown Guide", "food");
|
||||
expectedResults.put("Community Harvest of Southwest Seattle", "sustainable food");
|
||||
expectedResults.put("University District Food Bank", "food bank");
|
||||
final Expectation expectation = new Expectation();
|
||||
mentat.query(query).run(new RelResultHandler() {
|
||||
@Override
|
||||
public void handleRows(RelResult rows) {
|
||||
assertNotNull(rows);
|
||||
for (int i = 0; i < expectedResults.size(); ++i) {
|
||||
TupleResult row = rows.rowAtIndex(i);
|
||||
assertNotNull(row);
|
||||
String name = row.asString(0);
|
||||
assertNotNull(name);
|
||||
String category = row.asString(1);
|
||||
assertNotNull(category);
|
||||
String expectedCategory = expectedResults.get(name).toString();
|
||||
assertNotNull(expectedCategory);
|
||||
assertEquals(expectedCategory, category);
|
||||
}
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingLongValueSucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingRefValueSucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(bEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingRefKwValueSucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(bEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingKwValueSucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingDateValueSucceeds() throws InterruptedException, ParseException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleRow(TupleResult row) {
|
||||
assertNotNull(row);
|
||||
TypedValue value = row.get(0);
|
||||
assertNotNull(value);
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingStringValueSucceeds() throws InterruptedException {
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals("KOMO Communities - Wallingford", value.asString());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingUuidValueSucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingBooleanValueSucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindingDoubleValueSucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToLong() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(25, value.asLong().longValue());
|
||||
assertEquals(25, value.asLong().longValue());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToRef() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
assertEquals(aEntid, value.asEntid());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToKeyword() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(":foo/string", value.asKeyword());
|
||||
assertEquals(":foo/string", value.asKeyword());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToBoolean() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(true, value.asBoolean());
|
||||
assertEquals(true, value.asBoolean());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToDouble() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(new Double(11.23), value.asDouble());
|
||||
assertEquals(new Double(11.23), value.asDouble());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToDate() throws InterruptedException, ParseException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(expectedDate.getTime(), value.asDate());
|
||||
assertEquals(expectedDate.getTime(), value.asDate());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToString() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals("The higher we soar the smaller we appear to those who cannot fly.", value.asString());
|
||||
assertEquals("The higher we soar the smaller we appear to those who cannot fly.", value.asString());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedValueConvertsToUUID() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
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() {
|
||||
@Override
|
||||
public void handleValue(TypedValue value) {
|
||||
assertNotNull(value);
|
||||
assertEquals(expectedUUID, value.asUUID());
|
||||
assertEquals(expectedUUID, value.asUUID());
|
||||
expectation.fulfill();
|
||||
}
|
||||
});
|
||||
synchronized (expectation) {
|
||||
expectation.wait(1000);
|
||||
}
|
||||
assertTrue(expectation.isFulfilled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void valueForAttributeOfEntitySucceeds() throws InterruptedException {
|
||||
Mentat mentat = new Mentat();
|
||||
TxReport report = this.populateWithTypesSchema(mentat);
|
||||
final Long aEntid = report.getEntidForTempId("a");
|
||||
TypedValue value = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
|
||||
assertNotNull(value);
|
||||
assertEquals(25, value.asLong().longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entidForAttributeSucceeds() {
|
||||
Mentat mentat = new Mentat();
|
||||
this.populateWithTypesSchema(mentat);
|
||||
long entid = mentat.entIdForAttribute(":foo/long");
|
||||
assertEquals(65540, entid);
|
||||
}
|
||||
}
|
6
sdks/android/Mentat/library/src/main/AndroidManifest.xml
Normal file
6
sdks/android/Mentat/library/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mozilla.mentat">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
</manifest>
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- 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 com.sun.jna.Structure;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a C struct of a list of Strings containing attributes in the format
|
||||
* `:namespace/name`.
|
||||
*/
|
||||
public class AttributeList extends Structure implements Closeable {
|
||||
public static class ByReference extends AttributeList implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static class ByValue extends AttributeList implements Structure.ByValue {
|
||||
}
|
||||
|
||||
public IntByReference attributes;
|
||||
public int numberOfItems;
|
||||
// Used by the Swift counterpart, JNA does this for us automagically.
|
||||
// But we still need it here so that the number of fields and their order is correct
|
||||
public int len;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList("attributes", "numberOfItems", "len");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.getPointer() != null) {
|
||||
JNA.INSTANCE.destroy(this.getPointer());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Iterator for a {@link CollResult}
|
||||
*/
|
||||
public class ColResultIterator extends RustObject implements Iterator {
|
||||
|
||||
Pointer nextPointer;
|
||||
|
||||
ColResultIterator(Pointer iterator) {
|
||||
this.rawPointer = iterator;
|
||||
}
|
||||
|
||||
private Pointer getNextPointer() {
|
||||
return JNA.INSTANCE.typed_value_list_iter_next(this.rawPointer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
this.nextPointer = getNextPointer();
|
||||
return this.nextPointer != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedValue next() {
|
||||
Pointer next = this.nextPointer == null ? getNextPointer() : this.nextPointer;
|
||||
if (next == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TypedValue(next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.typed_value_list_iter_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Wraps a `Coll` result from a Mentat query.
|
||||
* A `Coll` result is a list of rows of single values of type {@link TypedValue}.
|
||||
* Values for individual rows can be fetched as {@link TypedValue} or converted into a requested type.
|
||||
* <p>
|
||||
* Row values can be fetched as one of the following types:
|
||||
* <ul>
|
||||
* <li>{@link TypedValue}</li>
|
||||
* <li>long</li>
|
||||
* <li>Entid (as long)</li>
|
||||
* <li>Keyword (as String)</li>
|
||||
* <li>boolean</li>
|
||||
* <li>double</li>
|
||||
* <li>{@link Date}</li>
|
||||
* <li>{@link String}</li>
|
||||
* <li>{@link UUID}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* To iterate over the result set use standard iteration flows.
|
||||
*/
|
||||
public class CollResult extends TupleResult implements Iterable<TypedValue> {
|
||||
|
||||
public CollResult(Pointer pointer) {
|
||||
super(pointer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColResultIterator iterator() {
|
||||
Pointer iterPointer = JNA.INSTANCE.typed_value_list_into_iter(this.rawPointer);
|
||||
this.rawPointer = null;
|
||||
if (iterPointer == null) {
|
||||
return null;
|
||||
}
|
||||
return new ColResultIterator(iterPointer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- 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;
|
||||
|
||||
/**
|
||||
* Interface defining the structure of a callback from a query returning a {@link CollResult}.
|
||||
*/
|
||||
public interface CollResultHandler {
|
||||
void handleList(CollResult list);
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- 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 com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.NativeLibrary;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
/**
|
||||
* JNA interface for FFI to Mentat's Rust library
|
||||
* Each function definition here link directly to a function in Mentat's FFI crate.
|
||||
* Signatures must match for the linking to work correctly.
|
||||
*/
|
||||
public interface JNA extends Library {
|
||||
String JNA_LIBRARY_NAME = "mentat_ffi";
|
||||
NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(JNA_LIBRARY_NAME);
|
||||
|
||||
JNA INSTANCE = (JNA) Native.loadLibrary(JNA_LIBRARY_NAME, JNA.class);
|
||||
|
||||
Pointer store_open(String dbPath);
|
||||
|
||||
void destroy(Pointer obj);
|
||||
void query_builder_destroy(Pointer obj);
|
||||
void store_destroy(Pointer obj);
|
||||
void typed_value_destroy(Pointer obj);
|
||||
void typed_value_list_destroy(Pointer obj);
|
||||
void typed_value_list_iter_destroy(Pointer obj);
|
||||
void typed_value_result_set_destroy(Pointer obj);
|
||||
void typed_value_result_set_iter_destroy(Pointer obj);
|
||||
void tx_report_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);
|
||||
|
||||
// sync
|
||||
RustResult store_sync(Pointer store, String userUuid, String serverUri);
|
||||
|
||||
// observers
|
||||
void store_register_observer(Pointer store, String key, Pointer attributes, int len, TxObserverCallback callback);
|
||||
void store_unregister_observer(Pointer store, String key);
|
||||
long store_entid_for_attribute(Pointer store, String attr);
|
||||
|
||||
// Query Building
|
||||
Pointer store_query(Pointer store, String query);
|
||||
RustResult store_value_for_attribute(Pointer store, long entid, String attribute);
|
||||
void query_builder_bind_long(Pointer query, String var, long value);
|
||||
void query_builder_bind_ref(Pointer query, String var, long value);
|
||||
void query_builder_bind_ref_kw(Pointer query, String var, String value);
|
||||
void query_builder_bind_kw(Pointer query, String var, String value);
|
||||
void query_builder_bind_boolean(Pointer query, String var, int value);
|
||||
void query_builder_bind_double(Pointer query, String var, double value);
|
||||
void query_builder_bind_timestamp(Pointer query, String var, long value);
|
||||
void query_builder_bind_string(Pointer query, String var, String value);
|
||||
void query_builder_bind_uuid(Pointer query, String var, Pointer value);
|
||||
|
||||
// Query Execution
|
||||
RustResult query_builder_execute(Pointer query);
|
||||
RustResult query_builder_execute_scalar(Pointer query);
|
||||
RustResult query_builder_execute_coll(Pointer query);
|
||||
RustResult query_builder_execute_tuple(Pointer query);
|
||||
|
||||
// Query Result Processing
|
||||
long typed_value_into_long(Pointer value);
|
||||
long typed_value_into_entid(Pointer value);
|
||||
String typed_value_into_kw(Pointer value);
|
||||
String typed_value_into_string(Pointer value);
|
||||
Pointer typed_value_into_uuid(Pointer value);
|
||||
int typed_value_into_boolean(Pointer value);
|
||||
double typed_value_into_double(Pointer value);
|
||||
long typed_value_into_timestamp(Pointer value);
|
||||
Pointer typed_value_value_type(Pointer value);
|
||||
|
||||
Pointer row_at_index(Pointer rows, int index);
|
||||
Pointer typed_value_result_set_into_iter(Pointer rows);
|
||||
Pointer typed_value_result_set_iter_next(Pointer iter);
|
||||
|
||||
Pointer typed_value_list_into_iter(Pointer rows);
|
||||
Pointer typed_value_list_iter_next(Pointer iter);
|
||||
|
||||
Pointer value_at_index(Pointer rows, int index);
|
||||
long value_at_index_into_long(Pointer rows, int index);
|
||||
long value_at_index_into_entid(Pointer rows, int index);
|
||||
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);
|
||||
double value_at_index_into_double(Pointer rows, int index);
|
||||
long value_at_index_into_timestamp(Pointer rows, int index);
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/* -*- 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;
|
||||
|
||||
/**
|
||||
* The primary class for accessing Mentat's API.<br/>
|
||||
* This class provides all of the basic API that can be found in Mentat's Store struct.<br/>
|
||||
* The raw pointer it holds is a pointer to a Store.
|
||||
*/
|
||||
public class Mentat extends RustObject {
|
||||
|
||||
static {
|
||||
System.loadLibrary("mentat_ffi");
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a connection to a Store in a given location.<br/>
|
||||
* If the store does not already exist, one will be created.
|
||||
* @param dbPath The URI as a String of the store to open.
|
||||
*/
|
||||
public Mentat(String dbPath) {
|
||||
this.rawPointer = JNA.INSTANCE.store_open(dbPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a connection to an in-memory Store.
|
||||
*/
|
||||
public Mentat() {
|
||||
this.rawPointer = JNA.INSTANCE.store_open("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Mentat with the provided pointer to a Mentat Store
|
||||
* @param rawPointer A pointer to a Mentat Store.
|
||||
*/
|
||||
public Mentat(Pointer rawPointer) { this.rawPointer = rawPointer; }
|
||||
|
||||
/**
|
||||
* Simple transact of an EDN string.
|
||||
* TODO: Throw an exception if the transact fails
|
||||
* @param transaction The string, as EDN, to be transacted.
|
||||
* @return The {@link TxReport} of the completed transaction
|
||||
*/
|
||||
public TxReport transact(String transaction) {
|
||||
RustResult result = JNA.INSTANCE.store_transact(this.rawPointer, transaction);
|
||||
if (result.isFailure()) {
|
||||
Log.e("Mentat", result.err);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return new TxReport(result.ok);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the the `Entid` of the attribute
|
||||
* @param attribute The string represeting the attribute whose `Entid` we are after. The string is represented as `:namespace/name`.
|
||||
* @return The `Entid` associated with the attribute.
|
||||
*/
|
||||
public long entIdForAttribute(String attribute) {
|
||||
return JNA.INSTANCE.store_entid_for_attribute(this.rawPointer, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a query.
|
||||
* @param query The string represeting the the query to be executed.
|
||||
* @return The {@link Query} representing the query that can be executed.
|
||||
*/
|
||||
public Query query(String query) {
|
||||
return new Query(JNA.INSTANCE.store_query(this.rawPointer, query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single value of an attribute for an Entity
|
||||
* TODO: Throw an exception if there is no the result contains an error.
|
||||
* @param attribute The string the attribute whose value is to be returned. The string is represented as `:namespace/name`.
|
||||
* @param entid The `Entid` of the entity we want the value from.
|
||||
* @return The {@link TypedValue} containing the value of the attribute for the entity.
|
||||
*/
|
||||
public TypedValue valueForAttributeOfEntity(String attribute, long entid) {
|
||||
RustResult result = JNA.INSTANCE.store_value_for_attribute(this.rawPointer, entid, attribute);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return new TypedValue(result.ok);
|
||||
}
|
||||
|
||||
if (result.isFailure()) {
|
||||
Log.e("Mentat", result.err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an callback and a set of attributes to observer for transaction observation.
|
||||
* The callback function is called when a transaction occurs in the `Store` that this `Mentat`
|
||||
* is connected to that affects the attributes that an observer has registered for.
|
||||
* @param key `String` representing an identifier for the observer.
|
||||
* @param attributes An array of Strings representing the attributes that the observer wishes
|
||||
* to be notified about if they are referenced in a transaction.
|
||||
* @param callback the function to call when an observer notice is fired.
|
||||
*/
|
||||
public void registerObserver(String key, String[] attributes, TxObserverCallback callback) {
|
||||
// turn string array into int array
|
||||
long[] attrEntids = new long[attributes.length];
|
||||
for(int i = 0; i < attributes.length; i++) {
|
||||
attrEntids[i] = JNA.INSTANCE.store_entid_for_attribute(this.rawPointer, attributes[i]);
|
||||
}
|
||||
final Pointer entidsNativeArray = new Memory(8 * attrEntids.length);
|
||||
entidsNativeArray.write(0, attrEntids, 0, attrEntids.length);
|
||||
JNA.INSTANCE.store_register_observer(rawPointer, key, entidsNativeArray, attrEntids.length, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the observer that was registered with the provided key such that it will no longer be called
|
||||
* if a transaction occurs that affects the attributes that the observer was registered to observe.
|
||||
* <p/>
|
||||
* The observer will need to re-register if it wants to start observing again.
|
||||
* @param key String representing an identifier for the observer.
|
||||
*/
|
||||
public void unregisterObserver(String key) {
|
||||
JNA.INSTANCE.store_unregister_observer(rawPointer, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.store_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
/* -*- 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.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class allows you to construct a query, bind values to variables and run those queries against a mentat DB.
|
||||
* <p/>
|
||||
* This class cannot be created directly, but must be created through `Mentat.query(String:)`.
|
||||
* <p/>
|
||||
* The types of values you can bind are:
|
||||
* <ul>
|
||||
* <li>{@link TypedValue}</li>
|
||||
* <li>long</li>
|
||||
* <li>Entid (as long)</li>
|
||||
* <li>Keyword (as String)</li>
|
||||
* <li>boolean</li>
|
||||
* <li>double</li>
|
||||
* <li>{@link Date}</li>
|
||||
* <li>{@link String}</li>
|
||||
* <li>{@link UUID}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <p/>
|
||||
* Each bound variable must have a corresponding value in the query string used to create this query.
|
||||
* <p/>
|
||||
* <pre>{@code
|
||||
* String query = "[:find ?name ?cat\n" +
|
||||
* " :in ?type\n" +
|
||||
* " :where\n" +
|
||||
* " [?c :community/name ?name]\n" +
|
||||
* " [?c :community/type ?type]\n" +
|
||||
* " [?c :community/category ?cat]]";
|
||||
* mentat.query(query).bindKeywordReference("?type", ":community.type/website").run(new RelResultHandler() {
|
||||
* @Override
|
||||
* public void handleRows(RelResult rows) {
|
||||
* ...
|
||||
* }
|
||||
* });
|
||||
*}</pre>
|
||||
* <p/>
|
||||
* Queries can be run and the results returned in a number of different formats. Individual result values are returned as `TypedValues` and
|
||||
* the format differences relate to the number and structure of those values. The result format is related to the format provided in the query string.
|
||||
* <p/>
|
||||
* - `Rel` - This is the default `run` function and returns a list of rows of values. Queries that wish to have `Rel` results should format their query strings:
|
||||
*
|
||||
* <pre>{@code
|
||||
* String query = "[: find ?a ?b ?c\n" +
|
||||
* " : where ... ]";
|
||||
* mentat.query(query).run(new RelResultHandler() {
|
||||
* @Override
|
||||
* public void handleRows(RelResult rows) {
|
||||
* ...
|
||||
* }
|
||||
* });
|
||||
*}</pre>
|
||||
* <p/>
|
||||
* - `Scalar` - This returns a single value as a result. This can be optional, as the value may not be present. Queries that wish to have `Scalar` results should format their query strings:
|
||||
*
|
||||
* <pre>{@code
|
||||
* String query = "[: find ?a .\n" +
|
||||
* " : where ... ]";
|
||||
* mentat.query(query).runScalar(new ScalarResultHandler() {
|
||||
* @Override
|
||||
* public void handleValue(TypedValue value) {
|
||||
* ...
|
||||
* }
|
||||
* });
|
||||
*}</pre>
|
||||
* <p/>
|
||||
* - `Coll` - This returns a list of single values as a result. Queries that wish to have `Coll` results should format their query strings:
|
||||
* <pre>{@code
|
||||
* String query = "[: find [?a ...]\n" +
|
||||
* " : where ... ]";
|
||||
* mentat.query(query).runColl(new ScalarResultHandler() {
|
||||
* @Override
|
||||
* public void handleList(CollResult list) {
|
||||
* ...
|
||||
* }
|
||||
* });
|
||||
*}</pre>
|
||||
* <p/>
|
||||
* - `Tuple` - This returns a single row of values. Queries that wish to have `Tuple` results should format their query strings:
|
||||
* <pre>{@code
|
||||
* String query = "[: find [?a ?b ?c]\n" +
|
||||
* " : where ... ]";
|
||||
* mentat.query(query).runTuple(new TupleResultHandler() {
|
||||
* @Override
|
||||
* public void handleRow(TupleResult row) {
|
||||
* ...
|
||||
* }
|
||||
* });
|
||||
*}</pre>
|
||||
*/
|
||||
public class Query extends RustObject {
|
||||
|
||||
public Query(Pointer pointer) {
|
||||
this.rawPointer = pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a long value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindLong(String varName, long value) {
|
||||
this.validate();
|
||||
JNA.INSTANCE.query_builder_bind_long(this.rawPointer, varName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a Entid value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindEntidReference(String varName, long value) {
|
||||
this.validate();
|
||||
JNA.INSTANCE.query_builder_bind_ref(this.rawPointer, varName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a String keyword value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindKeywordReference(String varName, String value) {
|
||||
this.validate();
|
||||
JNA.INSTANCE.query_builder_bind_ref_kw(this.rawPointer, varName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a keyword value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindKeyword(String varName, String value) {
|
||||
this.validate();
|
||||
JNA.INSTANCE.query_builder_bind_kw(this.rawPointer, varName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a boolean value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindBoolean(String varName, boolean value) {
|
||||
this.validate();
|
||||
JNA.INSTANCE.query_builder_bind_boolean(this.rawPointer, varName, value ? 1 : 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a double value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindDouble(String varName, double value) {
|
||||
this.validate();
|
||||
JNA.INSTANCE.query_builder_bind_double(this.rawPointer, varName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a {@link Date} value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindDate(String varName, Date value) {
|
||||
this.validate();
|
||||
long timestamp = value.getTime() * 1000;
|
||||
JNA.INSTANCE.query_builder_bind_timestamp(this.rawPointer, varName, timestamp);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a {@link String} value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindString(String varName, String value) {
|
||||
this.validate();
|
||||
JNA.INSTANCE.query_builder_bind_string(this.rawPointer, varName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a {@link UUID} value to the provided variable name.
|
||||
* TODO: Throw an exception if the query raw pointer has been consumed.
|
||||
* @param varName The name of the variable in the format `?name`.
|
||||
* @param value The value to be bound
|
||||
* @return This {@link Query} such that further function can be called.
|
||||
*/
|
||||
Query bindUUID(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);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query with the values bound associated with this {@link Query} and call the provided
|
||||
* callback function with the results as a list of rows of {@link TypedValue}s.
|
||||
* 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 run(final RelResultHandler handler) {
|
||||
this.validate();
|
||||
RustResult result = JNA.INSTANCE.query_builder_execute(rawPointer);
|
||||
rawPointer = null;
|
||||
|
||||
if (result.isFailure()) {
|
||||
Log.e("Query", result.err);
|
||||
return;
|
||||
}
|
||||
handler.handleRows(new RelResult(result.ok));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query with the values bound associated with this {@link Query} and call the provided
|
||||
* callback function with the results with the result as a single {@link TypedValue}.
|
||||
* 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) {
|
||||
this.validate();
|
||||
RustResult result = JNA.INSTANCE.query_builder_execute_scalar(rawPointer);
|
||||
rawPointer = null;
|
||||
|
||||
if (result.isFailure()) {
|
||||
Log.e("Query", result.err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.isSuccess()) {
|
||||
handler.handleValue(new TypedValue(result.ok));
|
||||
} else {
|
||||
handler.handleValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query with the values bound associated with this {@link Query} and call the provided
|
||||
* callback function with the results with the result as a list of single {@link TypedValue}s.
|
||||
* 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) {
|
||||
this.validate();
|
||||
RustResult result = JNA.INSTANCE.query_builder_execute_coll(rawPointer);
|
||||
rawPointer = null;
|
||||
|
||||
if (result.isFailure()) {
|
||||
Log.e("Query", result.err);
|
||||
return;
|
||||
}
|
||||
handler.handleList(new CollResult(result.ok));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query with the values bound associated with this {@link Query} and call the provided
|
||||
* callback function with the results with the result as a list of single {@link TypedValue}s.
|
||||
* 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) {
|
||||
this.validate();
|
||||
RustResult result = JNA.INSTANCE.query_builder_execute_tuple(rawPointer);
|
||||
rawPointer = null;
|
||||
|
||||
if (result.isFailure()) {
|
||||
Log.e("Query", result.err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.isSuccess()) {
|
||||
handler.handleRow(new TupleResult(result.ok));
|
||||
} else {
|
||||
handler.handleRow(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer == null) {
|
||||
return;
|
||||
}
|
||||
JNA.INSTANCE.query_builder_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
/**
|
||||
* Wraps a `Rel` result from a Mentat query.
|
||||
* A `Rel` result is a list of rows of `TypedValues`.
|
||||
* Individual rows can be fetched or the set can be iterated.
|
||||
* </p>
|
||||
* To fetch individual rows from a `RelResult` use `row(Int32)`.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* mentat.query(query).run(new RelResultHandler() {
|
||||
* @Override
|
||||
* public void handleRows(RelResult rows) {
|
||||
* TupleResult row1 = rows.rowAtIndex(0);
|
||||
* TupleResult row2 = rows.rowAtIndex(1);
|
||||
* ...
|
||||
* }
|
||||
* });
|
||||
*}</pre>
|
||||
* </p>
|
||||
* To iterate over the result set use standard iteration flows.
|
||||
* <pre>{@code
|
||||
* mentat.query(query).run(new RelResultHandler() {
|
||||
* @Override
|
||||
* public void handleRows(RelResult rows) {
|
||||
* for (TupleResult row: rows) {
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*}</pre>
|
||||
* </p>
|
||||
* Note that iteration is consuming and can only be done once.
|
||||
*/
|
||||
public class RelResult extends RustObject implements Iterable<TupleResult> {
|
||||
|
||||
public RelResult(Pointer pointer) {
|
||||
this.rawPointer = pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the row at the requested index.
|
||||
* TODO: Throw an exception if the result set has already been iterated.
|
||||
* @param index the index of the row to be fetched
|
||||
* @return The row at the requested index as a `TupleResult`, if present, or nil if there is no row at that index.
|
||||
*/
|
||||
public TupleResult rowAtIndex(int index) {
|
||||
this.validate();
|
||||
Pointer pointer = JNA.INSTANCE.row_at_index(this.rawPointer, index);
|
||||
if (pointer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TupleResult(pointer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelResultIterator iterator() {
|
||||
this.validate();
|
||||
Pointer iterPointer = JNA.INSTANCE.typed_value_result_set_into_iter(this.rawPointer);
|
||||
this.rawPointer = null;
|
||||
if (iterPointer == null) {
|
||||
return null;
|
||||
}
|
||||
return new RelResultIterator(iterPointer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.typed_value_result_set_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- 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;
|
||||
|
||||
/**
|
||||
* Interface defining the structure of a callback from a query returning a {@link RelResult}.
|
||||
*/
|
||||
public interface RelResultHandler {
|
||||
void handleRows(RelResult rows);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
import java.util.Iterator;
|
||||
/**
|
||||
* Iterator for a {@link RelResult}
|
||||
*/
|
||||
public class RelResultIterator extends RustObject implements Iterator {
|
||||
|
||||
Pointer nextPointer;
|
||||
|
||||
RelResultIterator(Pointer iterator) {
|
||||
this.rawPointer = iterator;
|
||||
}
|
||||
|
||||
private Pointer getNextPointer() {
|
||||
return JNA.INSTANCE.typed_value_result_set_iter_next(this.rawPointer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
this.nextPointer = getNextPointer();
|
||||
return this.nextPointer != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TupleResult next() {
|
||||
Pointer next = this.nextPointer == null ? getNextPointer() : this.nextPointer;
|
||||
if (next == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TupleResult(next);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.typed_value_result_set_iter_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* Base class that wraps an non-optional {@link Pointer} representing a pointer to a Rust object.
|
||||
* This class implements {@link Closeable} but does not provide an implementation, forcing all
|
||||
* subclasses to implement it. This ensures that all classes that inherit from RustObject
|
||||
* will have their {@link Pointer} destroyed when the Java wrapper is destroyed.
|
||||
*/
|
||||
abstract class RustObject implements Closeable {
|
||||
Pointer rawPointer;
|
||||
|
||||
/**
|
||||
* Throws a {@link NullPointerException} if the underlying {@link Pointer} is null.
|
||||
*/
|
||||
void validate() {
|
||||
if (this.rawPointer == null) {
|
||||
throw new NullPointerException(this.getClass() + " consumed");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* -*- 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 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;
|
||||
|
||||
/**
|
||||
* Represents a C struct containing a {@link Pointer}s and String that map to a Rust Result.
|
||||
* A RustResult will contain either an ok value, OR an err value, or neither - never both.
|
||||
*/
|
||||
public class RustResult extends Structure implements Closeable {
|
||||
public static class ByReference extends RustResult implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static class ByValue extends RustResult implements Structure.ByValue {
|
||||
}
|
||||
|
||||
public Pointer ok;
|
||||
public String err;
|
||||
|
||||
/**
|
||||
* Is there an value attached to this result
|
||||
* @return true if a value is present, false otherwise
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return this.ok != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there an error attached to this result?
|
||||
* @return true is an error is present, false otherwise
|
||||
*/
|
||||
public boolean isFailure() {
|
||||
return this.err != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList("ok", "err");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.getPointer() != null) {
|
||||
JNA.INSTANCE.destroy(this.getPointer());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- 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;
|
||||
|
||||
/**
|
||||
* Interface defining the structure of a callback from a query returning a single {@link TypedValue}.
|
||||
*/
|
||||
public interface ScalarResultHandler {
|
||||
void handleValue(TypedValue value);
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Wraps a `Tuple` result from a Mentat query.
|
||||
* A `Tuple` result is a single row {@link TypedValue}s.
|
||||
* Values for individual fields can be fetched as {@link TypedValue} or converted into a requested type.
|
||||
* <p>
|
||||
* Field values can be fetched as one of the following types:
|
||||
* <ul>
|
||||
* <li>{@link TypedValue}</li>
|
||||
* <li>long</li>
|
||||
* <li>Entid (as long)</li>
|
||||
* <li>Keyword (as String)</li>
|
||||
* <li>boolean</li>
|
||||
* <li>double</li>
|
||||
* <li>{@link Date}</li>
|
||||
* <li>{@link String}</li>
|
||||
* <li>{@link UUID}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* To iterate over the result set use standard iteration flows.
|
||||
*/
|
||||
public class TupleResult extends RustObject {
|
||||
|
||||
public TupleResult(Pointer pointer) {
|
||||
this.rawPointer = pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link TypedValue} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The {@link TypedValue} at that index.
|
||||
*/
|
||||
public TypedValue get(Integer index) {
|
||||
this.validate();
|
||||
Pointer pointer = JNA.INSTANCE.value_at_index(this.rawPointer, index);
|
||||
if (pointer == null) {
|
||||
return null;
|
||||
}
|
||||
return new TypedValue(pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Long} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `Long` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The {@link Long} at that index.
|
||||
*/
|
||||
public Long asLong(Integer index) {
|
||||
this.validate();
|
||||
return JNA.INSTANCE.value_at_index_into_long(this.rawPointer, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Entid at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `Ref` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The Entid at that index.
|
||||
*/
|
||||
public Long asEntid(Integer index) {
|
||||
this.validate();
|
||||
return JNA.INSTANCE.value_at_index_into_entid(this.rawPointer, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the keyword {@link String} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `Keyword` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The keyword at that index.
|
||||
*/
|
||||
public String asKeyword(Integer index) {
|
||||
this.validate();
|
||||
return JNA.INSTANCE.value_at_index_into_kw(this.rawPointer, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Boolean} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `Boolean` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The {@link Boolean} at that index.
|
||||
*/
|
||||
public Boolean asBool(Integer index) {
|
||||
this.validate();
|
||||
return JNA.INSTANCE.value_at_index_into_boolean(this.rawPointer, index) == 0 ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Double} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `Double` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The {@link Double} at that index.
|
||||
*/
|
||||
public Double asDouble(Integer index) {
|
||||
this.validate();
|
||||
return JNA.INSTANCE.value_at_index_into_double(this.rawPointer, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Date} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `Instant` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The {@link Date} at that index.
|
||||
*/
|
||||
public Date asDate(Integer index) {
|
||||
this.validate();
|
||||
return new Date(JNA.INSTANCE.value_at_index_into_timestamp(this.rawPointer, index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link String} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `String` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The {@link String} at that index.
|
||||
*/
|
||||
public String asString(Integer index) {
|
||||
this.validate();
|
||||
return JNA.INSTANCE.value_at_index_into_string(this.rawPointer, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link UUID} at the specified index.
|
||||
* If the index is greater than the number of values then this function will crash.
|
||||
* If the value type if the {@link TypedValue} at this index is not `Uuid` then this function will crash.
|
||||
* @param index The index of the value to fetch.
|
||||
* @return The {@link UUID} at that index.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.typed_value_list_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- 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;
|
||||
|
||||
/**
|
||||
* Interface defining the structure of a callback from a query returning a {@link TupleResult}.
|
||||
*/
|
||||
public interface TupleResultHandler {
|
||||
void handleRow(TupleResult row);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a C struct representing changes that occured during a transaction.
|
||||
* These changes contain the transaction identifier, a {@link Pointer} to a list of affected attribute
|
||||
* Entids and the number of items that the list contains.
|
||||
*/
|
||||
public class TxChange extends Structure implements Closeable {
|
||||
public static class ByReference extends TxChange implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static class ByValue extends TxChange implements Structure.ByValue {
|
||||
}
|
||||
|
||||
public int txid;
|
||||
public Pointer changes;
|
||||
public int numberOfItems;
|
||||
// Used by the Swift counterpart, JNA does this for us automagically.
|
||||
// But we still need it here so that the number of fields and their order is correct
|
||||
public int changes_len;
|
||||
|
||||
/**
|
||||
* Get the affected attributes for this transaction
|
||||
* @return The changes as a list of Entids of affected attributes
|
||||
*/
|
||||
public List<Long> getChanges() {
|
||||
final long[] array = (long[]) changes.getLongArray(0, numberOfItems);
|
||||
Long[] longArray = new Long[numberOfItems];
|
||||
int idx = 0;
|
||||
for (long change: array) {
|
||||
longArray[idx++] = change;
|
||||
}
|
||||
return Arrays.asList(longArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList("txid", "changes", "changes_len", "numberOfItems");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.getPointer() != null) {
|
||||
JNA.INSTANCE.destroy(this.getPointer());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 com.sun.jna.Structure;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a C struct containing a list of {@link TxChange}s that occured.
|
||||
*/
|
||||
public class TxChangeList extends Structure implements Closeable {
|
||||
public static class ByReference extends TxChangeList implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static class ByValue extends TxChangeList implements Structure.ByValue {
|
||||
}
|
||||
|
||||
public TxChange.ByReference reports;
|
||||
public int numberOfItems;
|
||||
// Used by the Swift counterpart, JNA does this for us automagically.
|
||||
// // But we still need it here so that the number of fields and their order is correct
|
||||
public int len;
|
||||
|
||||
/**
|
||||
* Get the changes that occured
|
||||
* @return a list of {@link TxChange}s for the notification
|
||||
*/
|
||||
public List<TxChange> getReports() {
|
||||
final TxChange[] array = (TxChange[]) reports.toArray(numberOfItems);
|
||||
return Arrays.asList(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList("reports", "numberOfItems", "len");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
final TxChange[] nativeReports = (TxChange[]) reports.toArray(numberOfItems);
|
||||
for (TxChange nativeReport : nativeReports) {
|
||||
nativeReport.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* -*- 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 com.sun.jna.Callback;
|
||||
|
||||
/**
|
||||
* Protocol to be implemented by any object that wishes to register for transaction observation
|
||||
*/
|
||||
public interface TxObserverCallback extends Callback {
|
||||
void transactionObserverCalled(String key, TxChangeList.ByReference reports);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This class wraps a raw pointer than points to a Rust `TxReport` object.
|
||||
* </p>
|
||||
* The `TxReport` contains information about a successful Mentat transaction.
|
||||
* </p>
|
||||
* This information includes:
|
||||
* <ul>
|
||||
* <li>`txId` - the identifier for the transaction.</li>
|
||||
* <li>`txInstant` - the time that the transaction occured.</li>
|
||||
* <li>a map of temporary identifiers provided in the transaction and the `Entid`s that they were mapped to.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
* Access an `Entid` for a temporary identifier that was provided in the transaction can be done through `entid(String:)`.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* TxReport report = mentat.transact("[[:db/add "a" :foo/boolean true]]");
|
||||
* long aEntid = report.getEntidForTempId("a");
|
||||
*}</pre>
|
||||
*/
|
||||
public class TxReport extends RustObject {
|
||||
|
||||
private Long txId;
|
||||
private Date txInstant;
|
||||
|
||||
|
||||
public TxReport(Pointer pointer) {
|
||||
this.rawPointer = pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier for the transaction.
|
||||
* @return The identifier for the transaction.
|
||||
*/
|
||||
public Long getTxId() {
|
||||
if (this.txId == null) {
|
||||
this.txId = JNA.INSTANCE.tx_report_get_entid(this.rawPointer);
|
||||
}
|
||||
|
||||
return this.txId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time that the transaction occured.
|
||||
* @return The time that the transaction occured.
|
||||
*/
|
||||
public Date getTxInstant() {
|
||||
if (this.txInstant == null) {
|
||||
this.txInstant = new Date(JNA.INSTANCE.tx_report_get_tx_instant(this.rawPointer));
|
||||
}
|
||||
return this.txInstant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access an `Entid` for a temporary identifier that was provided in the transaction.
|
||||
* @param tempId A {@link String} representing the temporary identifier to fetch the `Entid` for.
|
||||
* @return The `Entid` for the temporary identifier, if present, otherwise `null`.
|
||||
*/
|
||||
public Long getEntidForTempId(String tempId) {
|
||||
Pointer longPointer = JNA.INSTANCE.tx_report_entity_for_temp_id(this.rawPointer, tempId);
|
||||
if (longPointer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return longPointer.getLong(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.tx_report_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/* -*- 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 com.sun.jna.Pointer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A wrapper around Mentat's `TypedValue` Rust object. This class wraps a raw pointer to a Rust `TypedValue`
|
||||
* struct and provides accessors to the values according to expected result type.
|
||||
* </p>
|
||||
* As the FFI functions for fetching values are consuming, this class keeps a copy of the result internally after
|
||||
* fetching so that the value can be referenced several times.
|
||||
* </p>
|
||||
* Also, due to the consuming nature of the FFI layer, this class also manages it's raw pointer, nilling it after calling the
|
||||
* FFI conversion function so that the underlying base class can manage cleanup.
|
||||
*/
|
||||
public class TypedValue extends RustObject {
|
||||
|
||||
private Object value;
|
||||
|
||||
private boolean isConsumed() {
|
||||
return this.rawPointer == null;
|
||||
}
|
||||
|
||||
public TypedValue(Pointer pointer) {
|
||||
this.rawPointer = pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a {@link Long}. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `Long`
|
||||
* @return the value of this {@link TypedValue} as a {@link Long}
|
||||
*/
|
||||
public Long asLong() {
|
||||
if (!this.isConsumed()) {
|
||||
this.value = JNA.INSTANCE.typed_value_into_long(this.rawPointer);
|
||||
this.rawPointer = null;
|
||||
}
|
||||
return (Long)value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a Entid. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `Ref`
|
||||
* @return the value of this {@link TypedValue} as a Entid
|
||||
*/
|
||||
public Long asEntid() {
|
||||
if (!this.isConsumed()) {
|
||||
this.value = JNA.INSTANCE.typed_value_into_entid(this.rawPointer);
|
||||
this.rawPointer = null;
|
||||
}
|
||||
return (Long)value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a keyword {@link String}. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `Keyword`
|
||||
* @return the value of this {@link TypedValue} as a Keyword
|
||||
*/
|
||||
public String asKeyword() {
|
||||
if (!this.isConsumed()) {
|
||||
this.value = JNA.INSTANCE.typed_value_into_kw(this.rawPointer);
|
||||
this.rawPointer = null;
|
||||
}
|
||||
return (String)value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a {@link Boolean}. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `Boolean`
|
||||
* @return the value of this {@link TypedValue} as a {@link Boolean}
|
||||
*/
|
||||
public Boolean asBoolean() {
|
||||
if (!this.isConsumed()) {
|
||||
long value = JNA.INSTANCE.typed_value_into_boolean(this.rawPointer);
|
||||
this.value = value == 0 ? false : true;
|
||||
this.rawPointer = null;
|
||||
}
|
||||
return (Boolean) this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a {@link Double}. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `Double`
|
||||
* @return the value of this {@link TypedValue} as a {@link Double}
|
||||
*/
|
||||
public Double asDouble() {
|
||||
if (!this.isConsumed()) {
|
||||
this.value = JNA.INSTANCE.typed_value_into_double(this.rawPointer);
|
||||
this.rawPointer = null;
|
||||
}
|
||||
return (Double)value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a {@link Date}. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `Instant`
|
||||
* @return the value of this {@link TypedValue} as a {@link Date}
|
||||
*/
|
||||
public Date asDate() {
|
||||
if (!this.isConsumed()) {
|
||||
this.value = new Date(JNA.INSTANCE.typed_value_into_timestamp(this.rawPointer) * 1_000);
|
||||
this.rawPointer = null;
|
||||
}
|
||||
return (Date)this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a {@link String}. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `String`
|
||||
* @return the value of this {@link TypedValue} as a {@link String}
|
||||
*/
|
||||
public String asString() {
|
||||
if (!this.isConsumed()) {
|
||||
this.value = JNA.INSTANCE.typed_value_into_string(this.rawPointer);
|
||||
this.rawPointer = null;
|
||||
}
|
||||
return (String)value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value as a {@link UUID}. This function will panic if the `ValueType` of this
|
||||
* {@link TypedValue} is not a `Uuid`
|
||||
* @return the value of this {@link TypedValue} as a {@link UUID}
|
||||
*/
|
||||
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.rawPointer = null;
|
||||
}
|
||||
return (UUID)this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.rawPointer != null) {
|
||||
JNA.INSTANCE.typed_value_destroy(this.rawPointer);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so
Executable file
BIN
sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so
Executable file
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Mentat</string>
|
||||
</resources>
|
1
sdks/android/Mentat/settings.gradle
Normal file
1
sdks/android/Mentat/settings.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
include ':library'
|
615
sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj
Normal file
615
sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj
Normal file
|
@ -0,0 +1,615 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */; };
|
||||
7BAE75A22089020E00895D37 /* libmentat_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */; };
|
||||
7BAE75A42089022B00895D37 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BAE75A32089022B00895D37 /* libsqlite3.tbd */; };
|
||||
7BDB96942077C299009D0651 /* Mentat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BDB968A2077C299009D0651 /* Mentat.framework */; };
|
||||
7BDB96992077C299009D0651 /* MentatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96982077C299009D0651 /* MentatTests.swift */; };
|
||||
7BDB969B2077C299009D0651 /* Mentat.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BDB968D2077C299009D0651 /* Mentat.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
7BDB96AF2077C38E009D0651 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96A62077C38D009D0651 /* Query.swift */; };
|
||||
7BDB96B02077C38E009D0651 /* Mentat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96A72077C38D009D0651 /* Mentat.swift */; };
|
||||
7BDB96B12077C38E009D0651 /* store.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BDB96A82077C38E009D0651 /* store.h */; };
|
||||
7BDB96B22077C38E009D0651 /* RelResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96A92077C38E009D0651 /* RelResult.swift */; };
|
||||
7BDB96B32077C38E009D0651 /* RustObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AA2077C38E009D0651 /* RustObject.swift */; };
|
||||
7BDB96B42077C38E009D0651 /* OptionalRustObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */; };
|
||||
7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AC2077C38E009D0651 /* TupleResult.swift */; };
|
||||
7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AE2077C38E009D0651 /* TypedValue.swift */; };
|
||||
7BDB96C22077CD98009D0651 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BDB96C12077CD98009D0651 /* libresolv.tbd */; };
|
||||
7BDB96C62077D347009D0651 /* Date+Int64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96C52077D346009D0651 /* Date+Int64.swift */; };
|
||||
7BDB96C9207B735A009D0651 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 7BDB96C8207B735A009D0651 /* fixtures */; };
|
||||
7BDB96CC207B7684009D0651 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96CB207B7684009D0651 /* Errors.swift */; };
|
||||
7BEB7D2C207D03DA000369AD /* TxReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEB7D2B207D03DA000369AD /* TxReport.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
7BDB96952077C299009D0651 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 7BDB96812077C299009D0651 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 7BDB96892077C299009D0651;
|
||||
remoteInfo = Mentat;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Result+Unwrap.swift"; path = "Mentat/Extensions/Result+Unwrap.swift"; sourceTree = SOURCE_ROOT; };
|
||||
7B911E1A2085081D000998CB /* libtoodle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtoodle.a; path = "../../../../sync-storage-prototype/rust/target/universal/release/libtoodle.a"; sourceTree = "<group>"; };
|
||||
7BAE75A32089022B00895D37 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
|
||||
7BDB968A2077C299009D0651 /* Mentat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Mentat.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7BDB968D2077C299009D0651 /* Mentat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Mentat.h; sourceTree = "<group>"; };
|
||||
7BDB968E2077C299009D0651 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7BDB96932077C299009D0651 /* MentatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MentatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7BDB96982077C299009D0651 /* MentatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentatTests.swift; sourceTree = "<group>"; };
|
||||
7BDB969A2077C299009D0651 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7BDB96A62077C38D009D0651 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = "<group>"; };
|
||||
7BDB96A72077C38D009D0651 /* Mentat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mentat.swift; sourceTree = "<group>"; };
|
||||
7BDB96A82077C38E009D0651 /* store.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = store.h; sourceTree = "<group>"; };
|
||||
7BDB96A92077C38E009D0651 /* RelResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelResult.swift; sourceTree = "<group>"; };
|
||||
7BDB96AA2077C38E009D0651 /* RustObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RustObject.swift; sourceTree = "<group>"; };
|
||||
7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalRustObject.swift; sourceTree = "<group>"; };
|
||||
7BDB96AC2077C38E009D0651 /* TupleResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TupleResult.swift; sourceTree = "<group>"; };
|
||||
7BDB96AE2077C38E009D0651 /* TypedValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypedValue.swift; sourceTree = "<group>"; };
|
||||
7BDB96BF2077CD7A009D0651 /* libmentat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmentat.a; path = ../../../target/universal/release/libmentat.a; sourceTree = "<group>"; };
|
||||
7BDB96C12077CD98009D0651 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
7BDB96C32077D090009D0651 /* module.map */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.map; sourceTree = "<group>"; };
|
||||
7BDB96C52077D346009D0651 /* Date+Int64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Int64.swift"; sourceTree = "<group>"; };
|
||||
7BDB96C8207B735A009D0651 /* fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = fixtures; path = ../../../../fixtures; sourceTree = "<group>"; };
|
||||
7BDB96CB207B7684009D0651 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
|
||||
7BEB7D21207BDDEF000369AD /* libtoodle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtoodle.a; path = ../../../target/universal/release/libtoodle.a; sourceTree = "<group>"; };
|
||||
7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmentat_ffi.a; path = ../../../target/universal/release/libmentat_ffi.a; sourceTree = "<group>"; };
|
||||
7BEB7D2B207D03DA000369AD /* TxReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxReport.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
7BDB96862077C299009D0651 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7BAE75A42089022B00895D37 /* libsqlite3.tbd in Frameworks */,
|
||||
7BDB96C22077CD98009D0651 /* libresolv.tbd in Frameworks */,
|
||||
7BAE75A22089020E00895D37 /* libmentat_ffi.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
7BDB96902077C299009D0651 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7BDB96942077C299009D0651 /* Mentat.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
7BDB96802077C299009D0651 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB968C2077C299009D0651 /* Mentat */,
|
||||
7BDB96972077C299009D0651 /* MentatTests */,
|
||||
7BDB968B2077C299009D0651 /* Products */,
|
||||
7BDB96BE2077CD7A009D0651 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB968B2077C299009D0651 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB968A2077C299009D0651 /* Mentat.framework */,
|
||||
7BDB96932077C299009D0651 /* MentatTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB968C2077C299009D0651 /* Mentat */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB96CA207B7672009D0651 /* Errors */,
|
||||
7BDB96C42077D346009D0651 /* Extensions */,
|
||||
7BDB96BA2077C42B009D0651 /* Core */,
|
||||
7BDB96A42077C301009D0651 /* Query */,
|
||||
7BDB96B92077C403009D0651 /* Rust */,
|
||||
7BDB96A82077C38E009D0651 /* store.h */,
|
||||
7BDB96A72077C38D009D0651 /* Mentat.swift */,
|
||||
7BEB7D26207BE5BB000369AD /* Transact */,
|
||||
7BDB968D2077C299009D0651 /* Mentat.h */,
|
||||
7BDB968E2077C299009D0651 /* Info.plist */,
|
||||
7BDB96C32077D090009D0651 /* module.map */,
|
||||
);
|
||||
path = Mentat;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB96972077C299009D0651 /* MentatTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB96C8207B735A009D0651 /* fixtures */,
|
||||
7BDB96982077C299009D0651 /* MentatTests.swift */,
|
||||
7BDB969A2077C299009D0651 /* Info.plist */,
|
||||
);
|
||||
path = MentatTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB96A42077C301009D0651 /* Query */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB96A62077C38D009D0651 /* Query.swift */,
|
||||
7BDB96A92077C38E009D0651 /* RelResult.swift */,
|
||||
7BDB96AC2077C38E009D0651 /* TupleResult.swift */,
|
||||
);
|
||||
path = Query;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB96B92077C403009D0651 /* Rust */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */,
|
||||
7BDB96AA2077C38E009D0651 /* RustObject.swift */,
|
||||
);
|
||||
path = Rust;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB96BA2077C42B009D0651 /* Core */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB96AE2077C38E009D0651 /* TypedValue.swift */,
|
||||
);
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB96BE2077CD7A009D0651 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BAE75A32089022B00895D37 /* libsqlite3.tbd */,
|
||||
7B911E1A2085081D000998CB /* libtoodle.a */,
|
||||
7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */,
|
||||
7BEB7D21207BDDEF000369AD /* libtoodle.a */,
|
||||
7BDB96C12077CD98009D0651 /* libresolv.tbd */,
|
||||
7BDB96BF2077CD7A009D0651 /* libmentat.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB96C42077D346009D0651 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */,
|
||||
7BDB96C52077D346009D0651 /* Date+Int64.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BDB96CA207B7672009D0651 /* Errors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BDB96CB207B7684009D0651 /* Errors.swift */,
|
||||
);
|
||||
path = Errors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7BEB7D26207BE5BB000369AD /* Transact */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7BEB7D2B207D03DA000369AD /* TxReport.swift */,
|
||||
);
|
||||
path = Transact;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
7BDB96872077C299009D0651 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7BDB96B12077C38E009D0651 /* store.h in Headers */,
|
||||
7BDB969B2077C299009D0651 /* Mentat.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
7BDB96892077C299009D0651 /* Mentat */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 7BDB969E2077C299009D0651 /* Build configuration list for PBXNativeTarget "Mentat" */;
|
||||
buildPhases = (
|
||||
7BDB96852077C299009D0651 /* Sources */,
|
||||
7BDB96862077C299009D0651 /* Frameworks */,
|
||||
7BDB96872077C299009D0651 /* Headers */,
|
||||
7BDB96882077C299009D0651 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Mentat;
|
||||
productName = Mentat;
|
||||
productReference = 7BDB968A2077C299009D0651 /* Mentat.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
7BDB96922077C299009D0651 /* MentatTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 7BDB96A12077C299009D0651 /* Build configuration list for PBXNativeTarget "MentatTests" */;
|
||||
buildPhases = (
|
||||
7BDB968F2077C299009D0651 /* Sources */,
|
||||
7BDB96902077C299009D0651 /* Frameworks */,
|
||||
7BDB96912077C299009D0651 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
7BDB96962077C299009D0651 /* PBXTargetDependency */,
|
||||
);
|
||||
name = MentatTests;
|
||||
productName = MentatTests;
|
||||
productReference = 7BDB96932077C299009D0651 /* MentatTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
7BDB96812077C299009D0651 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0930;
|
||||
LastUpgradeCheck = 0930;
|
||||
ORGANIZATIONNAME = Mozilla;
|
||||
TargetAttributes = {
|
||||
7BDB96892077C299009D0651 = {
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
LastSwiftMigration = 0930;
|
||||
};
|
||||
7BDB96922077C299009D0651 = {
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 7BDB96842077C299009D0651 /* Build configuration list for PBXProject "Mentat" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 7BDB96802077C299009D0651;
|
||||
productRefGroup = 7BDB968B2077C299009D0651 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
7BDB96892077C299009D0651 /* Mentat */,
|
||||
7BDB96922077C299009D0651 /* MentatTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
7BDB96882077C299009D0651 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
7BDB96912077C299009D0651 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7BDB96C9207B735A009D0651 /* fixtures in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
7BDB96852077C299009D0651 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7BDB96B32077C38E009D0651 /* RustObject.swift in Sources */,
|
||||
7BDB96C62077D347009D0651 /* Date+Int64.swift in Sources */,
|
||||
7BEB7D2C207D03DA000369AD /* TxReport.swift in Sources */,
|
||||
7BDB96B42077C38E009D0651 /* OptionalRustObject.swift in Sources */,
|
||||
7BDB96B22077C38E009D0651 /* RelResult.swift in Sources */,
|
||||
7BDB96AF2077C38E009D0651 /* Query.swift in Sources */,
|
||||
7BDB96CC207B7684009D0651 /* Errors.swift in Sources */,
|
||||
7BDB96B02077C38E009D0651 /* Mentat.swift in Sources */,
|
||||
7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */,
|
||||
7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */,
|
||||
7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
7BDB968F2077C299009D0651 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7BDB96992077C299009D0651 /* MentatTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
7BDB96962077C299009D0651 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 7BDB96892077C299009D0651 /* Mentat */;
|
||||
targetProxy = 7BDB96952077C299009D0651 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
7BDB969C2077C299009D0651 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "-fembed-bitcode";
|
||||
OTHER_LDFLAGS = "";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7BDB969D2077C299009D0651 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CFLAGS = "-fembed-bitcode";
|
||||
OTHER_LDFLAGS = "";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
7BDB969F2077C299009D0651 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 8BHJ767F4Y;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Mentat/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../../target/universal/release";
|
||||
OTHER_CFLAGS = "-fembed-bitcode";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.Mentat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7BDB96A02077C299009D0651 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 8BHJ767F4Y;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_TESTABILITY = NO;
|
||||
INFOPLIST_FILE = Mentat/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../../target/universal/release";
|
||||
OTHER_CFLAGS = "-fembed-bitcode";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.Mentat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
7BDB96A22077C299009D0651 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 8BHJ767F4Y;
|
||||
INFOPLIST_FILE = MentatTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_CFLAGS = "";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.MentatTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7BDB96A32077C299009D0651 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 8BHJ767F4Y;
|
||||
INFOPLIST_FILE = MentatTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_CFLAGS = "";
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.MentatTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
7BDB96842077C299009D0651 /* Build configuration list for PBXProject "Mentat" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7BDB969C2077C299009D0651 /* Debug */,
|
||||
7BDB969D2077C299009D0651 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
7BDB969E2077C299009D0651 /* Build configuration list for PBXNativeTarget "Mentat" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7BDB969F2077C299009D0651 /* Debug */,
|
||||
7BDB96A02077C299009D0651 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
7BDB96A12077C299009D0651 /* Build configuration list for PBXNativeTarget "MentatTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7BDB96A22077C299009D0651 /* Debug */,
|
||||
7BDB96A32077C299009D0651 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 7BDB96812077C299009D0651 /* Project object */;
|
||||
}
|
7
sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:Mentat.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
|
@ -0,0 +1,107 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96922077C299009D0651"
|
||||
BuildableName = "MentatTests.xctest"
|
||||
BlueprintName = "MentatTests"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "MentatTests/testBindBoolean()">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "MentatTests/testBindDate()">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96922077C299009D0651"
|
||||
BuildableName = "MentatTests.xctest"
|
||||
BlueprintName = "MentatTests"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "7BDB96892077C299009D0651"
|
||||
BuildableName = "Mentat.framework"
|
||||
BlueprintName = "Mentat"
|
||||
ReferencedContainer = "container:Mentat.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
181
sdks/swift/Mentat/Mentat/Core/TypedValue.swift
Normal file
181
sdks/swift/Mentat/Mentat/Core/TypedValue.swift
Normal file
|
@ -0,0 +1,181 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
import MentatStore
|
||||
|
||||
/**
|
||||
A wrapper around Mentat's `TypedValue` Rust object. This class wraps a raw pointer to a Rust `TypedValue`
|
||||
struct and provides accessors to the values according to expected result type.
|
||||
|
||||
As the FFI functions for fetching values are consuming, this class keeps a copy of the result internally after
|
||||
fetching so that the value can be referenced several times.
|
||||
|
||||
Also, due to the consuming nature of the FFI layer, this class also manages it's raw pointer, nilling it after calling the
|
||||
FFI conversion function so that the underlying base class can manage cleanup.
|
||||
*/
|
||||
class TypedValue: OptionalRustObject {
|
||||
|
||||
private var value: Any?
|
||||
|
||||
/**
|
||||
The `ValueType` for this `TypedValue`.
|
||||
- Returns: The `ValueType` for this `TypedValue`.
|
||||
*/
|
||||
var valueType: ValueType {
|
||||
return typed_value_value_type(self.raw!)
|
||||
}
|
||||
|
||||
private func isConsumed() -> Bool {
|
||||
return self.raw == nil
|
||||
}
|
||||
|
||||
/**
|
||||
This value as a `Int64`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `Long`
|
||||
|
||||
- Returns: the value of this `TypedValue` as a `Int64`
|
||||
*/
|
||||
func asLong() -> Int64 {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
if !self.isConsumed() {
|
||||
self.value = typed_value_into_long(self.raw!)
|
||||
}
|
||||
return self.value as! Int64
|
||||
}
|
||||
|
||||
/**
|
||||
This value as an `Entid`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `Ref`
|
||||
|
||||
- Returns: the value of this `TypedValue` as an `Entid`
|
||||
*/
|
||||
func asEntid() -> Entid {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
|
||||
if !self.isConsumed() {
|
||||
self.value = typed_value_into_entid(self.raw!)
|
||||
}
|
||||
return self.value as! Entid
|
||||
}
|
||||
|
||||
/**
|
||||
This value as a keyword `String`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `Keyword`
|
||||
|
||||
- Returns: the value of this `TypedValue` as a keyword `String`
|
||||
*/
|
||||
func asKeyword() -> String {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
|
||||
if !self.isConsumed() {
|
||||
self.value = String(cString: typed_value_into_kw(self.raw!))
|
||||
}
|
||||
return self.value as! String
|
||||
}
|
||||
|
||||
/**
|
||||
This value as a `Bool`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `Boolean`
|
||||
|
||||
- Returns: the value of this `TypedValue` as a `Bool`
|
||||
*/
|
||||
func asBool() -> Bool {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
|
||||
if !self.isConsumed() {
|
||||
let v = typed_value_into_boolean(self.raw!)
|
||||
self.value = v > 0
|
||||
}
|
||||
return self.value as! Bool
|
||||
}
|
||||
|
||||
/**
|
||||
This value as a `Double`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `Double`
|
||||
|
||||
- Returns: the value of this `TypedValue` as a `Double`
|
||||
*/
|
||||
func asDouble() -> Double {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
|
||||
if !self.isConsumed() {
|
||||
self.value = typed_value_into_double(self.raw!)
|
||||
}
|
||||
return self.value as! Double
|
||||
}
|
||||
|
||||
/**
|
||||
This value as a `Date`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `Instant`
|
||||
|
||||
- Returns: the value of this `TypedValue` as a `Date`
|
||||
*/
|
||||
func asDate() -> Date {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
|
||||
if !self.isConsumed() {
|
||||
let timestamp = typed_value_into_timestamp(self.raw!)
|
||||
self.value = Date(timeIntervalSince1970: TimeInterval(timestamp))
|
||||
}
|
||||
return self.value as! Date
|
||||
}
|
||||
|
||||
/**
|
||||
This value as a `String`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `String`
|
||||
|
||||
- Returns: the value of this `TypedValue` as a `String`
|
||||
*/
|
||||
func asString() -> String {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
|
||||
if !self.isConsumed() {
|
||||
self.value = String(cString: typed_value_into_string(self.raw!))
|
||||
}
|
||||
return self.value as! String
|
||||
}
|
||||
|
||||
/**
|
||||
This value as a `UUID`. This function will panic if the `ValueType` of this `TypedValue`
|
||||
is not a `Uuid`
|
||||
|
||||
- Returns: the value of this `TypedValue` as a `UUID?`. If the `UUID` is not valid then this function returns nil.
|
||||
*/
|
||||
func asUUID() -> UUID? {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
|
||||
if !self.isConsumed() {
|
||||
let bytes = typed_value_into_uuid(self.raw!).pointee
|
||||
self.value = UUID(uuid: bytes)
|
||||
}
|
||||
return self.value as! UUID?
|
||||
}
|
||||
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
typed_value_destroy(pointer)
|
||||
}
|
||||
}
|
30
sdks/swift/Mentat/Mentat/Errors/Errors.swift
Normal file
30
sdks/swift/Mentat/Mentat/Errors/Errors.swift
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum QueryError: Error {
|
||||
case invalidKeyword(message: String)
|
||||
case executionFailed(message: String)
|
||||
}
|
||||
|
||||
public struct MentatError: Error {
|
||||
let message: String
|
||||
}
|
||||
|
||||
public enum PointerError: Error {
|
||||
case pointerConsumed
|
||||
}
|
||||
|
||||
public enum ResultError: Error {
|
||||
case error(message: String)
|
||||
case empty
|
||||
}
|
22
sdks/swift/Mentat/Mentat/Extensions/Date+Int64.swift
Normal file
22
sdks/swift/Mentat/Mentat/Extensions/Date+Int64.swift
Normal file
|
@ -0,0 +1,22 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Date {
|
||||
/**
|
||||
This `Date` as microseconds.
|
||||
|
||||
- Returns: The `timeIntervalSince1970` in microseconds
|
||||
*/
|
||||
func toMicroseconds() -> Int64 {
|
||||
return Int64(self.timeIntervalSince1970 * 1_000_000)
|
||||
}
|
||||
}
|
50
sdks/swift/Mentat/Mentat/Extensions/Result+Unwrap.swift
Normal file
50
sdks/swift/Mentat/Mentat/Extensions/Result+Unwrap.swift
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
import MentatStore
|
||||
|
||||
extension Result {
|
||||
/**
|
||||
Force unwraps a result.
|
||||
Expects there to be a value attached and throws an error is there is not.
|
||||
|
||||
- Throws: `ResultError.error` if the result contains an error
|
||||
- Throws: `ResultError.empty` if the result contains no error but also no result.
|
||||
|
||||
- Returns: The pointer to the successful result value.
|
||||
*/
|
||||
@discardableResult public func unwrap() throws -> UnsafeMutableRawPointer {
|
||||
guard let success = self.ok else {
|
||||
if let error = self.err {
|
||||
throw ResultError.error(message: String(cString: error))
|
||||
}
|
||||
throw ResultError.empty
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
/**
|
||||
Unwraps an optional result, yielding either a successful value or a nil.
|
||||
|
||||
- Throws: `ResultError.error` if the result contains an error
|
||||
|
||||
- Returns: The pointer to the successful result value, or nil if no value is present.
|
||||
*/
|
||||
@discardableResult public func tryUnwrap() throws -> UnsafeMutableRawPointer? {
|
||||
guard let success = self.ok else {
|
||||
if let error = self.err {
|
||||
throw ResultError.error(message: String(cString: error))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return success
|
||||
}
|
||||
}
|
24
sdks/swift/Mentat/Mentat/Info.plist
Normal file
24
sdks/swift/Mentat/Mentat/Info.plist
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
18
sdks/swift/Mentat/Mentat/Mentat.h
Normal file
18
sdks/swift/Mentat/Mentat/Mentat.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* 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. */
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for Mentat.
|
||||
FOUNDATION_EXPORT double MentatVersionNumber;
|
||||
|
||||
//! Project version string for Mentat.
|
||||
FOUNDATION_EXPORT const unsigned char MentatVersionString[];
|
||||
|
170
sdks/swift/Mentat/Mentat/Mentat.swift
Normal file
170
sdks/swift/Mentat/Mentat/Mentat.swift
Normal file
|
@ -0,0 +1,170 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
|
||||
import MentatStore
|
||||
|
||||
typealias Entid = Int64
|
||||
|
||||
/**
|
||||
Protocol to be implemented by any object that wishes to register for transaction observation
|
||||
*/
|
||||
protocol Observing {
|
||||
func transactionDidOccur(key: String, reports: [TxChange])
|
||||
}
|
||||
|
||||
/**
|
||||
Protocol to be implemented by any object that provides an interface to Mentat's transaction observers.
|
||||
*/
|
||||
protocol Observable {
|
||||
func register(key: String, observer: Observing, attributes: [String])
|
||||
func unregister(key: String)
|
||||
}
|
||||
|
||||
/**
|
||||
The primary class for accessing Mentat's API.
|
||||
This class provides all of the basic API that can be found in Mentat's Store struct.
|
||||
The raw pointer it holds is a pointer to a Store.
|
||||
*/
|
||||
class Mentat: RustObject {
|
||||
fileprivate static var observers = [String: Observing]()
|
||||
|
||||
/**
|
||||
Create a new Mentat with the provided pointer to a Mentat Store
|
||||
- Parameter raw: A pointer to a Mentat Store.
|
||||
*/
|
||||
required override init(raw: OpaquePointer) {
|
||||
super.init(raw: raw)
|
||||
}
|
||||
|
||||
/**
|
||||
Open a connection to a Store in a given location.
|
||||
If the store does not already exist, one will be created.
|
||||
|
||||
- Parameter storeURI: The URI as a String of the store to open.
|
||||
If no store URI is provided, an in-memory store will be opened.
|
||||
*/
|
||||
convenience init(storeURI: String = "") {
|
||||
self.init(raw: store_open(storeURI))
|
||||
}
|
||||
|
||||
/**
|
||||
Simple transact of an EDN string.
|
||||
- Parameter transaction: The string, as EDN, to be transacted
|
||||
|
||||
- Throws: `MentatError` if the an error occured during the transaction, or the TxReport is nil.
|
||||
|
||||
- Returns: The `TxReport` of the completed transaction
|
||||
*/
|
||||
func transact(transaction: String) throws -> TxReport {
|
||||
let result = store_transact(self.raw, transaction).pointee
|
||||
return TxReport(raw: try result.unwrap())
|
||||
}
|
||||
|
||||
/**
|
||||
Get the the `Entid` of the attribute.
|
||||
- Parameter attribute: The string represeting the attribute whose `Entid` we are after.
|
||||
The string is represented as `:namespace/name`.
|
||||
|
||||
- Returns: The `Entid` associated with the attribute.
|
||||
*/
|
||||
func entidForAttribute(attribute: String) -> Entid {
|
||||
return Entid(store_entid_for_attribute(self.raw, attribute))
|
||||
}
|
||||
|
||||
/**
|
||||
Start a query.
|
||||
- Parameter query: The string represeting the the query to be executed.
|
||||
|
||||
- Returns: The `Query` representing the query that can be executed.
|
||||
*/
|
||||
func query(query: String) -> Query {
|
||||
return Query(raw: store_query(self.raw, query))
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieve a single value of an attribute for an Entity
|
||||
- Parameter attribute: The string the attribute whose value is to be returned.
|
||||
The string is represented as `:namespace/name`.
|
||||
- Parameter entid: The `Entid` of the entity we want the value from.
|
||||
|
||||
- Returns: The `TypedValue` containing the value of the attribute for the entity.
|
||||
*/
|
||||
func value(forAttribute attribute: String, ofEntity entid: Entid) throws -> TypedValue? {
|
||||
let result = store_value_for_attribute(self.raw, entid, attribute).pointee
|
||||
return TypedValue(raw: try result.unwrap())
|
||||
}
|
||||
|
||||
// Destroys the pointer by passing it back into Rust to be cleaned up
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
store_destroy(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Set up `Mentat` to provide an interface to Mentat's transaction observation
|
||||
*/
|
||||
extension Mentat: Observable {
|
||||
/**
|
||||
Register an `Observing` and a set of attributes to observer for transaction observation.
|
||||
The `transactionDidOccur(String: [TxChange]:)` function is called when a transaction
|
||||
occurs in the `Store` that this `Mentat` is connected to that affects the attributes that an
|
||||
`Observing` has registered for.
|
||||
|
||||
- Parameter key: `String` representing an identifier for the `Observing`.
|
||||
- Parameter observer: The `Observing` to be notified when a transaction occurs.
|
||||
- Parameter attributes: An `Array` of `Strings` representing the attributes that the `Observing`
|
||||
wishes to be notified about if they are referenced in a transaction.
|
||||
*/
|
||||
func register(key: String, observer: Observing, attributes: [String]) {
|
||||
let attrEntIds = attributes.map({ (kw) -> Entid in
|
||||
let entid = Entid(self.entidForAttribute(attribute: kw));
|
||||
return entid
|
||||
})
|
||||
|
||||
let ptr = UnsafeMutablePointer<Entid>.allocate(capacity: attrEntIds.count)
|
||||
let entidPointer = UnsafeMutableBufferPointer(start: ptr, count: attrEntIds.count)
|
||||
var _ = entidPointer.initialize(from: attrEntIds)
|
||||
|
||||
guard let firstElement = entidPointer.baseAddress else {
|
||||
return
|
||||
}
|
||||
Mentat.observers[key] = observer
|
||||
store_register_observer(self.raw, key, firstElement, Entid(attributes.count), transactionObserverCallback)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Unregister the `Observing` that was registered with the provided key such that it will no longer be called
|
||||
if a transaction occurs that affects the attributes that `Observing` was registered to observe.
|
||||
|
||||
The `Observing` will need to re-register if it wants to start observing again.
|
||||
|
||||
- Parameter key: `String` representing an identifier for the `Observing`.
|
||||
*/
|
||||
func unregister(key: String) {
|
||||
Mentat.observers.removeValue(forKey: key)
|
||||
store_unregister_observer(self.raw, key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
This function needs to be static as callbacks passed into Rust from Swift cannot contain state. Therefore the observers are static, as is
|
||||
the function that we pass into Rust to receive the callback.
|
||||
*/
|
||||
private func transactionObserverCallback(key: UnsafePointer<CChar>, reports: UnsafePointer<TxChangeList>) {
|
||||
let key = String(cString: key)
|
||||
guard let observer = Mentat.observers[key] else { return }
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
observer.transactionDidOccur(key: key, reports: [TxChange]())
|
||||
}
|
||||
}
|
337
sdks/swift/Mentat/Mentat/Query/Query.swift
Normal file
337
sdks/swift/Mentat/Mentat/Query/Query.swift
Normal file
|
@ -0,0 +1,337 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
import MentatStore
|
||||
|
||||
|
||||
/**
|
||||
This class allows you to construct a query, bind values to variables and run those queries against a mentat DB.
|
||||
|
||||
This class cannot be created directly, but must be created through `Mentat.query(String:)`.
|
||||
|
||||
The types of values you can bind are
|
||||
- `Int64`
|
||||
- `Entid`
|
||||
- `Keyword`
|
||||
- `Bool`
|
||||
- `Double`
|
||||
- `Date`
|
||||
- `String`
|
||||
- `UUID`.
|
||||
|
||||
Each bound variable must have a corresponding value in the query string used to create this query.
|
||||
|
||||
```
|
||||
let query = """
|
||||
[:find ?name ?cat
|
||||
:in ?type
|
||||
:where
|
||||
[?c :community/name ?name]
|
||||
[?c :community/type ?type]
|
||||
[?c :community/category ?cat]]
|
||||
"""
|
||||
mentat.query(query: query)
|
||||
.bind(varName: "?type", toKeyword: ":community.type/website")
|
||||
.run { result in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Queries can be run and the results returned in a number of different formats. Individual result values are returned as `TypedValues` and
|
||||
the format differences relate to the number and structure of those values. The result format is related to the format provided in the query string.
|
||||
|
||||
- `Rel` - This is the default `run` function and returns a list of rows of values. Queries that wish to have `Rel` results should format their query strings:
|
||||
```
|
||||
let query = """
|
||||
[: find ?a ?b ?c
|
||||
: where ... ]
|
||||
"""
|
||||
mentat.query(query: query)
|
||||
.run { result in
|
||||
...
|
||||
}
|
||||
```
|
||||
- `Scalar` - This returns a single value as a result. This can be optional, as the value may not be present. Queries that wish to have `Scalar` results should format their query strings:
|
||||
```
|
||||
let query = """
|
||||
[: find ?a .
|
||||
: where ... ]
|
||||
"""
|
||||
mentat.query(query: query)
|
||||
.runScalar { result in
|
||||
...
|
||||
}
|
||||
```
|
||||
- `Coll` - This returns a list of single values as a result. Queries that wish to have `Coll` results should format their query strings:
|
||||
```
|
||||
let query = """
|
||||
[: find [?a ...]
|
||||
: where ... ]
|
||||
"""
|
||||
mentat.query(query: query)
|
||||
.runColl { result in
|
||||
...
|
||||
}
|
||||
```
|
||||
- `Tuple` - This returns a single row of values. Queries that wish to have `Tuple` results should format their query strings:
|
||||
```
|
||||
let query = """
|
||||
[: find [?a ?b ?c]
|
||||
: where ... ]
|
||||
"""
|
||||
mentat.query(query: query)
|
||||
.runTuple { result in
|
||||
...
|
||||
}
|
||||
```
|
||||
*/
|
||||
class Query: OptionalRustObject {
|
||||
|
||||
/**
|
||||
Binds a `Int64` value to the provided variable name.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toLong value: Int64) throws -> Query {
|
||||
query_builder_bind_long(try! self.validPointer(), varName, value)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a `Entid` value to the provided variable name.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toReference value: Entid) throws -> Query {
|
||||
query_builder_bind_ref(try! self.validPointer(), varName, value)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a `String` value representing a keyword for an attribute to the provided variable name.
|
||||
Keywords take the format `:namespace/name`.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toReference value: String) throws -> Query {
|
||||
query_builder_bind_ref_kw(try! self.validPointer(), varName, value)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a keyword `String` value to the provided variable name.
|
||||
Keywords take the format `:namespace/name`.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toKeyword value: String) throws -> Query {
|
||||
query_builder_bind_kw(try! self.validPointer(), varName, value)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a `Bool` value to the provided variable name.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toBoolean value: Bool) throws -> Query {
|
||||
query_builder_bind_boolean(try! self.validPointer(), varName, value ? 1 : 0)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a `Double` value to the provided variable name.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toDouble value: Double) throws -> Query {
|
||||
query_builder_bind_double(try! self.validPointer(), varName, value)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a `Date` value to the provided variable name.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toDate value: Date) throws -> Query {
|
||||
query_builder_bind_timestamp(try! self.validPointer(), varName, value.toMicroseconds())
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a `String` value to the provided variable name.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toString value: String) throws -> Query {
|
||||
query_builder_bind_string(try! self.validPointer(), varName, value)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Binds a `UUID` value to the provided variable name.
|
||||
|
||||
- Parameter varName: The name of the variable in the format `?name`.
|
||||
- Parameter value: The value to be bound
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
|
||||
|
||||
- Returns: This `Query` such that further function can be called.
|
||||
*/
|
||||
func bind(varName: String, toUuid value: UUID) throws -> Query {
|
||||
var rawUuid = value.uuid
|
||||
withUnsafePointer(to: &rawUuid) { uuidPtr in
|
||||
query_builder_bind_uuid(try! self.validPointer(), varName, uuidPtr)
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Execute the query with the values bound associated with this `Query` and call the provided callback function with the results as a list of rows of `TypedValues`.
|
||||
|
||||
- Parameter callback: the function to call with the results of this query
|
||||
|
||||
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, or that
|
||||
variable we incorrectly bound, or that the query provided was not `Rel`.
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
|
||||
*/
|
||||
func run(callback: @escaping (RelResult?) -> Void) throws {
|
||||
let result = query_builder_execute(try! self.validPointer())
|
||||
self.raw = nil
|
||||
|
||||
if let err = result.pointee.err {
|
||||
let message = String(cString: err)
|
||||
throw QueryError.executionFailed(message: message)
|
||||
}
|
||||
guard let results = result.pointee.ok else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
callback(RelResult(raw: results))
|
||||
}
|
||||
|
||||
/**
|
||||
Execute the query with the values bound associated with this `Query` and call the provided callback function with the result as a single `TypedValue`.
|
||||
|
||||
- Parameter callback: the function to call with the results of this query
|
||||
|
||||
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, that
|
||||
variable we incorrectly bound, or that the query provided was not `Scalar`.
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
|
||||
*/
|
||||
func runScalar(callback: @escaping (TypedValue?) -> Void) throws {
|
||||
let result = query_builder_execute_scalar(try! self.validPointer())
|
||||
self.raw = nil
|
||||
|
||||
if let err = result.pointee.err {
|
||||
let message = String(cString: err)
|
||||
throw QueryError.executionFailed(message: message)
|
||||
}
|
||||
guard let results = result.pointee.ok else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
callback(TypedValue(raw: OpaquePointer(results)))
|
||||
}
|
||||
|
||||
/**
|
||||
Execute the query with the values bound associated with this `Query` and call the provided callback function with the result as a list of single `TypedValues`.
|
||||
|
||||
- Parameter callback: the function to call with the results of this query
|
||||
|
||||
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, that
|
||||
variable we incorrectly bound, or that the query provided was not `Coll`.
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
|
||||
*/
|
||||
func runColl(callback: @escaping (ColResult?) -> Void) throws {
|
||||
let result = query_builder_execute_coll(try! self.validPointer())
|
||||
self.raw = nil
|
||||
|
||||
if let err = result.pointee.err {
|
||||
let message = String(cString: err)
|
||||
throw QueryError.executionFailed(message: message)
|
||||
}
|
||||
guard let results = result.pointee.ok else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
callback(ColResult(raw: results))
|
||||
}
|
||||
|
||||
/**
|
||||
Execute the query with the values bound associated with this `Query` and call the provided callback function with the result as a list of single `TypedValues`.
|
||||
|
||||
- Parameter callback: the function to call with the results of this query
|
||||
|
||||
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, that
|
||||
variable we incorrectly bound, or that the query provided was not `Tuple`.
|
||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
|
||||
*/
|
||||
func runTuple(callback: @escaping (TupleResult?) -> Void) throws {
|
||||
let result = query_builder_execute_tuple(try! self.validPointer())
|
||||
self.raw = nil
|
||||
|
||||
if let err = result.pointee.err {
|
||||
let message = String(cString: err)
|
||||
throw QueryError.executionFailed(message: message)
|
||||
}
|
||||
guard let results = result.pointee.ok else {
|
||||
callback(nil)
|
||||
return
|
||||
}
|
||||
callback(TupleResult(raw: OpaquePointer(results)))
|
||||
}
|
||||
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
query_builder_destroy(pointer)
|
||||
}
|
||||
}
|
106
sdks/swift/Mentat/Mentat/Query/RelResult.swift
Normal file
106
sdks/swift/Mentat/Mentat/Query/RelResult.swift
Normal file
|
@ -0,0 +1,106 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
import MentatStore
|
||||
|
||||
/**
|
||||
Wraps a `Rel` result from a Mentat query.
|
||||
A `Rel` result is a list of rows of `TypedValues`.
|
||||
Individual rows can be fetched or the set can be iterated.
|
||||
|
||||
To fetch individual rows from a `RelResult` use `row(Int32)`.
|
||||
|
||||
```
|
||||
query.run { rows in
|
||||
let row1 = rows.row(0)
|
||||
let row2 = rows.row(1)
|
||||
}
|
||||
```
|
||||
|
||||
To iterate over the result set use standard iteration flows.
|
||||
```
|
||||
query.run { rows in
|
||||
rows.forEach { row in
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that iteration is consuming and can only be done once.
|
||||
*/
|
||||
class RelResult: OptionalRustObject {
|
||||
|
||||
/**
|
||||
Fetch the row at the requested index.
|
||||
|
||||
- Parameter index: the index of the row to be fetched
|
||||
|
||||
- Throws: `PointerError.pointerConsumed` if the result set has already been iterated.
|
||||
|
||||
- Returns: The row at the requested index as a `TupleResult`, if present, or nil if there is no row at that index.
|
||||
*/
|
||||
func row(index: Int32) throws -> TupleResult? {
|
||||
guard let row = row_at_index(try self.validPointer(), index) else {
|
||||
return nil
|
||||
}
|
||||
return TupleResult(raw: row)
|
||||
}
|
||||
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
destroy(UnsafeMutableRawPointer(pointer))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Iterator for `RelResult`.
|
||||
|
||||
To iterate over the result set use standard iteration flows.
|
||||
```
|
||||
query.run { result in
|
||||
rows.forEach { row in
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that iteration is consuming and can only be done once.
|
||||
*/
|
||||
class RelResultIterator: OptionalRustObject, IteratorProtocol {
|
||||
typealias Element = TupleResult
|
||||
|
||||
init(iter: OpaquePointer?) {
|
||||
super.init(raw: iter)
|
||||
}
|
||||
|
||||
func next() -> Element? {
|
||||
guard let iter = self.raw,
|
||||
let rowPtr = typed_value_result_set_iter_next(iter) else {
|
||||
return nil
|
||||
}
|
||||
return TupleResult(raw: rowPtr)
|
||||
}
|
||||
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
typed_value_result_set_iter_destroy(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
extension RelResult: Sequence {
|
||||
func makeIterator() -> RelResultIterator {
|
||||
do {
|
||||
let rowIter = typed_value_result_set_into_iter(try self.validPointer())
|
||||
self.raw = nil
|
||||
return RelResultIterator(iter: rowIter)
|
||||
} catch {
|
||||
return RelResultIterator(iter: nil)
|
||||
}
|
||||
}
|
||||
}
|
217
sdks/swift/Mentat/Mentat/Query/TupleResult.swift
Normal file
217
sdks/swift/Mentat/Mentat/Query/TupleResult.swift
Normal file
|
@ -0,0 +1,217 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
import MentatStore
|
||||
|
||||
/**
|
||||
Wraps a `Tuple` result from a Mentat query.
|
||||
A `Tuple` result is a list of `TypedValues`.
|
||||
Individual values can be fetched as `TypedValues` or converted into a requested type.
|
||||
|
||||
Values can be fetched as one of the following types:
|
||||
- `TypedValue`
|
||||
- `Int64`
|
||||
- `Entid`
|
||||
- `Keyword`
|
||||
- `Bool`
|
||||
- `Double`
|
||||
- `Date`
|
||||
- `String`
|
||||
- `UUID`.
|
||||
*/
|
||||
class TupleResult: OptionalRustObject {
|
||||
|
||||
/**
|
||||
Return the `TypedValue` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `TypedValue` at that index.
|
||||
*/
|
||||
func get(index: Int) -> TypedValue {
|
||||
return TypedValue(raw: value_at_index(self.raw!, Int32(index)))
|
||||
}
|
||||
|
||||
/**
|
||||
Return the `Int64` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `Long` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `Int64` at that index.
|
||||
*/
|
||||
func asLong(index: Int) -> Int64 {
|
||||
return value_at_index_into_long(self.raw!, Int32(index))
|
||||
}
|
||||
|
||||
/**
|
||||
Return the `Entid` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `Ref` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `Entid` at that index.
|
||||
*/
|
||||
func asEntid(index: Int) -> Entid {
|
||||
return value_at_index_into_entid(self.raw!, Int32(index))
|
||||
}
|
||||
|
||||
/**
|
||||
Return the keyword `String` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `Keyword` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The keyword `String` at that index.
|
||||
*/
|
||||
func asKeyword(index: Int) -> String {
|
||||
return String(cString: value_at_index_into_kw(self.raw!, Int32(index)))
|
||||
}
|
||||
|
||||
/**
|
||||
Return the `Bool` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `Boolean` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `Bool` at that index.
|
||||
*/
|
||||
func asBool(index: Int) -> Bool {
|
||||
return value_at_index_into_boolean(self.raw!, Int32(index)) == 0 ? false : true
|
||||
}
|
||||
|
||||
/**
|
||||
Return the `Double` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `Double` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `Double` at that index.
|
||||
*/
|
||||
func asDouble(index: Int) -> Double {
|
||||
return value_at_index_into_double(self.raw!, Int32(index))
|
||||
}
|
||||
|
||||
/**
|
||||
Return the `Date` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `Instant` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `Date` at that index.
|
||||
*/
|
||||
func asDate(index: Int) -> Date {
|
||||
return Date(timeIntervalSince1970: TimeInterval(value_at_index_into_timestamp(self.raw!, Int32(index))))
|
||||
}
|
||||
|
||||
/**
|
||||
Return the `String` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `String` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `String` at that index.
|
||||
*/
|
||||
func asString(index: Int) -> String {
|
||||
return String(cString: value_at_index_into_string(self.raw!, Int32(index)))
|
||||
}
|
||||
|
||||
/**
|
||||
Return the `UUID` at the specified index.
|
||||
If the index is greater than the number of values then this function will crash.
|
||||
If the value type if the `TypedValue` at this index is not `Uuid` then this function will crash.
|
||||
|
||||
- Parameter index: The index of the value to fetch.
|
||||
|
||||
- Returns: The `UUID` at that index.
|
||||
*/
|
||||
func asUUID(index: Int) -> UUID? {
|
||||
return UUID(uuid: value_at_index_into_uuid(self.raw!, Int32(index)).pointee)
|
||||
}
|
||||
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
typed_value_list_destroy(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Wraps a `Coll` result from a Mentat query.
|
||||
A `Coll` result is a list of rows of single values of type `TypedValue`.
|
||||
Values for individual rows can be fetched as `TypedValue` or converted into a requested type.
|
||||
|
||||
Row values can be fetched as one of the following types:
|
||||
- `TypedValue`
|
||||
- `Int64`
|
||||
- `Entid`
|
||||
- `Keyword`
|
||||
- `Bool`
|
||||
- `Double`
|
||||
- `Date`
|
||||
- `String`
|
||||
- `UUID`.
|
||||
*/
|
||||
class ColResult: TupleResult {
|
||||
}
|
||||
|
||||
/**
|
||||
Iterator for `ColResult`.
|
||||
|
||||
To iterate over the result set use standard iteration flows.
|
||||
```
|
||||
query.runColl { rows in
|
||||
rows.forEach { value in
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that iteration is consuming and can only be done once.
|
||||
*/
|
||||
class ColResultIterator: OptionalRustObject, IteratorProtocol {
|
||||
typealias Element = TypedValue
|
||||
|
||||
init(iter: OpaquePointer?) {
|
||||
super.init(raw: iter)
|
||||
}
|
||||
|
||||
func next() -> Element? {
|
||||
guard let iter = self.raw,
|
||||
let rowPtr = typed_value_list_iter_next(iter) else {
|
||||
return nil
|
||||
}
|
||||
return TypedValue(raw: rowPtr)
|
||||
}
|
||||
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
typed_value_list_iter_destroy(pointer)
|
||||
}
|
||||
}
|
||||
|
||||
extension ColResult: Sequence {
|
||||
func makeIterator() -> ColResultIterator {
|
||||
defer {
|
||||
self.raw = nil
|
||||
}
|
||||
guard let raw = self.raw else {
|
||||
return ColResultIterator(iter: nil)
|
||||
}
|
||||
let rowIter = typed_value_list_into_iter(raw)
|
||||
return ColResultIterator(iter: rowIter)
|
||||
}
|
||||
}
|
68
sdks/swift/Mentat/Mentat/Rust/OptionalRustObject.swift
Normal file
68
sdks/swift/Mentat/Mentat/Rust/OptionalRustObject.swift
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
import MentatStore
|
||||
|
||||
/**
|
||||
Base class that wraps an optional `OpaquePointer` representing a pointer to a Rust object.
|
||||
This class should be used to wrap Rust pointer that point to consuming structs, that is, calling a function
|
||||
for that Rust pointer, will cause Rust to destroy the pointer, leaving the Swift pointer dangling.
|
||||
These classes are responsible for ensuring that their raw `OpaquePointer` are `nil`led after calling a consuming
|
||||
FFI function.
|
||||
This class provides cleanup functions on deinit, ensuring that all classes
|
||||
that inherit from it will have their `OpaquePointer` destroyed when the Swift wrapper is destroyed.
|
||||
If a class does not override `cleanup` then a `fatalError` is thrown.
|
||||
The optional pointer is managed here such that is the pointer is nil, then the cleanup function is not called
|
||||
ensuring that we do not double free the pointer on exit.
|
||||
*/
|
||||
class OptionalRustObject: Destroyable {
|
||||
var raw: OpaquePointer?
|
||||
lazy var uniqueId: ObjectIdentifier = {
|
||||
ObjectIdentifier(self)
|
||||
}()
|
||||
|
||||
init(raw: UnsafeMutableRawPointer) {
|
||||
self.raw = OpaquePointer(raw)
|
||||
}
|
||||
|
||||
init(raw: OpaquePointer?) {
|
||||
self.raw = raw
|
||||
}
|
||||
|
||||
func intoRaw() -> OpaquePointer? {
|
||||
return self.raw
|
||||
}
|
||||
|
||||
deinit {
|
||||
guard let raw = self.raw else { return }
|
||||
self.cleanup(pointer: raw)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a non-optional `OpaquePointer` if one exists for this class.
|
||||
|
||||
- Throws: `Pointer.pointerConsumed` if the raw pointer wrapped by this class is nil
|
||||
|
||||
- Returns: the raw `OpaquePointer` wrapped by this class.
|
||||
*/
|
||||
func validPointer() throws -> OpaquePointer {
|
||||
guard let r = self.raw else {
|
||||
throw PointerError.pointerConsumed
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func cleanup(pointer: OpaquePointer) {
|
||||
fatalError("\(cleanup) is not implemented.")
|
||||
}
|
||||
}
|
||||
|
49
sdks/swift/Mentat/Mentat/Rust/RustObject.swift
Normal file
49
sdks/swift/Mentat/Mentat/Rust/RustObject.swift
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
import MentatStore
|
||||
|
||||
protocol Destroyable {
|
||||
func cleanup(pointer: OpaquePointer)
|
||||
}
|
||||
|
||||
/**
|
||||
Base class that wraps an non-optional `OpaquePointer` representing a pointer to a Rust object.
|
||||
This class provides cleanup functions on deinit, ensuring that all classes
|
||||
that inherit from it will have their `OpaquePointer` destroyed when the Swift wrapper is destroyed.
|
||||
If a class does not override `cleanup` then a `fatalError` is thrown.
|
||||
*/
|
||||
public class RustObject: Destroyable {
|
||||
var raw: OpaquePointer
|
||||
|
||||
init(raw: OpaquePointer) {
|
||||
self.raw = raw
|
||||
}
|
||||
|
||||
init(raw: UnsafeMutableRawPointer) {
|
||||
self.raw = OpaquePointer(raw)
|
||||
}
|
||||
|
||||
init?(raw: OpaquePointer?) {
|
||||
guard let r = raw else {
|
||||
return nil
|
||||
}
|
||||
self.raw = r
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.cleanup(pointer: self.raw)
|
||||
}
|
||||
|
||||
func cleanup(pointer: OpaquePointer) {
|
||||
fatalError("\(cleanup) is not implemented.")
|
||||
}
|
||||
}
|
61
sdks/swift/Mentat/Mentat/Transact/TxReport.swift
Normal file
61
sdks/swift/Mentat/Mentat/Transact/TxReport.swift
Normal file
|
@ -0,0 +1,61 @@
|
|||
/* 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. */
|
||||
|
||||
import Foundation
|
||||
|
||||
import MentatStore
|
||||
|
||||
/**
|
||||
This class wraps a raw pointer than points to a Rust `TxReport` object.
|
||||
|
||||
The `TxReport` contains information about a successful Mentat transaction.
|
||||
|
||||
This information includes:
|
||||
- `txId` - the identifier for the transaction.
|
||||
- `txInstant` - the time that the transaction occured.
|
||||
- a map of temporary identifiers provided in the transaction and the `Entid`s that they were mapped to,
|
||||
|
||||
Access an `Entid` for a temporary identifier that was provided in the transaction can be done through `entid(String:)`.
|
||||
|
||||
```
|
||||
let report = mentat.transact("[[:db/add "a" :foo/boolean true]]")
|
||||
let aEntid = report.entid(forTempId: "a")
|
||||
```
|
||||
*/
|
||||
class TxReport: RustObject {
|
||||
|
||||
// The identifier for the transaction.
|
||||
public var txId: Entid {
|
||||
return tx_report_get_entid(self.raw)
|
||||
}
|
||||
|
||||
// The time that the transaction occured.
|
||||
public var txInstant: Date {
|
||||
return Date(timeIntervalSince1970: TimeInterval(tx_report_get_tx_instant(self.raw)))
|
||||
}
|
||||
|
||||
/**
|
||||
Access an `Entid` for a temporary identifier that was provided in the transaction.
|
||||
|
||||
- Parameter tempId: A `String` representing the temporary identifier to fetch the `Entid` for.
|
||||
|
||||
- Returns: The `Entid` for the temporary identifier, if present, otherwise `nil`.
|
||||
*/
|
||||
public func entid(forTempId tempId: String) -> Entid? {
|
||||
guard let entidPtr = tx_report_entity_for_temp_id(self.raw, tempId) else {
|
||||
return nil
|
||||
}
|
||||
return entidPtr.pointee
|
||||
}
|
||||
|
||||
override func cleanup(pointer: OpaquePointer) {
|
||||
tx_report_destroy(pointer)
|
||||
}
|
||||
}
|
4
sdks/swift/Mentat/Mentat/module.map
Normal file
4
sdks/swift/Mentat/Mentat/module.map
Normal file
|
@ -0,0 +1,4 @@
|
|||
module MentatStore [system][extern_c] {
|
||||
header "store.h"
|
||||
export *
|
||||
}
|
171
sdks/swift/Mentat/Mentat/store.h
Normal file
171
sdks/swift/Mentat/Mentat/store.h
Normal file
|
@ -0,0 +1,171 @@
|
|||
/* 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. */
|
||||
|
||||
#ifndef store_h
|
||||
#define store_h
|
||||
#include <stdint.h>
|
||||
#include <Foundation/NSObjCRuntime.h>
|
||||
|
||||
/*
|
||||
* This file contains headers for all of the structs and functions that map directly to the functions
|
||||
* defined in mentat/ffi/src/lib.rs.
|
||||
*
|
||||
* The C in this file is specifically formatted to be used with Objective C and Swift and contains
|
||||
* macros and flags that will not be recognised by other C based languages.
|
||||
*/
|
||||
|
||||
/*
|
||||
A mapping of the TxChange repr(C) Rust object.
|
||||
The memory for this is managed by Swift.
|
||||
*/
|
||||
struct TxChange {
|
||||
int64_t txid;
|
||||
int64_t*_Nonnull* _Nonnull changes;
|
||||
uint64_t len;
|
||||
};
|
||||
|
||||
/*
|
||||
A mapping of the TxChangeList repr(C) Rust object.
|
||||
The memory for this is managed by Swift.
|
||||
*/
|
||||
struct TxChangeList {
|
||||
struct TxChange*_Nonnull* _Nonnull reports;
|
||||
uint64_t len;
|
||||
};
|
||||
typedef struct TxChangeList TxChangeList;
|
||||
|
||||
/*
|
||||
A mapping of the ExternResult repr(C) Rust object.
|
||||
The memory for this is managed by Swift.
|
||||
*/
|
||||
struct Result {
|
||||
void* _Nullable ok;
|
||||
char* _Nullable err;
|
||||
};
|
||||
typedef struct Result Result;
|
||||
|
||||
/*
|
||||
A mapping of the ExternOption repr(C) Rust object.
|
||||
The memory for this is managed by Swift.
|
||||
*/
|
||||
struct Option {
|
||||
void* _Nullable value;
|
||||
};
|
||||
typedef struct Option Option;
|
||||
|
||||
/*
|
||||
A Mapping for the ValueType Rust object.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, ValueType) {
|
||||
ValueTypeRef = 1,
|
||||
ValueTypeBoolean,
|
||||
ValueTypeInstant,
|
||||
ValueTypeLong,
|
||||
ValueTypeDouble,
|
||||
ValueTypeString,
|
||||
ValueTypeKeyword,
|
||||
ValueTypeUuid
|
||||
};
|
||||
|
||||
// Opaque Structs mapping to Rust types that are passed over the FFI boundary
|
||||
struct EntityBuilder;
|
||||
struct InProgress;
|
||||
struct InProgressBuilder;
|
||||
struct Query;
|
||||
struct QueryResultRow;
|
||||
struct QueryResultRows;
|
||||
struct QueryRowsIterator;
|
||||
struct QueryRowIterator;
|
||||
struct Store;
|
||||
struct TxReport;
|
||||
struct TypedValue;
|
||||
|
||||
// Store
|
||||
struct Store*_Nonnull store_open(const char*_Nonnull uri);
|
||||
|
||||
// Destructors.
|
||||
void destroy(void* _Nullable obj);
|
||||
void query_builder_destroy(struct Query* _Nullable obj);
|
||||
void store_destroy(struct Store* _Nonnull obj);
|
||||
void tx_report_destroy(struct TxReport* _Nonnull obj);
|
||||
void typed_value_destroy(struct TypedValue* _Nullable obj);
|
||||
void typed_value_list_destroy(struct QueryResultRow* _Nullable obj);
|
||||
void typed_value_list_iter_destroy(struct QueryRowIterator* _Nullable obj);
|
||||
void typed_value_result_set_destroy(struct QueryResultRows* _Nullable obj);
|
||||
void typed_value_result_set_iter_destroy(struct QueryRowsIterator* _Nullable obj);
|
||||
|
||||
// transact
|
||||
struct Result*_Nonnull store_transact(struct Store*_Nonnull store, const char* _Nonnull transaction);
|
||||
const int64_t* _Nullable tx_report_entity_for_temp_id(const struct TxReport* _Nonnull report, const char* _Nonnull tempid);
|
||||
int64_t tx_report_get_entid(const struct TxReport* _Nonnull report);
|
||||
int64_t tx_report_get_tx_instant(const struct TxReport* _Nonnull report);
|
||||
|
||||
|
||||
// Sync
|
||||
struct Result*_Nonnull store_sync(struct Store*_Nonnull store, const char* _Nonnull user_uuid, const char* _Nonnull server_uri);
|
||||
|
||||
// Observers
|
||||
void store_register_observer(struct Store*_Nonnull store, const char* _Nonnull key, const int64_t* _Nonnull attributes, const int64_t len, void (*_Nonnull callback_fn)(const char* _Nonnull key, const struct TxChangeList* _Nonnull reports));
|
||||
void store_unregister_observer(struct Store*_Nonnull store, const char* _Nonnull key);
|
||||
int64_t store_entid_for_attribute(struct Store*_Nonnull store, const char*_Nonnull attr);
|
||||
int64_t changelist_entry_at(const struct TxChange* _Nonnull report, size_t index);
|
||||
|
||||
// Query
|
||||
struct Query*_Nonnull store_query(struct Store*_Nonnull store, const char* _Nonnull query);
|
||||
struct Result*_Nonnull store_value_for_attribute(struct Store*_Nonnull store, const int64_t entid, const char* _Nonnull attribute);
|
||||
|
||||
// Query Variable Binding
|
||||
void query_builder_bind_long(struct Query*_Nonnull query, const char* _Nonnull var, const int64_t value);
|
||||
void query_builder_bind_ref(struct Query*_Nonnull query, const char* _Nonnull var, const int64_t value);
|
||||
void query_builder_bind_ref_kw(struct Query*_Nonnull query, const char* _Nonnull var, const char* _Nonnull value);
|
||||
void query_builder_bind_kw(struct Query*_Nonnull query, const char* _Nonnull var, const char* _Nonnull value);
|
||||
void query_builder_bind_boolean(struct Query*_Nonnull query, const char* _Nonnull var, const int32_t value);
|
||||
void query_builder_bind_double(struct Query*_Nonnull query, const char* _Nonnull var, const double value);
|
||||
void query_builder_bind_timestamp(struct Query*_Nonnull query, const char* _Nonnull var, const int64_t value);
|
||||
void query_builder_bind_string(struct Query*_Nonnull query, const char* _Nonnull var, const char* _Nonnull value);
|
||||
void query_builder_bind_uuid(struct Query*_Nonnull query, const char* _Nonnull var, const uuid_t* _Nonnull value);
|
||||
|
||||
// Query execution
|
||||
struct Result*_Nonnull query_builder_execute(struct Query*_Nonnull query);
|
||||
struct Result*_Nonnull query_builder_execute_scalar(struct Query*_Nonnull query);
|
||||
struct Result*_Nonnull query_builder_execute_coll(struct Query*_Nonnull query);
|
||||
struct Result*_Nonnull query_builder_execute_tuple(struct Query*_Nonnull query);
|
||||
|
||||
// Query Result Processing
|
||||
int64_t typed_value_into_long(struct TypedValue*_Nonnull value);
|
||||
int64_t typed_value_into_entid(struct TypedValue*_Nonnull value);
|
||||
const char* _Nonnull typed_value_into_kw(struct TypedValue*_Nonnull value);
|
||||
int32_t typed_value_into_boolean(struct TypedValue*_Nonnull value);
|
||||
double typed_value_into_double(struct TypedValue*_Nonnull value);
|
||||
int64_t typed_value_into_timestamp(struct TypedValue*_Nonnull value);
|
||||
const char* _Nonnull typed_value_into_string(struct TypedValue*_Nonnull value);
|
||||
const uuid_t* _Nonnull typed_value_into_uuid(struct TypedValue*_Nonnull value);
|
||||
enum ValueType typed_value_value_type(struct TypedValue*_Nonnull value);
|
||||
|
||||
struct QueryResultRow* _Nullable row_at_index(struct QueryResultRows* _Nonnull rows, const int32_t index);
|
||||
struct QueryRowsIterator* _Nonnull typed_value_result_set_into_iter(struct QueryResultRows* _Nonnull rows);
|
||||
struct QueryResultRow* _Nullable typed_value_result_set_iter_next(struct QueryRowsIterator* _Nonnull iter);
|
||||
struct QueryRowIterator* _Nonnull typed_value_list_into_iter(struct QueryResultRow* _Nonnull row);
|
||||
struct TypedValue* _Nullable typed_value_list_iter_next(struct QueryRowIterator* _Nonnull iter);
|
||||
|
||||
struct TypedValue* _Nonnull value_at_index(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
int64_t value_at_index_into_long(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
int64_t value_at_index_into_entid(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
const char* _Nonnull value_at_index_into_kw(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
int32_t value_at_index_into_boolean(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
double value_at_index_into_double(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
int64_t value_at_index_into_timestamp(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
const char* _Nonnull value_at_index_into_string(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
const uuid_t* _Nonnull value_at_index_into_uuid(struct QueryResultRow* _Nonnull row, const int32_t index);
|
||||
|
||||
// Transaction change lists
|
||||
const struct TxChange* _Nullable tx_change_list_entry_at(const struct TxChangeList* _Nonnull list, size_t index);
|
||||
|
||||
#endif /* store_h */
|
22
sdks/swift/Mentat/MentatTests/Info.plist
Normal file
22
sdks/swift/Mentat/MentatTests/Info.plist
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
765
sdks/swift/Mentat/MentatTests/MentatTests.swift
Normal file
765
sdks/swift/Mentat/MentatTests/MentatTests.swift
Normal file
|
@ -0,0 +1,765 @@
|
|||
/* 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. */
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import Mentat
|
||||
|
||||
class MentatTests: XCTestCase {
|
||||
|
||||
var citiesSchema: String?
|
||||
var seattleData: String?
|
||||
var store: Mentat?
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// test that a store can be opened in memory
|
||||
func testOpenInMemoryStore() {
|
||||
XCTAssertNotNil(Mentat().raw)
|
||||
}
|
||||
|
||||
// test that a store can be opened in a specific location
|
||||
func testOpenStoreInLocation() {
|
||||
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||
let documentsURL = paths[0]
|
||||
let storeURI = documentsURL.appendingPathComponent("test.db", isDirectory: false).absoluteString
|
||||
XCTAssertNotNil(Mentat(storeURI: storeURI).raw)
|
||||
}
|
||||
|
||||
func readFile(forResource resource: String, withExtension ext: String, subdirectory: String ) throws -> String {
|
||||
let bundle = Bundle(for: type(of: self))
|
||||
let schemaUrl = bundle.url(forResource: resource, withExtension: ext, subdirectory: subdirectory)!
|
||||
let contents = try String(contentsOf: schemaUrl)
|
||||
return contents
|
||||
}
|
||||
|
||||
func readCitiesSchema() throws -> String {
|
||||
guard let schema = self.citiesSchema else {
|
||||
self.citiesSchema = try self.readFile(forResource: "cities", withExtension: "schema", subdirectory: "fixtures")
|
||||
return self.citiesSchema!
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
func readSeattleData() throws -> String {
|
||||
guard let data = self.seattleData else {
|
||||
self.seattleData = try self.readFile(forResource: "all_seattle", withExtension: "edn", subdirectory: "fixtures")
|
||||
return self.seattleData!
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func transactCitiesSchema(mentat: Mentat) throws -> TxReport {
|
||||
let vocab = try readCitiesSchema()
|
||||
let report = try mentat.transact(transaction: vocab)
|
||||
return report
|
||||
}
|
||||
|
||||
func transactSeattleData(mentat: Mentat) throws -> TxReport {
|
||||
let data = try readSeattleData()
|
||||
let report = try mentat.transact(transaction: data)
|
||||
return report
|
||||
}
|
||||
|
||||
func openAndInitializeCitiesStore() -> Mentat {
|
||||
guard let mentat = self.store else {
|
||||
let mentat = Mentat()
|
||||
let _ = try! self.transactCitiesSchema(mentat: mentat)
|
||||
let _ = try! self.transactSeattleData(mentat: mentat)
|
||||
self.store = mentat
|
||||
return mentat
|
||||
}
|
||||
|
||||
return mentat
|
||||
}
|
||||
|
||||
func populateWithTypesSchema(mentat: Mentat) -> (TxReport?, TxReport?) {
|
||||
do {
|
||||
let schema = """
|
||||
[
|
||||
[:db/add "b" :db/ident :foo/boolean]
|
||||
[:db/add "b" :db/valueType :db.type/boolean]
|
||||
[:db/add "b" :db/cardinality :db.cardinality/one]
|
||||
[:db/add "l" :db/ident :foo/long]
|
||||
[:db/add "l" :db/valueType :db.type/long]
|
||||
[:db/add "l" :db/cardinality :db.cardinality/one]
|
||||
[:db/add "r" :db/ident :foo/ref]
|
||||
[:db/add "r" :db/valueType :db.type/ref]
|
||||
[:db/add "r" :db/cardinality :db.cardinality/one]
|
||||
[:db/add "i" :db/ident :foo/instant]
|
||||
[:db/add "i" :db/valueType :db.type/instant]
|
||||
[:db/add "i" :db/cardinality :db.cardinality/one]
|
||||
[:db/add "d" :db/ident :foo/double]
|
||||
[:db/add "d" :db/valueType :db.type/double]
|
||||
[:db/add "d" :db/cardinality :db.cardinality/one]
|
||||
[:db/add "s" :db/ident :foo/string]
|
||||
[:db/add "s" :db/valueType :db.type/string]
|
||||
[:db/add "s" :db/cardinality :db.cardinality/one]
|
||||
[:db/add "k" :db/ident :foo/keyword]
|
||||
[:db/add "k" :db/valueType :db.type/keyword]
|
||||
[:db/add "k" :db/cardinality :db.cardinality/one]
|
||||
[:db/add "u" :db/ident :foo/uuid]
|
||||
[:db/add "u" :db/valueType :db.type/uuid]
|
||||
[:db/add "u" :db/cardinality :db.cardinality/one]
|
||||
]
|
||||
"""
|
||||
let report = try mentat.transact(transaction: schema)
|
||||
let stringEntid = report.entid(forTempId: "s")!
|
||||
|
||||
let data = """
|
||||
[
|
||||
[:db/add "a" :foo/boolean true]
|
||||
[:db/add "a" :foo/long 25]
|
||||
[:db/add "a" :foo/instant #inst "2017-01-01T11:00:00.000Z"]
|
||||
[:db/add "a" :foo/double 11.23]
|
||||
[:db/add "a" :foo/string "The higher we soar the smaller we appear to those who cannot fly."]
|
||||
[:db/add "a" :foo/keyword :foo/string]
|
||||
[:db/add "a" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"]
|
||||
[:db/add "b" :foo/boolean false]
|
||||
[:db/add "b" :foo/ref \(stringEntid)]
|
||||
[:db/add "b" :foo/keyword :foo/string]
|
||||
[:db/add "b" :foo/long 50]
|
||||
[:db/add "b" :foo/instant #inst "2018-01-01T11:00:00.000Z"]
|
||||
[:db/add "b" :foo/double 22.46]
|
||||
[:db/add "b" :foo/string "Silence is worse; all truths that are kept silent become poisonous."]
|
||||
[:db/add "b" :foo/uuid #uuid "4cb3f828-752d-497a-90c9-b1fd516d5644"]
|
||||
]
|
||||
"""
|
||||
let dataReport = try mentat.transact(transaction: data)
|
||||
return (report, dataReport)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
return (nil, nil)
|
||||
}
|
||||
|
||||
func test1TransactVocabulary() {
|
||||
do {
|
||||
let mentat = Mentat()
|
||||
let vocab = try readCitiesSchema()
|
||||
let report = try mentat.transact(transaction: vocab)
|
||||
XCTAssertNotNil(report)
|
||||
assert(report.txId > 0)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func test2TransactEntities() {
|
||||
do {
|
||||
let mentat = Mentat()
|
||||
let vocab = try readCitiesSchema()
|
||||
let _ = try mentat.transact(transaction: vocab)
|
||||
let data = try readSeattleData()
|
||||
let report = try mentat.transact(transaction: data)
|
||||
XCTAssertNotNil(report)
|
||||
assert(report.txId > 0)
|
||||
let entid = report.entid(forTempId: "a17592186045438")
|
||||
assert(entid == 65566)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testQueryScalar() {
|
||||
let mentat = openAndInitializeCitiesStore()
|
||||
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?name", toString: "Wallingford").runScalar(callback: { scalarResult in
|
||||
guard let result = scalarResult?.asString() else {
|
||||
return assertionFailure("No String value received")
|
||||
}
|
||||
assert(result == "KOMO Communities - Wallingford")
|
||||
expect.fulfill()
|
||||
}))
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testQueryColl() {
|
||||
let mentat = openAndInitializeCitiesStore()
|
||||
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
|
||||
guard let rows = collResult else {
|
||||
return assertionFailure("No results received")
|
||||
}
|
||||
// we are expecting 3 results
|
||||
for i in 0..<3 {
|
||||
let _ = rows.asDate(index: i)
|
||||
assert(true)
|
||||
}
|
||||
expect.fulfill()
|
||||
}))
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testQueryCollResultIterator() {
|
||||
let mentat = openAndInitializeCitiesStore()
|
||||
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
|
||||
guard let rows = collResult else {
|
||||
return assertionFailure("No results received")
|
||||
}
|
||||
|
||||
rows.forEach({ (value) in
|
||||
assert(value.valueType.rawValue == 2)
|
||||
})
|
||||
expect.fulfill()
|
||||
}))
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testQueryTuple() {
|
||||
let mentat = openAndInitializeCitiesStore()
|
||||
let query = """
|
||||
[:find [?name ?cat]
|
||||
:where
|
||||
[?c :community/name ?name]
|
||||
[?c :community/type :community.type/website]
|
||||
[(fulltext $ :community/category "food") [[?c ?cat]]]]
|
||||
"""
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query).runTuple(callback: { tupleResult in
|
||||
guard let tuple = tupleResult else {
|
||||
return assertionFailure("expecting a result")
|
||||
}
|
||||
let name = tuple.asString(index: 0)
|
||||
let category = tuple.asString(index: 1)
|
||||
assert(name == "Community Harvest of Southwest Seattle")
|
||||
assert(category == "sustainable food")
|
||||
expect.fulfill()
|
||||
}))
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testQueryRel() {
|
||||
let mentat = openAndInitializeCitiesStore()
|
||||
let query = """
|
||||
[:find ?name ?cat
|
||||
:where
|
||||
[?c :community/name ?name]
|
||||
[?c :community/type :community.type/website]
|
||||
[(fulltext $ :community/category "food") [[?c ?cat]]]]
|
||||
"""
|
||||
let expect = expectation(description: "Query is executed")
|
||||
let expectedResults = [("InBallard", "food"),
|
||||
("Seattle Chinatown Guide", "food"),
|
||||
("Community Harvest of Southwest Seattle", "sustainable food"),
|
||||
("University District Food Bank", "food bank")]
|
||||
XCTAssertNoThrow(try mentat.query(query: query).run(callback: { relResult in
|
||||
guard let rows = relResult else {
|
||||
return assertionFailure("No results received")
|
||||
}
|
||||
|
||||
for (i, row) in rows.enumerated() {
|
||||
let (name, category) = expectedResults[i]
|
||||
assert( row.asString(index: 0) == name)
|
||||
assert(row.asString(index: 1) == category)
|
||||
}
|
||||
expect.fulfill()
|
||||
}))
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testQueryRelResultIterator() {
|
||||
let mentat = openAndInitializeCitiesStore()
|
||||
let query = """
|
||||
[:find ?name ?cat
|
||||
:where
|
||||
[?c :community/name ?name]
|
||||
[?c :community/type :community.type/website]
|
||||
[(fulltext $ :community/category "food") [[?c ?cat]]]]
|
||||
"""
|
||||
let expect = expectation(description: "Query is executed")
|
||||
let expectedResults = [("InBallard", "food"),
|
||||
("Seattle Chinatown Guide", "food"),
|
||||
("Community Harvest of Southwest Seattle", "sustainable food"),
|
||||
("University District Food Bank", "food bank")]
|
||||
XCTAssertNoThrow(try mentat.query(query: query).run(callback: { relResult in
|
||||
guard let rows = relResult else {
|
||||
return assertionFailure("No results received")
|
||||
}
|
||||
|
||||
var i = 0
|
||||
rows.forEach({ (row) in
|
||||
let (name, category) = expectedResults[i]
|
||||
i += 1
|
||||
assert(row.asString(index: 0) == name)
|
||||
assert(row.asString(index: 1) == category)
|
||||
})
|
||||
assert(i == 4)
|
||||
expect.fulfill()
|
||||
}))
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindLong() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")
|
||||
let query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?long", toLong: 25)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == aEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindRef() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let stringEntid = mentat.entidForAttribute(attribute: ":foo/string")
|
||||
let bEntid = report!.entid(forTempId: "b")
|
||||
let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?ref", toReference: stringEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == bEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindKwRef() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let bEntid = report!.entid(forTempId: "b")
|
||||
let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?ref", toReference: ":foo/string")
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == bEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindKw() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")
|
||||
let query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?kw", toKeyword: ":foo/string")
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == aEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindDate() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")
|
||||
let query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
let boundDate = formatter.date(from: "2018-04-16T16:39:18+00:00")!
|
||||
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?now", toDate: boundDate)
|
||||
.runTuple { row in
|
||||
XCTAssertNotNil(row)
|
||||
assert(row?.asEntid(index: 0) == aEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func testBindString() {
|
||||
let mentat = openAndInitializeCitiesStore()
|
||||
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?name", toString: "Wallingford")
|
||||
.runScalar(callback: { scalarResult in
|
||||
guard let result = scalarResult?.asString() else {
|
||||
return assertionFailure("No String value received")
|
||||
}
|
||||
assert(result == "KOMO Communities - Wallingford")
|
||||
expect.fulfill()
|
||||
}))
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindUuid() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")
|
||||
let query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]"
|
||||
let uuid = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!
|
||||
let expect = expectation(description: "Query is rund")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?uuid", toUuid: uuid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == aEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindBoolean() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")
|
||||
let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?bool", toBoolean: true)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == aEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBindDouble() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")
|
||||
let query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?double", toDouble: 11.23)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == aEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsLong() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?e", toReference: aEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asLong() == 25)
|
||||
assert(value?.asLong() == 25)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsRef() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?e . :where [?e :foo/long 25]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asEntid() == aEntid)
|
||||
assert(value?.asEntid() == aEntid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsKw() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?e", toReference: aEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asKeyword() == ":foo/string")
|
||||
assert(value?.asKeyword() == ":foo/string")
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsBoolean() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?e", toReference: aEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asBool() == true)
|
||||
assert(value?.asBool() == true)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsDouble() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?e", toReference: aEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asDouble() == 11.23)
|
||||
assert(value?.asDouble() == 11.23)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsDate() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
|
||||
let expectedDate = formatter.date(from: "2017-01-01T11:00:00+00:00")
|
||||
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?e", toReference: aEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asDate() == expectedDate)
|
||||
assert(value?.asDate() == expectedDate)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsString() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]"
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?e", toReference: aEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asString() == "The higher we soar the smaller we appear to those who cannot fly.")
|
||||
assert(value?.asString() == "The higher we soar the smaller we appear to those who cannot fly.")
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTypedValueAsUuid() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
let query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]"
|
||||
let expectedUuid = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!
|
||||
let expect = expectation(description: "Query is executed")
|
||||
XCTAssertNoThrow(try mentat.query(query: query)
|
||||
.bind(varName: "?e", toReference: aEntid)
|
||||
.runScalar { value in
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asUUID() == expectedUuid)
|
||||
assert(value?.asUUID() == expectedUuid)
|
||||
expect.fulfill()
|
||||
})
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testValueForAttributeOfEntity() {
|
||||
let mentat = Mentat()
|
||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||
let aEntid = report!.entid(forTempId: "a")!
|
||||
var value: TypedValue? = nil;
|
||||
XCTAssertNoThrow(value = try mentat.value(forAttribute: ":foo/long", ofEntity: aEntid))
|
||||
XCTAssertNotNil(value)
|
||||
assert(value?.asLong() == 25)
|
||||
}
|
||||
|
||||
func testEntidForAttribute() {
|
||||
let mentat = Mentat()
|
||||
let _ = self.populateWithTypesSchema(mentat: mentat)
|
||||
let entid = mentat.entidForAttribute(attribute: ":foo/long")
|
||||
assert(entid == 65540)
|
||||
}
|
||||
|
||||
func testMultipleQueries() {
|
||||
let mentat = Mentat()
|
||||
let _ = self.populateWithTypesSchema(mentat: mentat)
|
||||
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
|
||||
|
||||
let q1Expect = expectation(description: "Query 1 is executed")
|
||||
XCTAssertNoThrow(try q1.run { results in
|
||||
XCTAssertNotNil(results)
|
||||
q1Expect.fulfill()
|
||||
})
|
||||
|
||||
let q2 = mentat.query(query: "[:find ?x :where [_ _ ?x]]")
|
||||
let q2Expect = expectation(description: "Query 2 is executed")
|
||||
XCTAssertNoThrow(try q2.run { results in
|
||||
XCTAssertNotNil(results)
|
||||
q2Expect.fulfill()
|
||||
})
|
||||
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testNestedQueries() {
|
||||
let mentat = Mentat()
|
||||
let _ = self.populateWithTypesSchema(mentat: mentat)
|
||||
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
|
||||
let q2 = mentat.query(query: "[:find ?x :where [_ _ ?x]]")
|
||||
|
||||
let expect = expectation(description: "Query 1 is executed")
|
||||
XCTAssertNoThrow(try q1.run { results in
|
||||
XCTAssertNotNil(results)
|
||||
try? q2.run { results in
|
||||
XCTAssertNotNil(results)
|
||||
expect.fulfill()
|
||||
}
|
||||
})
|
||||
|
||||
waitForExpectations(timeout: 1) { error in
|
||||
if let error = error {
|
||||
assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: Add tests for transaction observation
|
||||
}
|
17
src/conn.rs
17
src/conn.rs
|
@ -94,7 +94,6 @@ use mentat_tolstoy::Syncer;
|
|||
use uuid::Uuid;
|
||||
|
||||
use entity_builder::{
|
||||
BuildTerms,
|
||||
InProgressBuilder,
|
||||
TermBuilder,
|
||||
};
|
||||
|
@ -647,10 +646,6 @@ impl Store {
|
|||
pub fn unregister_observer(&mut self, key: &String) {
|
||||
self.conn.unregister_observer(key);
|
||||
}
|
||||
|
||||
pub fn assert_datom<T>(&mut self, entid: T, attribute: Keyword, value: TypedValue) -> Result<()> where T: Into<KnownEntid> {
|
||||
self.conn.assert_datom(&mut self.sqlite, entid, attribute, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Queryable for Store {
|
||||
|
@ -973,18 +968,6 @@ impl Conn {
|
|||
pub fn unregister_observer(&mut self, key: &String) {
|
||||
self.tx_observer_service.lock().unwrap().deregister(key);
|
||||
}
|
||||
|
||||
// TODO: expose the entity builder over FFI and remove the need for this function entirely
|
||||
// It's really only here in order to keep the FFI layer as thin as possible.
|
||||
// Once the entity builder is exposed, we can perform all of these functions over FFI from the client.
|
||||
pub fn assert_datom<T>(&mut self, sqlite: &mut rusqlite::Connection, entid: T, attribute: Keyword, value: TypedValue) -> Result<()> where T: Into<KnownEntid> {
|
||||
let in_progress = self.begin_transaction(sqlite)?;
|
||||
let mut builder = in_progress.builder().describe(entid.into());
|
||||
builder.add_kw(&attribute, value)?;
|
||||
builder.commit()
|
||||
.map_err(|e| e.into())
|
||||
.and(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -55,6 +55,7 @@ pub use mentat_query::{
|
|||
pub use mentat_db::{
|
||||
CORE_SCHEMA_VERSION,
|
||||
DB_SCHEMA_CORE,
|
||||
AttributeSet,
|
||||
TxObserver,
|
||||
TxReport,
|
||||
new_connection,
|
||||
|
|
|
@ -14,10 +14,12 @@ use std::collections::{
|
|||
};
|
||||
|
||||
use mentat_core::{
|
||||
DateTime,
|
||||
Entid,
|
||||
Keyword,
|
||||
Binding,
|
||||
TypedValue,
|
||||
Utc,
|
||||
ValueType,
|
||||
};
|
||||
|
||||
|
@ -37,15 +39,15 @@ use errors::{
|
|||
};
|
||||
|
||||
pub struct QueryBuilder<'a> {
|
||||
sql: String,
|
||||
query: String,
|
||||
values: BTreeMap<Variable, TypedValue>,
|
||||
types: BTreeMap<Variable, ValueType>,
|
||||
store: &'a mut Store,
|
||||
}
|
||||
|
||||
impl<'a> QueryBuilder<'a> {
|
||||
pub fn new<T>(store: &'a mut Store, sql: T) -> QueryBuilder where T: Into<String> {
|
||||
QueryBuilder { sql: sql.into(), values: BTreeMap::new(), types: BTreeMap::new(), store }
|
||||
pub fn new<T>(store: &'a mut Store, query: T) -> QueryBuilder where T: Into<String> {
|
||||
QueryBuilder { query: query.into(), values: BTreeMap::new(), types: BTreeMap::new(), store }
|
||||
}
|
||||
|
||||
pub fn bind_value<T>(&mut self, var: &str, value: T) -> &mut Self where T: Into<TypedValue> {
|
||||
|
@ -71,6 +73,12 @@ impl<'a> QueryBuilder<'a> {
|
|||
|
||||
pub fn bind_instant(&mut self, var: &str, value: i64) -> &mut Self {
|
||||
self.values.insert(Variable::from_valid_name(var), TypedValue::instant(value));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bind_date_time(&mut self, var: &str, value: DateTime<Utc>) -> &mut Self {
|
||||
self.values.insert(Variable::from_valid_name(var), TypedValue::Instant(value));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -84,7 +92,7 @@ impl<'a> QueryBuilder<'a> {
|
|||
let types = ::std::mem::replace(&mut self.types, Default::default());
|
||||
let query_inputs = QueryInputs::new(types, values)?;
|
||||
let read = self.store.begin_read()?;
|
||||
read.q_once(&self.sql, query_inputs)
|
||||
read.q_once(&self.query, query_inputs)
|
||||
}
|
||||
|
||||
pub fn execute_scalar(&mut self) -> Result<Option<Binding>> {
|
||||
|
@ -116,6 +124,11 @@ mod test {
|
|||
Store,
|
||||
};
|
||||
|
||||
use mentat_core::{
|
||||
DateTime,
|
||||
Utc,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_scalar_query() {
|
||||
let mut store = Store::open("").expect("store connection");
|
||||
|
|
Loading…
Reference in a new issue