Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@ build.xcarchive
docs/_site
docs/.sass-cache
docs/.jekyll-metadata

18 changes: 14 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
language: rust
cache: cargo # cache cargo-audit once installed
before_script:
# - cargo install --force clippy
- cargo install --force cargo-audit
- cargo generate-lockfile
script:
- cargo audit
# We use OSX so that we can get a reasonably up to date version of SQLCipher.
# (The version in Travis's default Ubuntu Trusty is much too old).
os: osx
before_install:
- brew install sqlcipher --with-fts
- brew install sqlcipher
rust:
- 1.25.0
- 1.41.0
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: stable
- rust: nightly
fast_finish: true
jobs:
include:
- stage: "Test iOS"
rust: 1.25.0
rust: 1.41.0
script: ./scripts/test-ios.sh
- stage: "Docs"
rust: 1.25.0
rust: 1.41.0
script: ./scripts/cargo-doc.sh
script:
- cargo build --verbose --all
# - cargo clippy --all-targets --all-features -- -D warnings # Check tests and non-default crate features.
- cargo test --verbose --all
- cargo test --features edn/serde_support --verbose --all
# We can't pick individual features out with `cargo test --all` (At the time of this writing, this
Expand Down
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ members = ["tools/cli", "ffi"]
[build-dependencies]
rustc_version = "0.2"

[dev-dependencies.cargo-husky]
version = "1"
default-features = false # Disable features which are enabled by default
features = ["run-for-all", "precommit-hook", "run-cargo-fmt", "run-cargo-test", "run-cargo-check", "run-cargo-clippy"]
# cargo audit
# cargo outdated

[dependencies]
chrono = "0.4"
failure = "0.1.6"
Expand Down
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
# Project Mentat
[![Build Status](https://travis-ci.org/mozilla/mentat.svg?branch=master)](https://travis-ci.org/mozilla/mentat)

**Project Mentat is [no longer being developed or actively maintained by Mozilla](https://mail.mozilla.org/pipermail/firefox-dev/2018-September/006780.html).** This repository will be marked read-only in the near future. You are, of course, welcome to fork the repository and use the existing code.
[![Build Status](https://travis-ci.org/qpdb/mentat.svg?branch=master)](https://travis-ci.org/qpdb/mentat)

Project Mentat is a persistent, embedded knowledge base. It draws heavily on [DataScript](https://github.com/tonsky/datascript) and [Datomic](http://datomic.com).

Mentat is implemented in Rust.

The first version of Project Mentat, named Datomish, [was written in ClojureScript](https://github.com/mozilla/mentat/tree/clojure), targeting both Node (on top of `promise_sqlite`) and Firefox (on top of `Sqlite.jsm`). It also worked in pure Clojure on the JVM on top of `jdbc-sqlite`. The name was changed to avoid confusion with [Datomic](http://datomic.com).
This project was started by Mozilla, but [is no longer being developed or actively maintained by them](https://mail.mozilla.org/pipermail/firefox-dev/2018-September/006780.html). [Their repository](https://github.com/mozilla/mentat) was marked read-only, [this fork](https://github.com/qpdb/mentat) is an attempt to revive and continue that interesting work. We owe the team at Mozilla more than words can express for inspiring us all and for this project in particular.

The Rust implementation gives us a smaller compiled output, better performance, more type safety, better tooling, and easier deployment into Firefox and mobile platforms.
*Thank you*.

[Documentation](https://mozilla.github.io/mentat)
[Documentation](https://docs.rs/mentat)

---

Expand Down Expand Up @@ -77,9 +73,11 @@ We've observed that data storage is a particular area of difficulty for software

DataScript asks the question: "What if creating a database were as cheap as creating a Hashmap?"

Mentat is not interested in that. Instead, it's strongly interested in persistence and performance, with very little interest in immutable databases/databases as values or throwaway use.
Mentat is not interested in that. Instead, it's focused on persistence and performance, with very little interest in immutable databases/databases as values or throwaway use.

One might say that Mentat's question is: "What if an SQLite database could store arbitrary relations, for arbitrary consumers, without them having to coordinate an up-front storage-level schema?"
One might say that Mentat's question is: "What if a database could store arbitrary relations, for arbitrary consumers, without them having to coordinate an up-front storage-level schema?"

Consider this a practical approach to facts, to knowledge its storage and access, much like SQLite is a practical RDBMS.

(Note that [domain-level schemas are very valuable](http://martinfowler.com/articles/schemaless/).)

Expand All @@ -89,7 +87,7 @@ Some thought has been given to how databases as values — long-term references

Just like DataScript, Mentat speaks Datalog for querying and takes additions and retractions as input to a transaction.

Unlike DataScript, Mentat exposes free-text indexing, thanks to SQLite.
Unlike DataScript, Mentat exposes free-text indexing, thanks to SQLite/FTS.


## Comparison to Datomic
Expand All @@ -98,8 +96,6 @@ Datomic is a server-side, enterprise-grade data storage system. Datomic has a be

Many of these design decisions are inapplicable to deployed desktop software; indeed, the use of multiple JVM processes makes Datomic's use in a small desktop app, or a mobile device, prohibitive.

Mentat was designed for embedding, initially in an experimental Electron app ([Tofino](https://github.com/mozilla/tofino)). It is less concerned with exposing consistent database states outside transaction boundaries, because that's less important here, and dropping some of these requirements allows us to leverage SQLite itself.


## Comparison to SQLite

Expand Down
2 changes: 1 addition & 1 deletion build/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::process::exit;

/// 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.41.0";
static MIN_VERSION: &str = "1.40.0";

fn main() {
let ver = version().unwrap();
Expand Down
76 changes: 38 additions & 38 deletions core-traits/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl<V: TransactableValueMarker> Into<ValuePlace<V>> for KnownEntid {
/// When moving to a more concrete table, such as `datoms`, they are expanded out
/// via these flags and put into their own column rather than a bit field.
pub enum AttributeBitFlags {
IndexAVET = 1 << 0,
IndexAVET = 1,
IndexVAET = 1 << 1,
IndexFulltext = 1 << 2,
UniqueValue = 1 << 3,
Expand Down Expand Up @@ -327,20 +327,20 @@ impl ValueType {

pub fn from_keyword(keyword: &Keyword) -> Option<Self> {
if keyword.namespace() != Some("db.type") {
return None;
None
} else {
match keyword.name() {
"ref" => Some(ValueType::Ref),
"boolean" => Some(ValueType::Boolean),
"instant" => Some(ValueType::Instant),
"long" => Some(ValueType::Long),
"double" => Some(ValueType::Double),
"string" => Some(ValueType::String),
"keyword" => Some(ValueType::Keyword),
"uuid" => Some(ValueType::Uuid),
_ => None,
}
}

return match keyword.name() {
"ref" => Some(ValueType::Ref),
"boolean" => Some(ValueType::Boolean),
"instant" => Some(ValueType::Instant),
"long" => Some(ValueType::Long),
"double" => Some(ValueType::Double),
"string" => Some(ValueType::String),
"keyword" => Some(ValueType::Keyword),
"uuid" => Some(ValueType::Uuid),
_ => None,
};
}

pub fn into_typed_value(self) -> TypedValue {
Expand Down Expand Up @@ -372,9 +372,9 @@ impl ValueType {
}
}

pub fn is_numeric(&self) -> bool {
pub fn is_numeric(self) -> bool {
match self {
&ValueType::Long | &ValueType::Double => true,
ValueType::Long | ValueType::Double => true,
_ => false,
}
}
Expand Down Expand Up @@ -440,14 +440,14 @@ impl TypedValue {

pub fn value_type(&self) -> ValueType {
match self {
&TypedValue::Ref(_) => ValueType::Ref,
&TypedValue::Boolean(_) => ValueType::Boolean,
&TypedValue::Long(_) => ValueType::Long,
&TypedValue::Instant(_) => ValueType::Instant,
&TypedValue::Double(_) => ValueType::Double,
&TypedValue::String(_) => ValueType::String,
&TypedValue::Keyword(_) => ValueType::Keyword,
&TypedValue::Uuid(_) => ValueType::Uuid,
TypedValue::Ref(_) => ValueType::Ref,
TypedValue::Boolean(_) => ValueType::Boolean,
TypedValue::Long(_) => ValueType::Long,
TypedValue::Instant(_) => ValueType::Instant,
TypedValue::Double(_) => ValueType::Double,
TypedValue::String(_) => ValueType::String,
TypedValue::Keyword(_) => ValueType::Keyword,
TypedValue::Uuid(_) => ValueType::Uuid,
}
}

Expand Down Expand Up @@ -770,21 +770,21 @@ impl Binding {

pub fn as_scalar(&self) -> Option<&TypedValue> {
match self {
&Binding::Scalar(ref v) => Some(v),
Binding::Scalar(ref v) => Some(v),
_ => None,
}
}

pub fn as_vec(&self) -> Option<&Vec<Binding>> {
match self {
&Binding::Vec(ref v) => Some(v),
Binding::Vec(ref v) => Some(v),
_ => None,
}
}

pub fn as_map(&self) -> Option<&StructuredMap> {
match self {
&Binding::Map(ref v) => Some(v),
Binding::Map(ref v) => Some(v),
_ => None,
}
}
Expand Down Expand Up @@ -856,10 +856,10 @@ impl Binding {

pub fn value_type(&self) -> Option<ValueType> {
match self {
&Binding::Scalar(ref v) => Some(v.value_type()),
Binding::Scalar(ref v) => Some(v.value_type()),

&Binding::Map(_) => None,
&Binding::Vec(_) => None,
Binding::Map(_) => None,
Binding::Vec(_) => None,
}
}
}
Expand Down Expand Up @@ -970,56 +970,56 @@ impl Binding {

pub fn as_entid(&self) -> Option<&Entid> {
match self {
&Binding::Scalar(TypedValue::Ref(ref v)) => Some(v),
Binding::Scalar(TypedValue::Ref(ref v)) => Some(v),
_ => None,
}
}

pub fn as_kw(&self) -> Option<&ValueRc<Keyword>> {
match self {
&Binding::Scalar(TypedValue::Keyword(ref v)) => Some(v),
Binding::Scalar(TypedValue::Keyword(ref v)) => Some(v),
_ => None,
}
}

pub fn as_boolean(&self) -> Option<&bool> {
match self {
&Binding::Scalar(TypedValue::Boolean(ref v)) => Some(v),
Binding::Scalar(TypedValue::Boolean(ref v)) => Some(v),
_ => None,
}
}

pub fn as_long(&self) -> Option<&i64> {
match self {
&Binding::Scalar(TypedValue::Long(ref v)) => Some(v),
Binding::Scalar(TypedValue::Long(ref v)) => Some(v),
_ => None,
}
}

pub fn as_double(&self) -> Option<&f64> {
match self {
&Binding::Scalar(TypedValue::Double(ref v)) => Some(&v.0),
Binding::Scalar(TypedValue::Double(ref v)) => Some(&v.0),
_ => None,
}
}

pub fn as_instant(&self) -> Option<&DateTime<Utc>> {
match self {
&Binding::Scalar(TypedValue::Instant(ref v)) => Some(v),
Binding::Scalar(TypedValue::Instant(ref v)) => Some(v),
_ => None,
}
}

pub fn as_string(&self) -> Option<&ValueRc<String>> {
match self {
&Binding::Scalar(TypedValue::String(ref v)) => Some(v),
Binding::Scalar(TypedValue::String(ref v)) => Some(v),
_ => None,
}
}

pub fn as_uuid(&self) -> Option<&Uuid> {
match self {
&Binding::Scalar(TypedValue::Uuid(ref v)) => Some(v),
Binding::Scalar(TypedValue::Uuid(ref v)) => Some(v),
_ => None,
}
}
Expand Down
26 changes: 13 additions & 13 deletions core-traits/value_type_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,53 +92,53 @@ impl ValueTypeSet {
self.0.insert(vt)
}

pub fn len(&self) -> usize {
pub fn len(self) -> usize {
self.0.len()
}

/// Returns a set containing all the types in this set and `other`.
pub fn union(&self, other: &ValueTypeSet) -> ValueTypeSet {
pub fn union(self, other: ValueTypeSet) -> ValueTypeSet {
ValueTypeSet(self.0.union(other.0))
}

pub fn intersection(&self, other: &ValueTypeSet) -> ValueTypeSet {
pub fn intersection(self, other: ValueTypeSet) -> ValueTypeSet {
ValueTypeSet(self.0.intersection(other.0))
}

/// Returns the set difference between `self` and `other`, which is the
/// set of items in `self` that are not in `other`.
pub fn difference(&self, other: &ValueTypeSet) -> ValueTypeSet {
pub fn difference(self, other: ValueTypeSet) -> ValueTypeSet {
ValueTypeSet(self.0 - other.0)
}

/// Return an arbitrary type that's part of this set.
/// For a set containing a single type, this will be that type.
pub fn exemplar(&self) -> Option<ValueType> {
pub fn exemplar(self) -> Option<ValueType> {
self.0.iter().next()
}

pub fn is_subset(&self, other: &ValueTypeSet) -> bool {
pub fn is_subset(self, other: ValueTypeSet) -> bool {
self.0.is_subset(&other.0)
}

/// Returns true if `self` and `other` contain no items in common.
pub fn is_disjoint(&self, other: &ValueTypeSet) -> bool {
pub fn is_disjoint(self, other: ValueTypeSet) -> bool {
self.0.is_disjoint(&other.0)
}

pub fn contains(&self, vt: ValueType) -> bool {
pub fn contains(self, vt: ValueType) -> bool {
self.0.contains(&vt)
}

pub fn is_empty(&self) -> bool {
pub fn is_empty(self) -> bool {
self.0.is_empty()
}

pub fn is_unit(&self) -> bool {
pub fn is_unit(self) -> bool {
self.0.len() == 1
}

pub fn iter(&self) -> ::enum_set::Iter<ValueType> {
pub fn iter(self) -> ::enum_set::Iter<ValueType> {
self.0.iter()
}
}
Expand All @@ -150,8 +150,8 @@ impl From<ValueType> for ValueTypeSet {
}

impl ValueTypeSet {
pub fn is_only_numeric(&self) -> bool {
self.is_subset(&ValueTypeSet::of_numeric_types())
pub fn is_only_numeric(self) -> bool {
self.is_subset(ValueTypeSet::of_numeric_types())
}
}

Expand Down
8 changes: 4 additions & 4 deletions core-traits/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ use edn::types::Value;
/// Declare a lazy static `ident` of type `Value::Keyword` with the given `namespace` and
/// `name`.
///
/// It may look surprising that we declare a new `lazy_static!` block rather than including
/// It may look surprising to declare a new `lazy_static!` block rather than including
/// invocations inside an existing `lazy_static!` block. The latter cannot be done, since macros
/// are expanded outside-in. Looking at the `lazy_static!` source suggests that there is no harm in
/// repeating that macro, since internally a multi-`static` block is expanded into many
/// single-`static` blocks.
/// will be expanded outside-in. Looking at the `lazy_static!` source suggests that there is no
/// harm in repeating that macro, since internally a multi-`static` block will be expanded into
/// many single-`static` blocks.
///
/// TODO: take just ":db.part/db" and define DB_PART_DB using "db.part" and "db".
macro_rules! lazy_static_namespaced_keyword_value (
Expand Down
Loading