Coder Social home page Coder Social logo

lambdaisland / metabase-datomic Goto Github PK

View Code? Open in Web Editor NEW
63.0 9.0 11.0 190 KB

Datomic driver for Metabase

License: Mozilla Public License 2.0

Emacs Lisp 0.26% Shell 0.66% Clojure 97.69% Ruby 1.00% Dockerfile 0.38%
business-intelligence data-visualization metabase

metabase-datomic's Introduction

metabase-datomic

A Metabase driver for Datomic.

Commercial support is provided by Gaiwan.

Try it!

docker run -p 3000:3000 lambdaisland/metabase-datomic

Design decisions

See the Architecture Decision Log

Developing

To get a REPL based workflow, do a git clone of both metabase and metabase-datomic, such that they are in sibling directories

$ git clone [email protected]:metabase/metabase.git
$ git clone [email protected]:plexus/metabase-datomic.git

$ tree -L 1
.
│
├── metabase
└── metabase-datomic

Before you can use Metabase you need to build the frontend. This step you only need to do once.

cd metabase
yarn build

And install metabase locally

lein install

Now cd into the metabase-datomic directory, and run bin/start_metabase to lauch the process including nREPL running on port 4444.

cd metabase-datomic
bin/start_metabase

Now you can connect from Emacs/CIDER to port 4444, or use the bin/cider_connect script to automatically connect, and to associate the REPL session with both projects, so you can easily evaluate code in either, and navigate back and forth.

Once you have a REPL you can start the web app. This also opens a browser at localhost:3000

user=> (go)

The first time it will ask you to create a user and do some other initial setup. To skip this step, invoke setup!. This will create a user with username [email protected] and password dev. It will also create a Datomic database with URL datomic:free://localhost:4334/mbrainz. You are encouraged to run a datomic-free transactor, and import the MusicBrainz database for testing.

user=> (setup!)

Installing

The general process is to build an uberjar, and copy the result into your Metabase plugins/ directory. You can build a jar based on datomic-free, or datomic-pro (assuming you have a license). Metabase must be available as a local JAR.

cd metabase
lein install
mkdir plugins
cd ../metabase-datomic
lein with-profiles +datomic-free uberjar
# lein with-profiles +datomic-pro uberjar
cp target/uberjar/datomic.metabase-driver.jar ../metabase/plugins

Now you can start Metabase, and start adding Datomic databases

cd ../metabase
lein run -m metabase.core

Configuration EDN

When you configure a Datomic Database in Metabase you will notice a config field called "Configuration EDN". Here you can paste a snippet of EDN which will influence some of the Driver's behavior.

The EDN needs to represent a Clojure map. These keys are currently understood

  • :inclusion-clauses
  • :tx-filter
  • :relationships

Other keys are ignored.

:inclusion-clauses

Datomic does not have tables, but nevertheless the driver will map your data to Metabase tables based on the attribute names in your schema. To limit results to the right entities it needs to do a check to see if a certain entity logically belongs to such a table.

By default these look like this

[(or [?eid :user/name]
     [?eid :user/password]
     [?eid :user/roles])]

In other words we look for entities that have any attribute starting with the given prefix. This can be both suboptimal (there might be a single attribute with an index that is faster to check), and it may be wrong, depending on your setup.

So we allow configuring this clause per table. The configured value should be a vector of datomic clauses. You have the full power of datalog available. Use the special symbol ?eid for the entity that is being filtered.

{:inclusion-clauses {"user" [[?eid :user/handle]]}}

:tx-filter

The datomic.api/filter function allows you to get a filtered view of the database. A common use case is to select datoms based on metadata added to transaction entities.

You can set :tx-filter to any form that evaluates to a Clojure function. Make sure any namespaces like datomic.api are fully qualified.

{:tx-filter
 (fn [db ^datomic.Datom datom]
   (let [tx-user (get-in (datomic.api/entity db (.tx datom)) [:tx/user :db/id])]
     (or (nil? tx-tenant) (= 17592186046521 tx-user))))}

:rules

This allows you to configure Datomic rules. These then become available in the native query editor, as well in :inclusion-clauses and :relationships.

{:rules
 [[(sub-accounts ?p ?c)
   [?p :account/children ?c]]
  [(sub-accounts ?p ?d)
   [?p :account/children ?c]
   (sub-accounts ?c ?d)]]}

:relationships

This features allows you to add "synthetic foreign keys" to tables. These are fields that Metabase will consider to be foreign keys, but in reality they are backed by an arbitrary lookup path in Datomic. This can include reverse reference (:foo/_bar) and rules.

To set up an extra relationship you start from the table where you want to add the relationship, then give it a name, give the path of attributes and rules needed to get to the other entity, and specifiy which table the resulting entity belongs to.

{:relationships
 {;; foreign keys added to the account table
  :account
  {:journal-entry-lines
   {:path [:journal-entry-line/_account]
    :target :journal-entry-line}

   :subaccounts
   {:path [sub-accounts]
    :target :account}

   :parent-accounts
   {:path [_sub-accounts] ;; apply a rule in reverse
    :target :account}}

  ;; foreign keys added to the journal-entry-line table
  :journal-entry-line
  {:fiscal-year
   {:path [:journal-entry/_journal-entry-lines
           :ledger/_journal-entries
           :fiscal-year/_ledgers]
    :target :fiscal-year}}}}

Status

FeatureSupported?
Basics
    {:source-table integer-literal}Yes
    {:fields [& field]}Yes
        [:field-id field-id]Yes
        [:datetime-field local-field | fk unit]Yes
    {:breakout [& concrete-field]}Yes
        [:field-id field-id]Yes
        [:aggregation 0]Yes
        [:datetime-field local-field | fk unit]Yes
    {:filter filter-clause}
        [:and & filter-clause]Yes
        [:or & filter-clause]Yes
        [:not filter-clause]Yes
        [:= concrete-field value & value]Yes
        [:!= concrete-field value & value]Yes
        [:< concrete-field orderable-value]Yes
        [:> concrete-field orderable-value]Yes
        [:<= concrete-field orderable-value]Yes
        [:>= concrete-field orderable-value]Yes
        [:is-null concrete-field]Yes
        [:not-null concrete-field]Yes
        [:between concrete-field min orderable-value max orderable-value]Yes
        [:inside lat concrete-field lon concrete-field lat-max numeric-literal lon-min numeric-literal lat-min numeric-literal lon-max numeric-literal]Yes
        [:starts-with concrete-field string-literal]Yes
        [:contains concrete-field string-literal]Yes
        [:does-not-contain concrete-field string-literal]Yes
        [:ends-with concrete-field string-literal]Yes
        [:time-interval field concrete-field n :current|:last|:next|integer-literal unit relative-datetime-unit]
    {:limit integer-literal}Yes
    {:order-by [& order-by-clause]}Yes
:basic-aggregations
    {:aggregation aggregation-clause}
        [:count]Yes
        [:count concrete-field]Yes
        [:cum-count concrete-field]Yes
        [:cum-sum concrete-field]Yes
        [:distinct concrete-field]Yes
        [:sum concrete-field]Yes
        [:min concrete-field]Yes
        [:max concrete-field]Yes
        [:share filter-clause]
:standard-deviation-aggregationsYes
    {:aggregation aggregation-clause}Yes
        [:stddev concrete-field]Yes
:foreign-keysYes
    {:fields [& field]}Yes
        [:fk-> fk-field-id dest-field-id]Yes
:nested-fields
:set-timezone
:expressions
    {:fields [& field]}
        [:expression]
    {:breakout [& concrete-field]}
        [:expression]
    {:expressions {expression-name expression}}
:native-parameters
:expression-aggregations
:nested-queriesYes
    {:source-query query}Yes
:binning
:case-sensitivity-string-filter-optionsYes

License

Copyright © 2019 Arne Brasseur

Licensed under the term of the Mozilla Public License 2.0, see LICENSE.

metabase-datomic's People

Contributors

daveliepmann avatar plexus avatar zilti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

metabase-datomic's Issues

Problem to connect a (non in-memory) database

that's all the information I get from Metabase...

connecting to the same DB from lein repl works fine.

DB URI from host:
datomic:free://localhost:4334/mbrainz-1968-1973
DB URI passed to setup page of Metabase running in docker:
datomic:free://docker.host.internal:4334/mbrainz-1968-1973

12-27 15:37:44 DEBUG middleware.log :: POST /api/setup/validate 400 18 ms (0 DB calls) 
{:message "Unable to connect to database."}

12-27 15:37:44 DEBUG middleware.log :: POST /api/setup/validate 400 19 ms (0 DB calls) 
{:message "Unable to connect to database."}

datomic-free transactor running with default configuration options (local, no username/password).

#7

Problem to connect an in-memory database

Hi,

I am trying to connect to my in-memory database but I keep receiving the following error:

{:message "Unable to connect to database."}

It's possible to use with an in-memory db?

datomic pro unsupported protocol issue

I have the datomic pro version building from source with no issues however when I try to connect to a url like

datomic:sql://test?jdbc:postgresql://db.example.com:5432/datomic?user=datomic&password=datomic

or

datomic:dev://localhost:4334/test

I get an exception trying to connect

:cause :db.error/unsupported-protocol Unsupported protocol :sql

I assume that the classes are not loaded correctly at the time of connect - any ideas on how to proceed ?

datomic pro uberjar error

Probably related to #3, but I'm hitting the following error trying to build an uberjar for datomic pro

lein with-profiles +datomic-pro uberjar 
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 -Duser.timezone=UTC
If there are a lot of uncached dependencies this might take a while ...
Error encountered performing task 'uberjar' with profile(s): 'base,system,user,provided,dev,datomic-pro'
clojure.lang.ExceptionInfo: Unknown alias key: :mvn/repos {:key :mvn/repos}
	at clojure.tools.deps.alpha$choose_rule.invokeStatic(alpha.clj:37)
	at clojure.tools.deps.alpha$choose_rule.invoke(alpha.clj:35)
	at clojure.tools.deps.alpha$merge_alias_maps$fn__1329$fn__1331.invoke(alpha.clj:44)
	at clojure.core.protocols$iter_reduce.invokeStatic(protocols.clj:49)
	at clojure.core.protocols$fn__8125.invokeStatic(protocols.clj:75)
	at clojure.core.protocols$fn__8125.invoke(protocols.clj:75)
	at clojure.core.protocols$fn__8073$G__8068__8086.invoke(protocols.clj:13)
	at clojure.core$reduce.invokeStatic(core.clj:6828)
	at clojure.core$reduce.invoke(core.clj:6810)
	at clojure.tools.deps.alpha$merge_alias_maps$fn__1329.invoke(alpha.clj:43)
	at clojure.lang.ArrayChunk.reduce(ArrayChunk.java:58)
	at clojure.core.protocols$fn__8139.invokeStatic(protocols.clj:136)
	at clojure.core.protocols$fn__8139.invoke(protocols.clj:124)
	at clojure.core.protocols$fn__8099$G__8094__8108.invoke(protocols.clj:19)
	at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:31)
	at clojure.core.protocols$fn__8129.invokeStatic(protocols.clj:75)
	at clojure.core.protocols$fn__8129.invoke(protocols.clj:75)
	at clojure.core.protocols$fn__8073$G__8068__8086.invoke(protocols.clj:13)
	at clojure.core$reduce.invokeStatic(core.clj:6828)
	at clojure.core$reduce.invoke(core.clj:6810)
	at clojure.tools.deps.alpha$merge_alias_maps.invokeStatic(alpha.clj:42)
	at clojure.tools.deps.alpha$merge_alias_maps.doInvoke(alpha.clj:39)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:665)
	at clojure.core$apply.invoke(core.clj:660)
	at clojure.tools.deps.alpha$combine_aliases.invokeStatic(alpha.clj:63)
	at clojure.tools.deps.alpha$combine_aliases.invoke(alpha.clj:56)
	at lein_tools_deps.lein_project$resolve_deps.invokeStatic(lein_project.clj:53)
	at lein_tools_deps.lein_project$resolve_deps.invoke(lein_project.clj:50)
	at lein_tools_deps.plugin$apply_middleware.invokeStatic(plugin.clj:22)
	at lein_tools_deps.plugin$apply_middleware.invoke(plugin.clj:17)
	at lein_tools_deps.plugin$apply_middleware.invokeStatic(plugin.clj:25)
	at lein_tools_deps.plugin$apply_middleware.invoke(plugin.clj:17)
	at lein_tools_deps.plugin$resolve_dependencies_with_deps_edn.invokeStatic(plugin.clj:47)
	at lein_tools_deps.plugin$resolve_dependencies_with_deps_edn.invoke(plugin.clj:34)
	at clojure.lang.Var.invoke(Var.java:384)
	at leiningen.core.project$apply_middleware.invokeStatic(project.clj:817)
	at leiningen.core.project$apply_middleware.invoke(project.clj:810)
	at clojure.lang.ArrayChunk.reduce(ArrayChunk.java:58)
	at clojure.core.protocols$fn__8139.invokeStatic(protocols.clj:136)
	at clojure.core.protocols$fn__8139.invoke(protocols.clj:124)
	at clojure.core.protocols$fn__8099$G__8094__8108.invoke(protocols.clj:19)
	at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:31)
	at clojure.core.protocols$fn__8131.invokeStatic(protocols.clj:75)
	at clojure.core.protocols$fn__8131.invoke(protocols.clj:75)
	at clojure.core.protocols$fn__8073$G__8068__8086.invoke(protocols.clj:13)
	at clojure.core$reduce.invokeStatic(core.clj:6828)
	at clojure.core$reduce.invoke(core.clj:6810)
	at leiningen.core.project$apply_middleware.invokeStatic(project.clj:812)
	at leiningen.core.project$apply_middleware.invoke(project.clj:810)
	at leiningen.core.project$activate_middleware.invokeStatic(project.clj:844)
	at leiningen.core.project$activate_middleware.invoke(project.clj:840)
	at leiningen.core.project$set_profiles.invokeStatic(project.clj:929)
	at leiningen.core.project$set_profiles.doInvoke(project.clj:922)
	at clojure.lang.RestFn.invoke(RestFn.java:425)
	at leiningen.with_profile$with_profiles_STAR_.invokeStatic(with_profile.clj:12)
	at leiningen.with_profile$with_profiles_STAR_.invoke(with_profile.clj:8)
	at leiningen.with_profile$apply_task_with_profiles.invokeStatic(with_profile.clj:53)
	at leiningen.with_profile$apply_task_with_profiles.invoke(with_profile.clj:45)
	at leiningen.with_profile$with_profile$fn__10482.invoke(with_profile.clj:85)
	at clojure.core$mapv$fn__8430.invoke(core.clj:6912)
	at clojure.core.protocols$fn__8144.invokeStatic(protocols.clj:168)
	at clojure.core.protocols$fn__8144.invoke(protocols.clj:124)
	at clojure.core.protocols$fn__8099$G__8094__8108.invoke(protocols.clj:19)
	at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:31)
	at clojure.core.protocols$fn__8131.invokeStatic(protocols.clj:75)
	at clojure.core.protocols$fn__8131.invoke(protocols.clj:75)
	at clojure.core.protocols$fn__8073$G__8068__8086.invoke(protocols.clj:13)
	at clojure.core$reduce.invokeStatic(core.clj:6828)
	at clojure.core$mapv.invokeStatic(core.clj:6903)
	at clojure.core$mapv.invoke(core.clj:6903)
	at leiningen.with_profile$with_profile.invokeStatic(with_profile.clj:85)
	at leiningen.with_profile$with_profile.doInvoke(with_profile.clj:63)
	at clojure.lang.RestFn.invoke(RestFn.java:445)
	at clojure.lang.AFn.applyToHelper(AFn.java:160)
	at clojure.lang.RestFn.applyTo(RestFn.java:132)
	at clojure.lang.Var.applyTo(Var.java:705)
	at clojure.core$apply.invokeStatic(core.clj:667)
	at clojure.core$apply.invoke(core.clj:660)
	at leiningen.core.main$partial_task$fn__6592.doInvoke(main.clj:284)
	at clojure.lang.RestFn.applyTo(RestFn.java:139)
	at clojure.lang.AFunction$1.doInvoke(AFunction.java:31)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:667)
	at clojure.core$apply.invoke(core.clj:660)
	at leiningen.core.main$apply_task.invokeStatic(main.clj:334)
	at leiningen.core.main$apply_task.invoke(main.clj:320)
	at leiningen.core.main$resolve_and_apply.invokeStatic(main.clj:343)
	at leiningen.core.main$resolve_and_apply.invoke(main.clj:336)
	at leiningen.core.main$_main$fn__6681.invoke(main.clj:452)
	at leiningen.core.main$_main.invokeStatic(main.clj:442)
	at leiningen.core.main$_main.doInvoke(main.clj:439)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.lang.Var.applyTo(Var.java:705)
	at clojure.core$apply.invokeStatic(core.clj:665)
	at clojure.main$main_opt.invokeStatic(main.clj:491)
	at clojure.main$main_opt.invoke(main.clj:487)
	at clojure.main$main.invokeStatic(main.clj:598)
	at clojure.main$main.doInvoke(main.clj:561)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.lang.Var.applyTo(Var.java:705)
	at clojure.main.main(main.java:37)

How to build with datomic pro ?

I have added

:mvn/repos {"my.datomic.com" {:url "https://my.datomic.com/repo"}}

and added my cedentials to ~/.m2/settings.xml

but when I run lein with-profiles +datomic-pro uberjar

I get

Could not find artifact com.datomic:datomic-pro:jar:0.9.5561.62 in central (https://repo1.maven.org/maven2/)
Could not find artifact com.datomic:datomic-pro:jar:0.9.5561.62 in clojars (https://repo.clojars.org/)
This could be due to a typo in :dependencies, file system permissions, or network issues.
If you are behind a proxy, try setting the 'http_proxy' environment variable.
Uberjar aborting because jar failed: Could not resolve dependencies
Error encountered performing task 'uberjar' with profile(s): 'base,system,user,provided,dev,datomic-pro'
Uberjar aborting because jar failed: Could not resolve dependencies

Whats the right way to get the datomic-pro plugin working ?

Error at `(go)` in "Developing" instructions

I was blocked while following the Developing instructions due to an error when trying to evaluate (go) in the CIDER REPL connected to the nREPL running on 4444 (started with bin/start_metabase).

The error is:

user> (go)
Execution error (ClassCastException) at user/setup-db! (user.clj:18).
class clojure.lang.Delay cannot be cast to class java.util.concurrent.Future (clojure.lang.Delay is in unnamed module of loader 'app'; java.util.concurrent.Future is in module java.base of loader 'bootstrap')

The *cider-error* buffer shows:

  Show: Project-Only All 
  Hide: Clojure Java REPL Tooling Duplicates  (0 frames hidden)

1. Unhandled java.lang.ClassCastException
   class clojure.lang.Delay cannot be cast to class
   java.util.concurrent.Future (clojure.lang.Delay is in unnamed
   module of loader 'app'; java.util.concurrent.Future is in module
   java.base of loader 'bootstrap')

                  core.clj: 2298  clojure.core/deref-future
                  core.clj: 2324  clojure.core/deref
                  core.clj: 2306  clojure.core/deref
                  core.clj: 2625  clojure.core/partial/fn
                  Var.java:  388  clojure.lang.Var/invoke
                  user.clj:   18  user/setup-db!
                  user.clj:   17  user/setup-db!
                  user.clj:   28  user/go
                  user.clj:   23  user/go
                      REPL:   17  user/eval85939
                      REPL:   17  user/eval85939
             Compiler.java: 7176  clojure.lang.Compiler/eval
             Compiler.java: 7131  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  clojure.core/eval
                  main.clj:  414  clojure.main/repl/read-eval-print/fn
                  main.clj:  414  clojure.main/repl/read-eval-print
                  main.clj:  435  clojure.main/repl/fn
                  main.clj:  435  clojure.main/repl
                  main.clj:  345  clojure.main/repl
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   79  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   55  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  142  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  171  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  170  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  834  java.lang.Thread/run

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.